Merge branch 'master' into registry-mirror
This commit is contained in:
commit
fbdb8f39c8
|
|
@ -0,0 +1,191 @@
|
|||
# This file contains all available configuration options
|
||||
# with their default values.
|
||||
|
||||
# options for analysis running
|
||||
run:
|
||||
# default concurrency is a available CPU number
|
||||
concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 1m
|
||||
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: true
|
||||
|
||||
# list of build tags, all linters use it. Default is empty list.
|
||||
build-tags:
|
||||
|
||||
# which dirs to skip: they won't be analyzed;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but next dirs are always skipped independently
|
||||
# from this option's value:
|
||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
skip-dirs:
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files:
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number"
|
||||
format: colored-line-number
|
||||
|
||||
# print lines of code with issue, default is true
|
||||
print-issued-lines: true
|
||||
|
||||
# print linter name in the end of issue text, default is true
|
||||
print-linter-name: true
|
||||
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: false
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
#check-shadowing: true
|
||||
|
||||
# Obtain type information from installed (to $GOPATH/pkg) package files:
|
||||
# golangci-lint will execute `go install -i` and `go test -i` for analyzed packages
|
||||
# before analyzing them.
|
||||
# By default this option is disabled and govet gets type information by loader from source code.
|
||||
# Loading from source code is slow, but it's done only once for all linters.
|
||||
# Go-installing of packages first time is much slower than loading them from source code,
|
||||
# therefore this option is disabled by default.
|
||||
# But repeated installation is fast in go >= 1.10 because of build caching.
|
||||
# Enable this option only if all conditions are met:
|
||||
# 1. you use only "fast" linters (--fast e.g.): no program loading occurs
|
||||
# 2. you use go >= 1.10
|
||||
# 3. you do repeated runs (false for CI) or cache $GOPATH/pkg or `go env GOCACHE` dir in CI.
|
||||
#use-installed-packages: false
|
||||
golint:
|
||||
# minimal confidence for issues, default is 0.8
|
||||
min-confidence: 0.8
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
#gocyclo:
|
||||
# # minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
# min-complexity: 10
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
#dupl:
|
||||
# # tokens count to trigger issue, 150 by default
|
||||
# threshold: 100
|
||||
goconst:
|
||||
# minimal length of string constant, 3 by default
|
||||
min-len: 3
|
||||
# minimal occurrences count to trigger, 3 by default
|
||||
min-occurrences: 3
|
||||
#depguard:
|
||||
# list-type: blacklist
|
||||
# include-go-root: false
|
||||
# packages:
|
||||
# - github.com/davecgh/go-spew/spew
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
#lll:
|
||||
# # max line length, lines longer will be reported. Default is 120.
|
||||
# # '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||
# line-length: 120
|
||||
# # tab width in spaces. Default to 1.
|
||||
# tab-width: 1
|
||||
unused:
|
||||
# treat code as a program (not a library) and report unused exported identifiers; default is false.
|
||||
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
unparam:
|
||||
# call graph construction algorithm (cha, rta). In general, use cha for libraries,
|
||||
# and rta for programs with main packages. Default is cha.
|
||||
algo: cha
|
||||
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
#nakedret:
|
||||
# # make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||
# max-func-lines: 30
|
||||
#prealloc:
|
||||
# # XXX: we don't recommend using this linter before doing performance profiling.
|
||||
# # For most programs usage of prealloc will be a premature optimization.
|
||||
|
||||
# # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
||||
# # True by default.
|
||||
# simple: true
|
||||
# range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||
# for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- goconst
|
||||
- goimports
|
||||
- golint
|
||||
- interfacer
|
||||
- maligned
|
||||
- misspell
|
||||
- unconvert
|
||||
- unparam
|
||||
enable-all: false
|
||||
disable:
|
||||
- errcheck
|
||||
- gas
|
||||
disable-all: false
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
fast: false
|
||||
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
exclude:
|
||||
|
||||
# Independently from option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`.
|
||||
# Default value for this option is true.
|
||||
exclude-use-default: true
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-per-linter: 50
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same: 3
|
||||
|
||||
# Show only new issues: if there are unstaged changes or untracked files,
|
||||
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
|
||||
# It's a super-useful option for integration of golangci-lint into existing
|
||||
# large codebase. It's not practical to fix all existing issues at the moment
|
||||
# of integration: much better don't allow issues in new code.
|
||||
# Default is false.
|
||||
new: false
|
||||
|
||||
## Show only new issues created after git revision `REV`
|
||||
#new-from-rev: REV
|
||||
|
||||
## Show only new issues created in git patch with set file path.
|
||||
#new-from-patch: path/to/patch/file
|
||||
54
README.md
54
README.md
|
|
@ -46,33 +46,33 @@ _If you are interested in contributing to kaniko, see [DEVELOPMENT.md](DEVELOPME
|
|||
- [Pushing to Docker Hub](#pushing-to-docker-hub)
|
||||
- [Pushing to Amazon ECR](#pushing-to-amazon-ecr)
|
||||
- [Additional Flags](#additional-flags)
|
||||
- [--build-arg](#build-arg)
|
||||
- [--cache](#cache)
|
||||
- [--cache-dir](#cache-dir)
|
||||
- [--cache-repo](#cache-repo)
|
||||
- [--digest-file](#digest-file)
|
||||
- [--oci-layout-path](#oci-layout-path)
|
||||
- [--insecure-registry](#insecure-registry)
|
||||
- [--skip-tls-verify-registry](#skip-tls-verify-registry)
|
||||
- [--cleanup](#cleanup)
|
||||
- [--insecure](#insecure)
|
||||
- [--insecure-pull](#insecure-pull)
|
||||
- [--no-push](#no-push)
|
||||
- [--build-arg](#--build-arg)
|
||||
- [--cache](#--cache)
|
||||
- [--cache-dir](#--cache-dir)
|
||||
- [--cache-repo](#--cache-repo)
|
||||
- [--digest-file](#--digest-file)
|
||||
- [--oci-layout-path](#--oci-layout-path)
|
||||
- [--insecure-registry](#--insecure-registry)
|
||||
- [--skip-tls-verify-registry](#--skip-tls-verify-registry)
|
||||
- [--cleanup](#--cleanup)
|
||||
- [--insecure](#--insecure)
|
||||
- [--insecure-pull](#--insecure-pull)
|
||||
- [--no-push](#--no-push)
|
||||
- [--registry-mirror](#--registry-mirror)
|
||||
- [--reproducible](#reproducible)
|
||||
- [--single-snapshot](#single-snapshot)
|
||||
- [--skip-tls-verify](#skip-tls-verify)
|
||||
- [--skip-tls-verify-pull](#skip-tls-verify-pull)
|
||||
- [--snapshotMode](#snapshotmode)
|
||||
- [--target](#target)
|
||||
- [--tarPath](#tarpath)
|
||||
- [--verbosity](#verbosity)
|
||||
- [Debug Image](#debug-image)
|
||||
- [--reproducible](#--reproducible)
|
||||
- [--single-snapshot](#--single-snapshot)
|
||||
- [--skip-tls-verify](#--skip-tls-verify)
|
||||
- [--skip-tls-verify-pull](#--skip-tls-verify-pull)
|
||||
- [--snapshotMode](#--snapshotmode)
|
||||
- [--target](#--target)
|
||||
- [--tarPath](#--tarpath)
|
||||
- [--verbosity](#--verbosity)
|
||||
- [Debug Image](#debug-image)
|
||||
- [Security](#security)
|
||||
- [Comparison with Other Tools](#comparison-with-other-tools)
|
||||
- [Community](#community)
|
||||
- [Limitations](#limitations)
|
||||
- [mtime and snapshotting](#mtime-and-snapshotting)
|
||||
- [mtime and snapshotting](#mtime-and-snapshotting)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
|
|
@ -288,7 +288,7 @@ as a remote image destination:
|
|||
### Caching
|
||||
|
||||
#### Caching Layers
|
||||
kaniko currently can cache layers created by `RUN` commands in a remote repository.
|
||||
kaniko can cache layers created by `RUN` commands in a remote repository.
|
||||
Before executing a command, kaniko checks the cache for the layer.
|
||||
If it exists, kaniko will pull and extract the cached layer instead of executing the command.
|
||||
If not, kaniko will execute the command and then push the newly created layer to the cache.
|
||||
|
|
@ -299,7 +299,7 @@ If this flag isn't provided, a cached repo will be inferred from the `--destinat
|
|||
|
||||
#### Caching Base Images
|
||||
|
||||
kaniko can cache images in a local directory that can be volume mounted into the kaniko image.
|
||||
kaniko can cache images in a local directory that can be volume mounted into the kaniko pod.
|
||||
To do so, the cache must first be populated, as it is read-only. We provide a kaniko cache warming
|
||||
image at `gcr.io/kaniko-project/warmer`:
|
||||
|
||||
|
|
@ -310,7 +310,7 @@ docker run -v $(pwd):/workspace gcr.io/kaniko-project/warmer:latest --cache-dir=
|
|||
`--image` can be specified for any number of desired images.
|
||||
This command will cache those images by digest in a local directory named `cache`.
|
||||
Once the cache is populated, caching is opted into with the same `--cache=true` flag as above.
|
||||
The location of the local cache is provided via the `--cache-dir` flag, defaulting at `/cache` as with the cache warmer.
|
||||
The location of the local cache is provided via the `--cache-dir` flag, defaulting to `/cache` as with the cache warmer.
|
||||
See the `examples` directory for how to use with kubernetes clusters and persistent cache volumes.
|
||||
|
||||
### Pushing to Different Registries
|
||||
|
|
@ -323,7 +323,7 @@ kaniko comes with support for GCR, Docker `config.json` and Amazon ECR, but conf
|
|||
|
||||
Get your docker registry user and password encoded in base64
|
||||
|
||||
echo USER:PASSWORD | base64
|
||||
echo -n USER:PASSWORD | base64
|
||||
|
||||
Create a `config.json` file with your Docker registry url and the previous generated base64 string
|
||||
|
||||
|
|
@ -346,7 +346,7 @@ Run kaniko with the `config.json` inside `/kaniko/.docker/config.json`
|
|||
The Amazon ECR [credential helper](https://github.com/awslabs/amazon-ecr-credential-helper) is built in to the kaniko executor image.
|
||||
To configure credentials, you will need to do the following:
|
||||
|
||||
1. Update the `credHelpers` section of [config.json](https://github.com/GoogleContainerTools/kaniko/blob/master/files/config.json) with the specific URI of your ECR registry:
|
||||
1. Update the `credHelpers` section of [config.json](https://github.com/awslabs/amazon-ecr-credential-helper#configuration) with the specific URI of your ECR registry:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,15 +23,4 @@ if ! [ -x "$(command -v golangci-lint)" ]; then
|
|||
${DIR}/install_golint.sh -b $GOPATH/bin v1.9.3
|
||||
fi
|
||||
|
||||
golangci-lint run \
|
||||
--no-config \
|
||||
-E goconst \
|
||||
-E goimports \
|
||||
-E golint \
|
||||
-E interfacer \
|
||||
-E maligned \
|
||||
-E misspell \
|
||||
-E unconvert \
|
||||
-E unparam \
|
||||
-D errcheck \
|
||||
-D gas
|
||||
golangci-lint run
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
|
|
@ -24,6 +25,12 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var RootDir string
|
||||
|
||||
func init() {
|
||||
RootDir = constants.RootDir
|
||||
}
|
||||
|
||||
type CurrentCacheKey func() (string, error)
|
||||
|
||||
type DockerCommand interface {
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ type CachingCopyCommand struct {
|
|||
func (cr *CachingCopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Infof("Found cached layer, extracting to filesystem")
|
||||
var err error
|
||||
cr.extractedFiles, err = util.GetFSFromImage(constants.RootDir, cr.img)
|
||||
cr.extractedFiles, err = util.GetFSFromImage(RootDir, cr.img)
|
||||
logrus.Infof("extractedFiles: %s", cr.extractedFiles)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "extracting fs from image")
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ func targetStage(stages []instructions.Stage, target string) (int, error) {
|
|||
|
||||
// resolveStages resolves any calls to previous stages with names to indices
|
||||
// Ex. --from=second_stage should be --from=1 for easier processing later on
|
||||
// As third party library lowers stage name in FROM instruction, this function resolves stage case insensitively.
|
||||
func resolveStages(stages []instructions.Stage) {
|
||||
nameToIndex := make(map[string]string)
|
||||
for i, stage := range stages {
|
||||
|
|
@ -214,7 +215,7 @@ func resolveStages(stages []instructions.Stage) {
|
|||
switch c := cmd.(type) {
|
||||
case *instructions.CopyCommand:
|
||||
if c.From != "" {
|
||||
if val, ok := nameToIndex[c.From]; ok {
|
||||
if val, ok := nameToIndex[strings.ToLower(c.From)]; ok {
|
||||
c.From = val
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,8 +197,14 @@ func Test_resolveStages(t *testing.T) {
|
|||
FROM scratch AS second
|
||||
COPY --from=0 /hi /hi2
|
||||
|
||||
FROM scratch
|
||||
FROM scratch AS tHiRd
|
||||
COPY --from=second /hi2 /hi3
|
||||
COPY --from=1 /hi2 /hi3
|
||||
|
||||
FROM scratch
|
||||
COPY --from=thIrD /hi3 /hi4
|
||||
COPY --from=third /hi3 /hi4
|
||||
COPY --from=2 /hi3 /hi4
|
||||
`
|
||||
stages, _, err := Parse([]byte(dockerfile))
|
||||
if err != nil {
|
||||
|
|
@ -209,11 +215,14 @@ func Test_resolveStages(t *testing.T) {
|
|||
if index == 0 {
|
||||
continue
|
||||
}
|
||||
copyCmd := stage.Commands[0].(*instructions.CopyCommand)
|
||||
expectedStage := strconv.Itoa(index - 1)
|
||||
if copyCmd.From != expectedStage {
|
||||
t.Fatalf("unexpected copy command: %s resolved to stage %s, expected %s", copyCmd.String(), copyCmd.From, expectedStage)
|
||||
for _, command := range stage.Commands {
|
||||
copyCmd := command.(*instructions.CopyCommand)
|
||||
if copyCmd.From != expectedStage {
|
||||
t.Fatalf("unexpected copy command: %s resolved to stage %s, expected %s", copyCmd.String(), copyCmd.From, expectedStage)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/otiai10/copy"
|
||||
otiai10Cpy "github.com/otiai10/copy"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
|
||||
|
|
@ -52,12 +52,21 @@ import (
|
|||
// This is the size of an empty tar in Go
|
||||
const emptyTarSize = 1024
|
||||
|
||||
type cachePusher func(*config.KanikoOptions, string, string, string) error
|
||||
type snapShotter interface {
|
||||
Init() error
|
||||
TakeSnapshotFS() (string, error)
|
||||
TakeSnapshot([]string) (string, error)
|
||||
}
|
||||
|
||||
// stageBuilder contains all fields necessary to build one stage of a Dockerfile
|
||||
type stageBuilder struct {
|
||||
stage config.KanikoStage
|
||||
image v1.Image
|
||||
cf *v1.ConfigFile
|
||||
snapshotter *snapshot.Snapshotter
|
||||
snapshotter snapShotter
|
||||
layerCache cache.LayerCache
|
||||
pushCache cachePusher
|
||||
baseImageDigest string
|
||||
finalCacheKey string
|
||||
opts *config.KanikoOptions
|
||||
|
|
@ -103,6 +112,10 @@ func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage, cross
|
|||
opts: opts,
|
||||
crossStageDeps: crossStageDeps,
|
||||
digestToCacheKeyMap: dcm,
|
||||
layerCache: &cache.RegistryCache{
|
||||
Opts: opts,
|
||||
},
|
||||
pushCache: pushLayerToCache,
|
||||
}
|
||||
|
||||
for _, cmd := range s.stage.Commands {
|
||||
|
|
@ -138,9 +151,6 @@ func (s *stageBuilder) optimize(compositeKey CompositeCache, cfg v1.Config) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
layerCache := &cache.RegistryCache{
|
||||
Opts: s.opts,
|
||||
}
|
||||
stopCache := false
|
||||
// Possibly replace commands with their cached implementations.
|
||||
// We walk through all the commands, running any commands that only operate on metadata.
|
||||
|
|
@ -154,21 +164,21 @@ func (s *stageBuilder) optimize(compositeKey CompositeCache, cfg v1.Config) erro
|
|||
// If the command uses files from the context, add them.
|
||||
files, err := command.FilesUsedFromContext(&cfg, s.args)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to get files used from context")
|
||||
}
|
||||
for _, f := range files {
|
||||
if err := compositeKey.AddPath(f); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to add path to composite key")
|
||||
}
|
||||
}
|
||||
|
||||
ck, err := compositeKey.Hash()
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to hash composite key")
|
||||
}
|
||||
s.finalCacheKey = ck
|
||||
if command.ShouldCacheOutput() && !stopCache {
|
||||
img, err := layerCache.RetrieveLayer(ck)
|
||||
img, err := s.layerCache.RetrieveLayer(ck)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to retrieve layer: %s", err)
|
||||
logrus.Infof("No cached layer found for cmd %s", command.String())
|
||||
|
|
@ -205,7 +215,7 @@ func (s *stageBuilder) build() error {
|
|||
|
||||
// Apply optimizations to the instructions.
|
||||
if err := s.optimize(*compositeKey, s.cf.Config); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to optimize instructions")
|
||||
}
|
||||
|
||||
// Unpack file system to root if we need to.
|
||||
|
|
@ -224,14 +234,14 @@ func (s *stageBuilder) build() error {
|
|||
if shouldUnpack {
|
||||
t := timing.Start("FS Unpacking")
|
||||
if _, err := util.GetFSFromImage(constants.RootDir, s.image); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to get filesystem from image")
|
||||
}
|
||||
timing.DefaultRun.Stop(t)
|
||||
} else {
|
||||
logrus.Info("Skipping unpacking as no commands require it.")
|
||||
}
|
||||
if err := util.DetectFilesystemWhitelist(constants.WhitelistPath); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to check filesystem whitelist")
|
||||
}
|
||||
// Take initial snapshot
|
||||
t := timing.Start("Initial FS snapshot")
|
||||
|
|
@ -252,17 +262,17 @@ func (s *stageBuilder) build() error {
|
|||
// If the command uses files from the context, add them.
|
||||
files, err := command.FilesUsedFromContext(&s.cf.Config, s.args)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to get files used from context")
|
||||
}
|
||||
for _, f := range files {
|
||||
if err := compositeKey.AddPath(f); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to add path to composite key %v", f))
|
||||
}
|
||||
}
|
||||
logrus.Info(command.String())
|
||||
|
||||
if err := command.ExecuteCommand(&s.cf.Config, s.args); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to execute command")
|
||||
}
|
||||
files = command.FilesToSnapshot()
|
||||
timing.DefaultRun.Stop(t)
|
||||
|
|
@ -273,21 +283,21 @@ func (s *stageBuilder) build() error {
|
|||
|
||||
tarPath, err := s.takeSnapshot(files)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to take snapshot")
|
||||
}
|
||||
|
||||
ck, err := compositeKey.Hash()
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to hash composite key")
|
||||
}
|
||||
// Push layer to cache (in parallel) now along with new config file
|
||||
if s.opts.Cache && command.ShouldCacheOutput() {
|
||||
cacheGroup.Go(func() error {
|
||||
return pushLayerToCache(s.opts, ck, tarPath, command.String())
|
||||
return s.pushCache(s.opts, ck, tarPath, command.String())
|
||||
})
|
||||
}
|
||||
if err := s.saveSnapshotToImage(command.String(), tarPath); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to save snapshot to image")
|
||||
}
|
||||
}
|
||||
if err := cacheGroup.Wait(); err != nil {
|
||||
|
|
@ -343,7 +353,7 @@ func (s *stageBuilder) saveSnapshotToImage(createdBy string, tarPath string) err
|
|||
}
|
||||
fi, err := os.Stat(tarPath)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "tar file path does not exist")
|
||||
}
|
||||
if fi.Size() <= emptyTarSize {
|
||||
logrus.Info("No files were changed, appending empty layer to config. No layer added to image.")
|
||||
|
|
@ -505,7 +515,7 @@ func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
|||
}
|
||||
for _, p := range filesToSave {
|
||||
logrus.Infof("Saving file %s for later use.", p)
|
||||
copy.Copy(p, filepath.Join(dstDir, p))
|
||||
otiai10Cpy.Copy(p, filepath.Join(dstDir, p))
|
||||
}
|
||||
|
||||
// Delete the filesystem
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ limitations under the License.
|
|||
package executor
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -24,6 +27,7 @@ import (
|
|||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/commands"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
|
|
@ -32,6 +36,7 @@ import (
|
|||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Test_reviewConfig(t *testing.T) {
|
||||
|
|
@ -462,3 +467,455 @@ func TestInitializeConfig(t *testing.T) {
|
|||
testutil.CheckDeepEqual(t, tt.expected, actual.Config)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_stageBuilder_optimize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
opts *config.KanikoOptions
|
||||
retrieve bool
|
||||
name string
|
||||
}{
|
||||
{
|
||||
name: "cache enabled and layer not present in cache",
|
||||
opts: &config.KanikoOptions{Cache: true},
|
||||
},
|
||||
{
|
||||
name: "cache enabled and layer present in cache",
|
||||
opts: &config.KanikoOptions{Cache: true},
|
||||
retrieve: true,
|
||||
},
|
||||
{
|
||||
name: "cache disabled and layer not present in cache",
|
||||
opts: &config.KanikoOptions{Cache: false},
|
||||
},
|
||||
{
|
||||
name: "cache disabled and layer present in cache",
|
||||
opts: &config.KanikoOptions{Cache: false},
|
||||
retrieve: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cf := &v1.ConfigFile{}
|
||||
snap := fakeSnapShotter{}
|
||||
lc := &fakeLayerCache{retrieve: tc.retrieve}
|
||||
sb := &stageBuilder{opts: tc.opts, cf: cf, snapshotter: snap, layerCache: lc}
|
||||
ck := CompositeCache{}
|
||||
file, err := ioutil.TempFile("", "foo")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
command := MockDockerCommand{
|
||||
contextFiles: []string{file.Name()},
|
||||
cacheCommand: MockCachedDockerCommand{},
|
||||
}
|
||||
sb.cmds = []commands.DockerCommand{command}
|
||||
err = sb.optimize(ck, cf.Config)
|
||||
if err != nil {
|
||||
t.Errorf("Expected error to be nil but was %v", err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_stageBuilder_build(t *testing.T) {
|
||||
type testcase struct {
|
||||
description string
|
||||
opts *config.KanikoOptions
|
||||
layerCache *fakeLayerCache
|
||||
expectedCacheKeys []string
|
||||
pushedCacheKeys []string
|
||||
commands []commands.DockerCommand
|
||||
fileName string
|
||||
rootDir string
|
||||
image v1.Image
|
||||
config *v1.ConfigFile
|
||||
}
|
||||
|
||||
testCases := []testcase{
|
||||
func() testcase {
|
||||
dir, files := tempDirAndFile(t)
|
||||
file := files[0]
|
||||
filePath := filepath.Join(dir, file)
|
||||
ch := NewCompositeCache("", "meow")
|
||||
|
||||
ch.AddPath(filePath)
|
||||
hash, err := ch.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't create hash %v", err)
|
||||
}
|
||||
command := MockDockerCommand{
|
||||
contextFiles: []string{filePath},
|
||||
cacheCommand: MockCachedDockerCommand{
|
||||
contextFiles: []string{filePath},
|
||||
},
|
||||
}
|
||||
|
||||
destDir, err := ioutil.TempDir("", "baz")
|
||||
if err != nil {
|
||||
t.Errorf("could not create temp dir %v", err)
|
||||
}
|
||||
return testcase{
|
||||
description: "fake command cache enabled but key not in cache",
|
||||
config: &v1.ConfigFile{Config: v1.Config{WorkingDir: destDir}},
|
||||
opts: &config.KanikoOptions{Cache: true},
|
||||
expectedCacheKeys: []string{hash},
|
||||
pushedCacheKeys: []string{hash},
|
||||
commands: []commands.DockerCommand{command},
|
||||
rootDir: dir,
|
||||
}
|
||||
}(),
|
||||
func() testcase {
|
||||
dir, files := tempDirAndFile(t)
|
||||
file := files[0]
|
||||
filePath := filepath.Join(dir, file)
|
||||
ch := NewCompositeCache("", "meow")
|
||||
|
||||
ch.AddPath(filePath)
|
||||
hash, err := ch.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't create hash %v", err)
|
||||
}
|
||||
command := MockDockerCommand{
|
||||
contextFiles: []string{filePath},
|
||||
cacheCommand: MockCachedDockerCommand{
|
||||
contextFiles: []string{filePath},
|
||||
},
|
||||
}
|
||||
|
||||
destDir, err := ioutil.TempDir("", "baz")
|
||||
if err != nil {
|
||||
t.Errorf("could not create temp dir %v", err)
|
||||
}
|
||||
return testcase{
|
||||
description: "fake command cache enabled and key in cache",
|
||||
opts: &config.KanikoOptions{Cache: true},
|
||||
config: &v1.ConfigFile{Config: v1.Config{WorkingDir: destDir}},
|
||||
layerCache: &fakeLayerCache{
|
||||
retrieve: true,
|
||||
},
|
||||
expectedCacheKeys: []string{hash},
|
||||
pushedCacheKeys: []string{},
|
||||
commands: []commands.DockerCommand{command},
|
||||
rootDir: dir,
|
||||
}
|
||||
}(),
|
||||
{
|
||||
description: "fake command cache disabled and key not in cache",
|
||||
opts: &config.KanikoOptions{Cache: false},
|
||||
},
|
||||
{
|
||||
description: "fake command cache disabled and key in cache",
|
||||
opts: &config.KanikoOptions{Cache: false},
|
||||
layerCache: &fakeLayerCache{
|
||||
retrieve: true,
|
||||
},
|
||||
},
|
||||
func() testcase {
|
||||
dir, filenames := tempDirAndFile(t)
|
||||
filename := filenames[0]
|
||||
filepath := filepath.Join(dir, filename)
|
||||
|
||||
tarContent := generateTar(t, dir, filename)
|
||||
|
||||
ch := NewCompositeCache("", "")
|
||||
ch.AddPath(filepath)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
hash, err := ch.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't create hash %v", err)
|
||||
}
|
||||
copyCommandCacheKey := hash
|
||||
return testcase{
|
||||
description: "copy command cache enabled and key in cache",
|
||||
opts: &config.KanikoOptions{Cache: true},
|
||||
layerCache: &fakeLayerCache{
|
||||
retrieve: true,
|
||||
img: fakeImage{
|
||||
ImageLayers: []v1.Layer{
|
||||
fakeLayer{
|
||||
TarContent: tarContent,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rootDir: dir,
|
||||
expectedCacheKeys: []string{copyCommandCacheKey},
|
||||
// CachingCopyCommand is not pushed to the cache
|
||||
pushedCacheKeys: []string{},
|
||||
commands: getCommands(dir, []instructions.Command{
|
||||
&instructions.CopyCommand{
|
||||
SourcesAndDest: []string{
|
||||
filename, "foo.txt",
|
||||
},
|
||||
},
|
||||
}),
|
||||
fileName: filename,
|
||||
}
|
||||
}(),
|
||||
func() testcase {
|
||||
dir, filenames := tempDirAndFile(t)
|
||||
filename := filenames[0]
|
||||
tarContent := []byte{}
|
||||
destDir, err := ioutil.TempDir("", "baz")
|
||||
if err != nil {
|
||||
t.Errorf("could not create temp dir %v", err)
|
||||
}
|
||||
filePath := filepath.Join(dir, filename)
|
||||
ch := NewCompositeCache("", "")
|
||||
ch.AddPath(filePath)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
hash, err := ch.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't create hash %v", err)
|
||||
}
|
||||
return testcase{
|
||||
description: "copy command cache enabled and key is not in cache",
|
||||
opts: &config.KanikoOptions{Cache: true},
|
||||
config: &v1.ConfigFile{Config: v1.Config{WorkingDir: destDir}},
|
||||
layerCache: &fakeLayerCache{},
|
||||
image: fakeImage{
|
||||
ImageLayers: []v1.Layer{
|
||||
fakeLayer{
|
||||
TarContent: tarContent,
|
||||
},
|
||||
},
|
||||
},
|
||||
rootDir: dir,
|
||||
expectedCacheKeys: []string{hash},
|
||||
pushedCacheKeys: []string{hash},
|
||||
commands: getCommands(dir, []instructions.Command{
|
||||
&instructions.CopyCommand{
|
||||
SourcesAndDest: []string{
|
||||
filename, "foo.txt",
|
||||
},
|
||||
},
|
||||
}),
|
||||
fileName: filename,
|
||||
}
|
||||
}(),
|
||||
func() testcase {
|
||||
dir, filenames := tempDirAndFile(t)
|
||||
filename := filenames[0]
|
||||
tarContent := generateTar(t, filename)
|
||||
destDir, err := ioutil.TempDir("", "baz")
|
||||
if err != nil {
|
||||
t.Errorf("could not create temp dir %v", err)
|
||||
}
|
||||
filePath := filepath.Join(dir, filename)
|
||||
ch := NewCompositeCache("", fmt.Sprintf("COPY %s foo.txt", filename))
|
||||
ch.AddPath(filePath)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.Infof("test composite key %v", ch)
|
||||
hash1, err := ch.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't create hash %v", err)
|
||||
}
|
||||
ch.AddKey(fmt.Sprintf("COPY %s bar.txt", filename))
|
||||
ch.AddPath(filePath)
|
||||
logrus.Infof("test composite key %v", ch)
|
||||
hash2, err := ch.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't create hash %v", err)
|
||||
}
|
||||
ch = NewCompositeCache("", fmt.Sprintf("COPY %s foo.txt", filename))
|
||||
ch.AddKey(fmt.Sprintf("COPY %s bar.txt", filename))
|
||||
ch.AddPath(filePath)
|
||||
logrus.Infof("test composite key %v", ch)
|
||||
hash3, err := ch.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldn't create hash %v", err)
|
||||
}
|
||||
image := fakeImage{
|
||||
ImageLayers: []v1.Layer{
|
||||
fakeLayer{
|
||||
TarContent: tarContent,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
dockerFile := fmt.Sprintf(`
|
||||
FROM ubuntu:16.04
|
||||
COPY %s foo.txt
|
||||
COPY %s bar.txt
|
||||
`, filename, filename)
|
||||
f, _ := ioutil.TempFile("", "")
|
||||
ioutil.WriteFile(f.Name(), []byte(dockerFile), 0755)
|
||||
opts := &config.KanikoOptions{
|
||||
DockerfilePath: f.Name(),
|
||||
}
|
||||
|
||||
stages, err := dockerfile.Stages(opts)
|
||||
if err != nil {
|
||||
t.Errorf("could not parse test dockerfile")
|
||||
}
|
||||
stage := stages[0]
|
||||
cmds := stage.Commands
|
||||
return testcase{
|
||||
description: "cached copy command followed by uncached copy command result in different read and write hashes",
|
||||
opts: &config.KanikoOptions{Cache: true},
|
||||
rootDir: dir,
|
||||
config: &v1.ConfigFile{Config: v1.Config{WorkingDir: destDir}},
|
||||
layerCache: &fakeLayerCache{
|
||||
keySequence: []string{hash1},
|
||||
img: image,
|
||||
},
|
||||
image: image,
|
||||
// hash1 is the read cachekey for the first layer
|
||||
// hash2 is the read cachekey for the second layer
|
||||
expectedCacheKeys: []string{hash1, hash2},
|
||||
// Due to CachingCopyCommand and CopyCommand returning different values the write cache key for the second copy command will never match the read cache key
|
||||
// hash3 is the cachekey used to write to the cache for layer 2
|
||||
pushedCacheKeys: []string{hash3},
|
||||
commands: getCommands(dir, cmds),
|
||||
}
|
||||
}(),
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
var fileName string
|
||||
if tc.commands == nil {
|
||||
file, err := ioutil.TempFile("", "foo")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
command := MockDockerCommand{
|
||||
contextFiles: []string{file.Name()},
|
||||
cacheCommand: MockCachedDockerCommand{
|
||||
contextFiles: []string{file.Name()},
|
||||
},
|
||||
}
|
||||
tc.commands = []commands.DockerCommand{command}
|
||||
fileName = file.Name()
|
||||
} else {
|
||||
fileName = tc.fileName
|
||||
}
|
||||
|
||||
cf := tc.config
|
||||
if cf == nil {
|
||||
cf = &v1.ConfigFile{
|
||||
Config: v1.Config{
|
||||
Env: make([]string, 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
snap := fakeSnapShotter{file: fileName}
|
||||
lc := tc.layerCache
|
||||
if lc == nil {
|
||||
lc = &fakeLayerCache{}
|
||||
}
|
||||
keys := []string{}
|
||||
sb := &stageBuilder{
|
||||
args: &dockerfile.BuildArgs{}, //required or code will panic
|
||||
image: tc.image,
|
||||
opts: tc.opts,
|
||||
cf: cf,
|
||||
snapshotter: snap,
|
||||
layerCache: lc,
|
||||
pushCache: func(_ *config.KanikoOptions, cacheKey, _, _ string) error {
|
||||
keys = append(keys, cacheKey)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
sb.cmds = tc.commands
|
||||
tmp := commands.RootDir
|
||||
if tc.rootDir != "" {
|
||||
commands.RootDir = tc.rootDir
|
||||
}
|
||||
err := sb.build()
|
||||
if err != nil {
|
||||
t.Errorf("Expected error to be nil but was %v", err)
|
||||
}
|
||||
|
||||
assertCacheKeys(t, tc.expectedCacheKeys, lc.receivedKeys, "receive")
|
||||
assertCacheKeys(t, tc.pushedCacheKeys, keys, "push")
|
||||
|
||||
commands.RootDir = tmp
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertCacheKeys(t *testing.T, expectedCacheKeys, actualCacheKeys []string, description string) {
|
||||
if len(expectedCacheKeys) != len(actualCacheKeys) {
|
||||
t.Errorf("expected to %v %v keys but was %v", description, len(expectedCacheKeys), len(actualCacheKeys))
|
||||
}
|
||||
|
||||
sort.Slice(expectedCacheKeys, func(x, y int) bool {
|
||||
return expectedCacheKeys[x] > expectedCacheKeys[y]
|
||||
})
|
||||
sort.Slice(actualCacheKeys, func(x, y int) bool {
|
||||
return actualCacheKeys[x] > actualCacheKeys[y]
|
||||
})
|
||||
for i, key := range expectedCacheKeys {
|
||||
if key != actualCacheKeys[i] {
|
||||
t.Errorf("expected to %v keys %d to be %v but was %v %v", description, i, key, actualCacheKeys[i], actualCacheKeys)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCommands(dir string, cmds []instructions.Command) []commands.DockerCommand {
|
||||
outCommands := make([]commands.DockerCommand, 0)
|
||||
for _, c := range cmds {
|
||||
cmd, err := commands.GetCommand(
|
||||
c,
|
||||
dir,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
outCommands = append(outCommands, cmd)
|
||||
}
|
||||
return outCommands
|
||||
|
||||
}
|
||||
|
||||
func tempDirAndFile(t *testing.T) (string, []string) {
|
||||
filenames := []string{"bar.txt"}
|
||||
|
||||
dir, err := ioutil.TempDir("", "foo")
|
||||
if err != nil {
|
||||
t.Errorf("could not create temp dir %v", err)
|
||||
}
|
||||
for _, filename := range filenames {
|
||||
filepath := filepath.Join(dir, filename)
|
||||
err = ioutil.WriteFile(filepath, []byte(`meow`), 0777)
|
||||
if err != nil {
|
||||
t.Errorf("could not create temp file %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return dir, filenames
|
||||
}
|
||||
func generateTar(t *testing.T, dir string, fileNames ...string) []byte {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
writer := tar.NewWriter(buf)
|
||||
defer writer.Close()
|
||||
|
||||
for _, filename := range fileNames {
|
||||
filePath := filepath.Join(dir, filename)
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
t.Errorf("could not get file info for temp file %v", err)
|
||||
}
|
||||
hdr, err := tar.FileInfoHeader(info, filename)
|
||||
if err != nil {
|
||||
t.Errorf("could not get tar header for temp file %v", err)
|
||||
}
|
||||
|
||||
if err := writer.WriteHeader(hdr); err != nil {
|
||||
t.Errorf("could not write tar header %v", err)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Errorf("could not read tempfile %v", err)
|
||||
}
|
||||
|
||||
if _, err := writer.Write(content); err != nil {
|
||||
t.Errorf("could not write file contents to tar")
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package executor
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_NewCompositeCache(t *testing.T) {
|
||||
r := NewCompositeCache()
|
||||
if reflect.TypeOf(r).String() != "*executor.CompositeCache" {
|
||||
t.Errorf("expected return to be *executor.CompositeCache but was %v", reflect.TypeOf(r).String())
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CompositeCache_AddKey(t *testing.T) {
|
||||
keys := []string{
|
||||
"meow",
|
||||
"purr",
|
||||
}
|
||||
r := NewCompositeCache()
|
||||
r.AddKey(keys...)
|
||||
if len(r.keys) != 2 {
|
||||
t.Errorf("expected keys to have length 2 but was %v", len(r.keys))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CompositeCache_Key(t *testing.T) {
|
||||
r := NewCompositeCache("meow", "purr")
|
||||
k := r.Key()
|
||||
if k != "meow-purr" {
|
||||
t.Errorf("expected result to equal meow-purr but was %v", k)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CompositeCache_Hash(t *testing.T) {
|
||||
r := NewCompositeCache("meow", "purr")
|
||||
h, err := r.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("expected error to be nil but was %v", err)
|
||||
}
|
||||
|
||||
expectedHash := "b4fd5a11af812a11a79d794007c842794cc668c8e7ebaba6d1e6d021b8e06c71"
|
||||
if h != expectedHash {
|
||||
t.Errorf("expected result to equal %v but was %v", expectedHash, h)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CompositeCache_AddPath_dir(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("/tmp", "foo")
|
||||
if err != nil {
|
||||
t.Errorf("got error setting up test %v", err)
|
||||
}
|
||||
|
||||
content := `meow meow meow`
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo.txt"), []byte(content), 0777); err != nil {
|
||||
t.Errorf("got error writing temp file %v", err)
|
||||
}
|
||||
|
||||
fn := func() string {
|
||||
r := NewCompositeCache()
|
||||
if err := r.AddPath(tmpDir); err != nil {
|
||||
t.Errorf("expected error to be nil but was %v", err)
|
||||
}
|
||||
|
||||
if len(r.keys) != 1 {
|
||||
t.Errorf("expected len of keys to be 1 but was %v", len(r.keys))
|
||||
}
|
||||
hash, err := r.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldnt generate hash from test cache")
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
hash1 := fn()
|
||||
hash2 := fn()
|
||||
if hash1 != hash2 {
|
||||
t.Errorf("expected hash %v to equal hash %v", hash1, hash2)
|
||||
}
|
||||
}
|
||||
func Test_CompositeCache_AddPath_file(t *testing.T) {
|
||||
tmpfile, err := ioutil.TempFile("/tmp", "foo.txt")
|
||||
if err != nil {
|
||||
t.Errorf("got error setting up test %v", err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name()) // clean up
|
||||
|
||||
content := `meow meow meow`
|
||||
if _, err := tmpfile.Write([]byte(content)); err != nil {
|
||||
t.Errorf("got error writing temp file %v", err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
t.Errorf("got error closing temp file %v", err)
|
||||
}
|
||||
|
||||
p := tmpfile.Name()
|
||||
fn := func() string {
|
||||
r := NewCompositeCache()
|
||||
if err := r.AddPath(p); err != nil {
|
||||
t.Errorf("expected error to be nil but was %v", err)
|
||||
}
|
||||
|
||||
if len(r.keys) != 1 {
|
||||
t.Errorf("expected len of keys to be 1 but was %v", len(r.keys))
|
||||
}
|
||||
hash, err := r.Hash()
|
||||
if err != nil {
|
||||
t.Errorf("couldnt generate hash from test cache")
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
hash1 := fn()
|
||||
hash2 := fn()
|
||||
if hash1 != hash2 {
|
||||
t.Errorf("expected hash %v to equal hash %v", hash1, hash2)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
Copyright 2018 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// for use in tests
|
||||
package executor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/commands"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
)
|
||||
|
||||
type fakeSnapShotter struct {
|
||||
file string
|
||||
tarPath string
|
||||
}
|
||||
|
||||
func (f fakeSnapShotter) Init() error { return nil }
|
||||
func (f fakeSnapShotter) TakeSnapshotFS() (string, error) {
|
||||
return f.tarPath, nil
|
||||
}
|
||||
func (f fakeSnapShotter) TakeSnapshot(_ []string) (string, error) {
|
||||
return f.tarPath, nil
|
||||
}
|
||||
|
||||
type MockDockerCommand struct {
|
||||
contextFiles []string
|
||||
cacheCommand commands.DockerCommand
|
||||
}
|
||||
|
||||
func (m MockDockerCommand) ExecuteCommand(c *v1.Config, args *dockerfile.BuildArgs) error { return nil }
|
||||
func (m MockDockerCommand) String() string {
|
||||
return "meow"
|
||||
}
|
||||
func (m MockDockerCommand) FilesToSnapshot() []string {
|
||||
return []string{"meow-snapshot-no-cache"}
|
||||
}
|
||||
func (m MockDockerCommand) CacheCommand(image v1.Image) commands.DockerCommand {
|
||||
return m.cacheCommand
|
||||
}
|
||||
func (m MockDockerCommand) FilesUsedFromContext(c *v1.Config, args *dockerfile.BuildArgs) ([]string, error) {
|
||||
return m.contextFiles, nil
|
||||
}
|
||||
func (m MockDockerCommand) MetadataOnly() bool {
|
||||
return false
|
||||
}
|
||||
func (m MockDockerCommand) RequiresUnpackedFS() bool {
|
||||
return false
|
||||
}
|
||||
func (m MockDockerCommand) ShouldCacheOutput() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type MockCachedDockerCommand struct {
|
||||
contextFiles []string
|
||||
}
|
||||
|
||||
func (m MockCachedDockerCommand) ExecuteCommand(c *v1.Config, args *dockerfile.BuildArgs) error {
|
||||
return nil
|
||||
}
|
||||
func (m MockCachedDockerCommand) String() string {
|
||||
return "meow"
|
||||
}
|
||||
func (m MockCachedDockerCommand) FilesToSnapshot() []string {
|
||||
return []string{"meow-snapshot"}
|
||||
}
|
||||
func (m MockCachedDockerCommand) CacheCommand(image v1.Image) commands.DockerCommand {
|
||||
return nil
|
||||
}
|
||||
func (m MockCachedDockerCommand) FilesUsedFromContext(c *v1.Config, args *dockerfile.BuildArgs) ([]string, error) {
|
||||
return m.contextFiles, nil
|
||||
}
|
||||
func (m MockCachedDockerCommand) MetadataOnly() bool {
|
||||
return false
|
||||
}
|
||||
func (m MockCachedDockerCommand) RequiresUnpackedFS() bool {
|
||||
return false
|
||||
}
|
||||
func (m MockCachedDockerCommand) ShouldCacheOutput() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type fakeLayerCache struct {
|
||||
retrieve bool
|
||||
receivedKeys []string
|
||||
img v1.Image
|
||||
keySequence []string
|
||||
}
|
||||
|
||||
func (f *fakeLayerCache) RetrieveLayer(key string) (v1.Image, error) {
|
||||
f.receivedKeys = append(f.receivedKeys, key)
|
||||
if len(f.keySequence) > 0 {
|
||||
if f.keySequence[0] == key {
|
||||
f.keySequence = f.keySequence[1:]
|
||||
return f.img, nil
|
||||
}
|
||||
return f.img, errors.New("could not find layer")
|
||||
}
|
||||
|
||||
if !f.retrieve {
|
||||
return nil, errors.New("could not find layer")
|
||||
}
|
||||
return f.img, nil
|
||||
}
|
||||
|
||||
type fakeLayer struct {
|
||||
TarContent []byte
|
||||
}
|
||||
|
||||
func (f fakeLayer) Digest() (v1.Hash, error) {
|
||||
return v1.Hash{}, nil
|
||||
}
|
||||
func (f fakeLayer) DiffID() (v1.Hash, error) {
|
||||
return v1.Hash{}, nil
|
||||
}
|
||||
func (f fakeLayer) Compressed() (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f fakeLayer) Uncompressed() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(f.TarContent)), nil
|
||||
}
|
||||
func (f fakeLayer) Size() (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (f fakeLayer) MediaType() (types.MediaType, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
type fakeImage struct {
|
||||
ImageLayers []v1.Layer
|
||||
}
|
||||
|
||||
func (f fakeImage) Layers() ([]v1.Layer, error) {
|
||||
return f.ImageLayers, nil
|
||||
}
|
||||
func (f fakeImage) MediaType() (types.MediaType, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (f fakeImage) Size() (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (f fakeImage) ConfigName() (v1.Hash, error) {
|
||||
return v1.Hash{}, nil
|
||||
}
|
||||
func (f fakeImage) ConfigFile() (*v1.ConfigFile, error) {
|
||||
return &v1.ConfigFile{}, nil
|
||||
}
|
||||
func (f fakeImage) RawConfigFile() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
func (f fakeImage) Digest() (v1.Hash, error) {
|
||||
return v1.Hash{}, nil
|
||||
}
|
||||
func (f fakeImage) Manifest() (*v1.Manifest, error) {
|
||||
return &v1.Manifest{}, nil
|
||||
}
|
||||
func (f fakeImage) RawManifest() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
func (f fakeImage) LayerByDigest(v1.Hash) (v1.Layer, error) {
|
||||
return fakeLayer{}, nil
|
||||
}
|
||||
func (f fakeImage) LayerByDiffID(v1.Hash) (v1.Layer, error) {
|
||||
return fakeLayer{}, nil
|
||||
}
|
||||
|
|
@ -151,7 +151,7 @@ func DoPush(image v1.Image, opts *config.KanikoOptions) error {
|
|||
if opts.ImageNameDigestFile != "" {
|
||||
err := ioutil.WriteFile(opts.ImageNameDigestFile, []byte(builder.String()), 0644)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing digest to file failed")
|
||||
return errors.Wrap(err, "writing image name with digest to file failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ func (s *Snapshotter) scanFullFilesystem() ([]string, []string, error) {
|
|||
// Only add changed files.
|
||||
fileChanged, err := s.l.CheckFileChange(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, fmt.Errorf("could not check if file has changed %s %s", path, err)
|
||||
}
|
||||
if fileChanged {
|
||||
logrus.Debugf("Adding %s to layer, because it was changed.", path)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
|
@ -25,7 +26,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/shell"
|
||||
|
|
@ -77,13 +78,16 @@ func ResolveEnvAndWildcards(sd instructions.SourcesAndDest, buildcontext string,
|
|||
// First, resolve any environment replacement
|
||||
resolvedEnvs, err := ResolveEnvironmentReplacementList(sd, envs, true)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, "", errors.Wrap(err, "failed to resolve environment")
|
||||
}
|
||||
if len(resolvedEnvs) == 0 {
|
||||
return nil, "", errors.New("resolved envs is empty")
|
||||
}
|
||||
dest := resolvedEnvs[len(resolvedEnvs)-1]
|
||||
// Resolve wildcards and get a list of resolved sources
|
||||
srcs, err := ResolveSources(resolvedEnvs[0:len(resolvedEnvs)-1], buildcontext)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, "", errors.Wrap(err, "failed to resolve sources")
|
||||
}
|
||||
err = IsSrcsValid(sd, srcs, buildcontext)
|
||||
return srcs, dest, err
|
||||
|
|
@ -219,9 +223,10 @@ func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, resolvedSources []stri
|
|||
if IsSrcRemoteFileURL(resolvedSources[0]) {
|
||||
return nil
|
||||
}
|
||||
fi, err := os.Lstat(filepath.Join(root, resolvedSources[0]))
|
||||
path := filepath.Join(root, resolvedSources[0])
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to get fileinfo for %v", path))
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
|
|
@ -237,7 +242,7 @@ func IsSrcsValid(srcsAndDest instructions.SourcesAndDest, resolvedSources []stri
|
|||
src = filepath.Clean(src)
|
||||
files, err := RelativeFiles(src, root)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to get relative files")
|
||||
}
|
||||
for _, file := range files {
|
||||
if excludeFile(file, root) {
|
||||
|
|
|
|||
|
|
@ -551,6 +551,12 @@ func CopyFile(src, dest, buildcontext string) (bool, error) {
|
|||
logrus.Debugf("%s found in .dockerignore, ignoring", src)
|
||||
return true, nil
|
||||
}
|
||||
if src == dest {
|
||||
// This is a no-op. Move on, but don't list it as ignored.
|
||||
// We have to make sure we do this so we don't overwrite our own file.
|
||||
// See iusse #904 for an example.
|
||||
return false, nil
|
||||
}
|
||||
fi, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
|
|
|||
|
|
@ -825,3 +825,41 @@ func Test_correctDockerignoreFileIsUsed(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CopyFile_skips_self(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir, err := ioutil.TempDir("", "kaniko_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tempFile := filepath.Join(tempDir, "foo")
|
||||
expected := "bar"
|
||||
|
||||
if err := ioutil.WriteFile(
|
||||
tempFile,
|
||||
[]byte(expected),
|
||||
0755,
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ignored, err := CopyFile(tempFile, tempFile, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ignored {
|
||||
t.Fatal("expected file to NOT be ignored")
|
||||
}
|
||||
|
||||
// Ensure file has expected contents
|
||||
actualData, err := ioutil.ReadFile(tempFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if actual := string(actualData); actual != expected {
|
||||
t.Fatalf("expected file contents to be %q, but got %q", expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue