Skip to content

Commit d20e397

Browse files
ilya-lesikovdistorhead
authored andcommitted
feat(staged-dockerfile): use contents of Copy/Add sources in checksum
Signed-off-by: Ilya Lesikov <ilya@lesikov.com>
1 parent 824c5bb commit d20e397

File tree

10 files changed

+129
-18
lines changed

10 files changed

+129
-18
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ require (
6767
go.opentelemetry.io/otel/sdk v1.7.0
6868
go.opentelemetry.io/otel/trace v1.7.0
6969
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
70+
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
7071
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462
7172
gopkg.in/errgo.v2 v2.1.0
7273
gopkg.in/ini.v1 v1.66.2

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,6 +2299,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
22992299
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
23002300
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
23012301
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
2302+
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
23022303
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
23032304
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
23042305
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

pkg/build/image/build_context_archive.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io/ioutil"
77
"os"
88
"path/filepath"
9+
"sort"
910

1011
"github.com/werf/logboek"
1112
"github.com/werf/werf/pkg/container_backend"
@@ -69,6 +70,10 @@ func (a *BuildContextArchive) Path() string {
6970
}
7071

7172
func (a *BuildContextArchive) ExtractOrGetExtractedDir(ctx context.Context) (string, error) {
73+
if a.path == "" {
74+
panic("extract should not be called before create")
75+
}
76+
7277
if a.extractionDir != "" {
7378
return a.extractionDir, nil
7479
}
@@ -107,12 +112,25 @@ func (a *BuildContextArchive) CleanupExtractedDir(ctx context.Context) {
107112
}
108113

109114
func (a *BuildContextArchive) CalculatePathsChecksum(ctx context.Context, paths []string) (string, error) {
115+
sort.Strings(paths)
116+
paths = util.UniqStrings(paths)
117+
110118
dir, err := a.ExtractOrGetExtractedDir(ctx)
111119
if err != nil {
112120
return "", fmt.Errorf("unable to access context directory: %w", err)
113121
}
114122

115-
_ = dir
123+
var pathsHashes []string
124+
for _, path := range paths {
125+
p := filepath.Join(dir, path)
126+
127+
hash, err := util.HashContentsAndPathsRecurse(p)
128+
if err != nil {
129+
return "", fmt.Errorf("unable to calculate hash: %w", err)
130+
}
131+
132+
pathsHashes = append(pathsHashes, hash)
133+
}
116134

117-
return "", nil
135+
return util.Sha256Hash(pathsHashes...), nil
118136
}

pkg/build/stage/instruction/add.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package instruction
33
import (
44
"context"
55
"fmt"
6+
"strings"
7+
8+
"github.com/containers/buildah/copier"
69

710
"github.com/werf/werf/pkg/build/stage"
811
"github.com/werf/werf/pkg/config"
@@ -34,11 +37,20 @@ func (stg *Add) GetDependencies(ctx context.Context, c stage.Conveyor, cb contai
3437
args = append(args, "Chown", stg.instruction.Data.Chown)
3538
args = append(args, "Chmod", stg.instruction.Data.Chmod)
3639

37-
pathsChecksum, err := buildContextArchive.CalculatePathsChecksum(ctx, stg.instruction.Data.Src)
38-
if err != nil {
39-
return "", fmt.Errorf("unable to calculate build context paths checksum: %w", err)
40+
var fileGlobSrc []string
41+
for _, src := range stg.instruction.Data.Src {
42+
if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
43+
fileGlobSrc = append(fileGlobSrc, src)
44+
}
45+
}
46+
47+
if len(fileGlobSrc) > 0 {
48+
if srcChecksum, err := calculateBuildContextGlobsChecksum(ctx, fileGlobSrc, true, buildContextArchive); err != nil {
49+
return "", fmt.Errorf("unable to calculate build context globs checksum: %w", err)
50+
} else {
51+
args = append(args, "SrcChecksum", srcChecksum)
52+
}
4053
}
41-
args = append(args, "SrcChecksum", pathsChecksum)
4254

4355
// TODO(staged-dockerfile): support http src and --checksum option: https://docs.docker.com/engine/reference/builder/#verifying-a-remote-file-checksum-add---checksumchecksum-http-src-dest
4456
// TODO(staged-dockerfile): support git ref: https://docs.docker.com/engine/reference/builder/#adding-a-git-repository-add-git-ref-dir
@@ -47,3 +59,36 @@ func (stg *Add) GetDependencies(ctx context.Context, c stage.Conveyor, cb contai
4759

4860
return util.Sha256Hash(args...), nil
4961
}
62+
63+
func calculateBuildContextGlobsChecksum(ctx context.Context, fileGlobs []string, checkForArchives bool, buildContextArchive container_backend.BuildContextArchiver) (string, error) {
64+
contextDir, err := buildContextArchive.ExtractOrGetExtractedDir(ctx)
65+
if err != nil {
66+
return "", fmt.Errorf("unable to get build context dir: %w", err)
67+
}
68+
69+
globStats, err := copier.Stat(contextDir, contextDir, copier.StatOptions{CheckForArchives: checkForArchives}, fileGlobs)
70+
if err != nil {
71+
return "", fmt.Errorf("unable to stat globs: %w", err)
72+
}
73+
if len(globStats) == 0 {
74+
return "", fmt.Errorf("no glob matches for globs: %v", fileGlobs)
75+
}
76+
77+
var matches []string
78+
for _, globStat := range globStats {
79+
if globStat.Error != "" {
80+
return "", fmt.Errorf("unable to stat glob %q: %w", globStat.Glob, globStat.Error)
81+
}
82+
83+
for _, match := range globStat.Globbed {
84+
matches = append(matches, match)
85+
}
86+
}
87+
88+
pathsChecksum, err := buildContextArchive.CalculatePathsChecksum(ctx, matches)
89+
if err != nil {
90+
return "", fmt.Errorf("unable to calculate build context paths checksum: %w", err)
91+
}
92+
93+
return pathsChecksum, nil
94+
}

pkg/build/stage/instruction/copy.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package instruction
22

33
import (
44
"context"
5+
"fmt"
56

67
"github.com/werf/werf/pkg/build/stage"
78
"github.com/werf/werf/pkg/config"
@@ -45,6 +46,16 @@ func (stg *Copy) GetDependencies(ctx context.Context, c stage.Conveyor, cb conta
4546
args = append(args, "Chmod", stg.instruction.Data.Chmod)
4647
args = append(args, "ExpandedFrom", stg.backendInstruction.From)
4748

49+
if stg.UsesBuildContext() {
50+
if srcChecksum, err := calculateBuildContextGlobsChecksum(ctx, stg.instruction.Data.Src, false, buildContextArchive); err != nil {
51+
return "", fmt.Errorf("unable to calculate build context globs checksum: %w", err)
52+
} else {
53+
args = append(args, "SrcChecksum", srcChecksum)
54+
}
55+
}
56+
57+
// TODO(ilya-lesikov): should checksum of files from other image be calculated if --from specified?
58+
4859
// TODO(staged-dockerfile): support --link option: https://docs.docker.com/engine/reference/builder/#copy---link
4960

5061
return util.Sha256Hash(args...), nil

pkg/build/stage/instruction/run.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,8 @@ func (stg *Run) GetDependencies(ctx context.Context, c stage.Conveyor, cb contai
2828

2929
args = append(args, "Instruction", stg.instruction.Data.Name())
3030
args = append(args, append([]string{"Command"}, stg.instruction.Data.Command...)...)
31+
32+
// TODO(ilya-lesikov): should bind mount with context as src be counted as dependency?
33+
3134
return util.Sha256Hash(args...), nil
3235
}

pkg/buildah/common.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,9 @@ type ConfigOpts struct {
110110
type CopyOpts struct {
111111
CommonOpts
112112

113-
Chown string
114-
Chmod string
113+
Chown string
114+
Chmod string
115+
Ignores []string
115116
}
116117

117118
type AddOpts struct {
@@ -120,6 +121,7 @@ type AddOpts struct {
120121
ContextDir string
121122
Chown string
122123
Chmod string
124+
Ignores []string
123125
}
124126

125127
type (

pkg/buildah/native_linux.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -574,10 +574,7 @@ func (b *NativeBuildah) Copy(ctx context.Context, container, contextDir string,
574574
Chmod: opts.Chmod,
575575
PreserveOwnership: false,
576576
ContextDir: contextDir,
577-
// TODO(ilya-lesikov): ignore file?
578-
Excludes: nil,
579-
// TODO(ilya-lesikov): ignore file?
580-
IgnoreFile: "",
577+
Excludes: opts.Ignores,
581578
}, absSrc...); err != nil {
582579
return fmt.Errorf("error copying files to %q: %w", dst, err)
583580
}
@@ -610,10 +607,7 @@ func (b *NativeBuildah) Add(ctx context.Context, container string, src []string,
610607
Chown: opts.Chown,
611608
PreserveOwnership: false,
612609
ContextDir: opts.ContextDir,
613-
// TODO(ilya-lesikov): ignore file?
614-
Excludes: nil,
615-
// TODO(ilya-lesikov): ignore file?
616-
IgnoreFile: "",
610+
Excludes: opts.Ignores,
617611
}, expandedSrc...); err != nil {
618612
return fmt.Errorf("error adding files to %q: %w", dst, err)
619613
}

pkg/dockerfile/frontend/buildkit_dockerfile.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,17 @@ func extractSrcAndDst(sourcesAndDest instructions.SourcesAndDest) ([]string, str
108108
// /home/user1/go/pkg/mod/github.com/moby/buildkit@v0.8.2/frontend/dockerfile/parser/parser.go:250
109109
var src []string
110110
for _, s := range sourcesAndDest[0 : len(sourcesAndDest)-1] {
111-
s, _ = strconv.Unquote(s)
111+
if unquoted, err := strconv.Unquote(s); err == nil {
112+
s = unquoted
113+
}
114+
112115
src = append(src, s)
113116
}
114117

115-
dst, _ := strconv.Unquote(sourcesAndDest[len(sourcesAndDest)-1])
118+
dst := sourcesAndDest[len(sourcesAndDest)-1]
119+
if unquoted, err := strconv.Unquote(dst); err == nil {
120+
dst = unquoted
121+
}
116122

117123
return src, dst
118124
}

pkg/util/hashsum.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ package util
33
import (
44
"crypto/sha256"
55
"fmt"
6+
"io"
7+
"os"
8+
"path/filepath"
69
"strings"
710

811
"github.com/spaolacci/murmur3"
912
"golang.org/x/crypto/sha3"
13+
"golang.org/x/mod/sumdb/dirhash"
1014
)
1115

1216
// LegacyMurmurHash function returns a hash of non-fixed length (1-8 symbols)
@@ -31,6 +35,32 @@ func Sha256Hash(args ...string) string {
3135
return fmt.Sprintf("%x", sum)
3236
}
3337

38+
// For file: hash contents of file with its name.
39+
// For directory: hash contents of all files in directory, along with their relative filenames.
40+
func HashContentsAndPathsRecurse(path string) (string, error) {
41+
path = filepath.Clean(path)
42+
43+
fi, err := os.Stat(path)
44+
if err != nil {
45+
return "", fmt.Errorf("unable to stat %q: %w", path, err)
46+
}
47+
48+
var hash string
49+
if fi.IsDir() {
50+
if hash, err = dirhash.HashDir(path, "/", dirhash.Hash1); err != nil {
51+
return "", fmt.Errorf("unable to calculate hash for dir %q: %w", path, err)
52+
}
53+
} else {
54+
if hash, err = dirhash.Hash1([]string{filepath.Base(path)}, func(_ string) (io.ReadCloser, error) {
55+
return os.Open(path)
56+
}); err != nil {
57+
return "", fmt.Errorf("unable to calculate hash for file %q: %w", path, err)
58+
}
59+
}
60+
61+
return hash, nil
62+
}
63+
3464
func prepareHashArgs(args ...string) string {
3565
return strings.Join(args, ":::")
3666
}

0 commit comments

Comments
 (0)