Merge branch 'master' into experiment
This commit is contained in:
commit
d37896b94f
51
README.md
51
README.md
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
kaniko is a tool to build container images from a Dockerfile, inside a container or Kubernetes cluster.
|
kaniko is a tool to build container images from a Dockerfile, inside a container or Kubernetes cluster.
|
||||||
|
|
||||||
kaniko doesn't depend on a Docker daemon and executes each command within a Dockerfile completely in userspace.
|
kaniko doesn't depend on a Docker daemon and executes each command within a Dockerfile completely in userspace.
|
||||||
This enables building container images in environments that can't easily or securely run a Docker daemon, such as a standard Kubernetes cluster.
|
This enables building container images in environments that can't easily or securely run a Docker daemon, such as a standard Kubernetes cluster.
|
||||||
|
|
@ -15,7 +15,7 @@ We'd love to hear from you! Join us on [#kaniko Kubernetes Slack](https://kuber
|
||||||
|
|
||||||
:mega: **Please fill out our [quick 5-question survey](https://forms.gle/HhZGEM33x4FUz9Qa6)** so that we can learn how satisfied you are with Kaniko, and what improvements we should make. Thank you! :dancers:
|
:mega: **Please fill out our [quick 5-question survey](https://forms.gle/HhZGEM33x4FUz9Qa6)** so that we can learn how satisfied you are with Kaniko, and what improvements we should make. Thank you! :dancers:
|
||||||
|
|
||||||
Kaniko is not an officially supported Google project.
|
Kaniko is not an officially supported Google project.
|
||||||
|
|
||||||
_If you are interested in contributing to kaniko, see [DEVELOPMENT.md](DEVELOPMENT.md) and [CONTRIBUTING.md](CONTRIBUTING.md)._
|
_If you are interested in contributing to kaniko, see [DEVELOPMENT.md](DEVELOPMENT.md) and [CONTRIBUTING.md](CONTRIBUTING.md)._
|
||||||
|
|
||||||
|
|
@ -50,6 +50,7 @@ _If you are interested in contributing to kaniko, see [DEVELOPMENT.md](DEVELOPME
|
||||||
- [--cache](#--cache)
|
- [--cache](#--cache)
|
||||||
- [--cache-dir](#--cache-dir)
|
- [--cache-dir](#--cache-dir)
|
||||||
- [--cache-repo](#--cache-repo)
|
- [--cache-repo](#--cache-repo)
|
||||||
|
- [--context-sub-path](#context-sub-path)
|
||||||
- [--digest-file](#--digest-file)
|
- [--digest-file](#--digest-file)
|
||||||
- [--oci-layout-path](#--oci-layout-path)
|
- [--oci-layout-path](#--oci-layout-path)
|
||||||
- [--insecure-registry](#--insecure-registry)
|
- [--insecure-registry](#--insecure-registry)
|
||||||
|
|
@ -69,6 +70,7 @@ _If you are interested in contributing to kaniko, see [DEVELOPMENT.md](DEVELOPME
|
||||||
- [--verbosity](#--verbosity)
|
- [--verbosity](#--verbosity)
|
||||||
- [--whitelist-var-run](#--whitelist-var-run)
|
- [--whitelist-var-run](#--whitelist-var-run)
|
||||||
- [--label](#--label)
|
- [--label](#--label)
|
||||||
|
- [--skip-unused-stages](#skip-unused-stages)
|
||||||
- [Debug Image](#debug-image)
|
- [Debug Image](#debug-image)
|
||||||
- [Security](#security)
|
- [Security](#security)
|
||||||
- [Comparison with Other Tools](#comparison-with-other-tools)
|
- [Comparison with Other Tools](#comparison-with-other-tools)
|
||||||
|
|
@ -121,11 +123,18 @@ Right now, kaniko supports these storage solutions:
|
||||||
- S3 Bucket
|
- S3 Bucket
|
||||||
- Azure Blob Storage
|
- Azure Blob Storage
|
||||||
- Local Directory
|
- Local Directory
|
||||||
|
- Local Tar
|
||||||
|
- Standard Input
|
||||||
- Git Repository
|
- Git Repository
|
||||||
|
|
||||||
_Note: the local directory option refers to a directory within the kaniko container.
|
_Note about Local Directory: this option refers to a directory within the kaniko container.
|
||||||
If you wish to use this option, you will need to mount in your build context into the container as a directory._
|
If you wish to use this option, you will need to mount in your build context into the container as a directory._
|
||||||
|
|
||||||
|
_Note about Local Tar: this option refers to a tar gz file within the kaniko container.
|
||||||
|
If you wish to use this option, you will need to mount in your build context into the container as a file._
|
||||||
|
|
||||||
|
_Note about Standard Input: the only Standard Input allowed by kaniko is in `.tar.gz` format._
|
||||||
|
|
||||||
If using a GCS or S3 bucket, you will first need to create a compressed tar of your build context and upload it to your bucket.
|
If using a GCS or S3 bucket, you will first need to create a compressed tar of your build context and upload it to your bucket.
|
||||||
Once running, kaniko will then download and unpack the compressed tar of the build context before starting the image build.
|
Once running, kaniko will then download and unpack the compressed tar of the build context before starting the image build.
|
||||||
|
|
||||||
|
|
@ -147,6 +156,7 @@ When running kaniko, use the `--context` flag with the appropriate prefix to spe
|
||||||
|---------|---------|---------|
|
|---------|---------|---------|
|
||||||
| Local Directory | dir://[path to a directory in the kaniko container] | `dir:///workspace` |
|
| Local Directory | dir://[path to a directory in the kaniko container] | `dir:///workspace` |
|
||||||
| Local Tar Gz | tar://[path to a .tar.gz in the kaniko container] | `tar://path/to/context.tar.gz` |
|
| Local Tar Gz | tar://[path to a .tar.gz in the kaniko container] | `tar://path/to/context.tar.gz` |
|
||||||
|
| Standard Input | tar://[stdin] | `tar://stdin` |
|
||||||
| GCS Bucket | gs://[bucket name]/[path to .tar.gz] | `gs://kaniko-bucket/path/to/context.tar.gz` |
|
| GCS Bucket | gs://[bucket name]/[path to .tar.gz] | `gs://kaniko-bucket/path/to/context.tar.gz` |
|
||||||
| S3 Bucket | s3://[bucket name]/[path to .tar.gz] | `s3://kaniko-bucket/path/to/context.tar.gz` |
|
| S3 Bucket | s3://[bucket name]/[path to .tar.gz] | `s3://kaniko-bucket/path/to/context.tar.gz` |
|
||||||
| Azure Blob Storage| https://[account].[azureblobhostsuffix]/[container]/[path to .tar.gz] | `https://myaccount.blob.core.windows.net/container/path/to/context.tar.gz` |
|
| Azure Blob Storage| https://[account].[azureblobhostsuffix]/[container]/[path to .tar.gz] | `https://myaccount.blob.core.windows.net/container/path/to/context.tar.gz` |
|
||||||
|
|
@ -161,6 +171,20 @@ If you are using Azure Blob Storage for context file, you will need to pass [Azu
|
||||||
### Using Private Git Repository
|
### Using Private Git Repository
|
||||||
You can use `Personal Access Tokens` for Build Contexts from Private Repositories from [GitHub](https://blog.github.com/2012-09-21-easier-builds-and-deployments-using-git-over-https-and-oauth/).
|
You can use `Personal Access Tokens` for Build Contexts from Private Repositories from [GitHub](https://blog.github.com/2012-09-21-easier-builds-and-deployments-using-git-over-https-and-oauth/).
|
||||||
|
|
||||||
|
### Using Standard Input
|
||||||
|
If running kaniko and using Standard Input build context, you will need to add the docker or kubernetes `-i, --interactive` flag.
|
||||||
|
Once running, kaniko will then get the data from `STDIN` and create the build context as a compressed tar.
|
||||||
|
It will then unpack the compressed tar of the build context before starting the image build.
|
||||||
|
If no data is piped during the interactive run, you will need to send the EOF signal by yourself by pressing `Ctrl+D`.
|
||||||
|
|
||||||
|
Complete example of how to interactively run kaniko with `.tar.gz` Standard Input data, using docker:
|
||||||
|
```shell
|
||||||
|
echo -e 'FROM alpine \nRUN echo "created from standard input"' > Dockerfile | tar -cf - Dockerfile | gzip -9 | docker run \
|
||||||
|
--interactive -v $(pwd):/workspace gcr.io/kaniko-project/executor:latest \
|
||||||
|
--context tar://stdin \
|
||||||
|
--destination=<gcr.io/$project/$image:$tag>
|
||||||
|
```
|
||||||
|
|
||||||
### Running kaniko
|
### Running kaniko
|
||||||
|
|
||||||
There are several different ways to deploy and run kaniko:
|
There are several different ways to deploy and run kaniko:
|
||||||
|
|
@ -270,9 +294,9 @@ docker run \
|
||||||
-v "$HOME"/.config/gcloud:/root/.config/gcloud \
|
-v "$HOME"/.config/gcloud:/root/.config/gcloud \
|
||||||
-v /path/to/context:/workspace \
|
-v /path/to/context:/workspace \
|
||||||
gcr.io/kaniko-project/executor:latest \
|
gcr.io/kaniko-project/executor:latest \
|
||||||
--dockerfile /workspace/Dockerfile
|
--dockerfile /workspace/Dockerfile \
|
||||||
--destination "gcr.io/$PROJECT_ID/$IMAGE_NAME:$TAG"
|
--destination "gcr.io/$PROJECT_ID/$IMAGE_NAME:$TAG" \
|
||||||
--context dir:///workspace/"
|
--context dir:///workspace/
|
||||||
```
|
```
|
||||||
|
|
||||||
There is also a utility script [`run_in_docker.sh`](./run_in_docker.sh) that can be used as follows:
|
There is also a utility script [`run_in_docker.sh`](./run_in_docker.sh) that can be used as follows:
|
||||||
|
|
@ -280,7 +304,7 @@ There is also a utility script [`run_in_docker.sh`](./run_in_docker.sh) that can
|
||||||
./run_in_docker.sh <path to Dockerfile> <path to build context> <destination of final image>
|
./run_in_docker.sh <path to Dockerfile> <path to build context> <destination of final image>
|
||||||
```
|
```
|
||||||
|
|
||||||
_NOTE: `run_in_docker.sh` expects a path to a
|
_NOTE: `run_in_docker.sh` expects a path to a
|
||||||
Dockerfile relative to the absolute path of the build context._
|
Dockerfile relative to the absolute path of the build context._
|
||||||
|
|
||||||
An example run, specifying the Dockerfile in the container directory `/workspace`, the build
|
An example run, specifying the Dockerfile in the container directory `/workspace`, the build
|
||||||
|
|
@ -336,7 +360,7 @@ Create a `config.json` file with your Docker registry url and the previous gener
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"auths": {
|
"auths": {
|
||||||
"https://index.docker.io/v1/": {
|
"https://index.docker.io/v2/": {
|
||||||
"auth": "xxxxxxxxxxxxxxx"
|
"auth": "xxxxxxxxxxxxxxx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -526,7 +550,11 @@ You need to set `--destination` as well (for example `--destination=image`).
|
||||||
|
|
||||||
#### --verbosity
|
#### --verbosity
|
||||||
|
|
||||||
Set this flag as `--verbosity=<panic|fatal|error|warn|info|debug>` to set the logging level. Defaults to `info`.
|
Set this flag as `--verbosity=<panic|fatal|error|warn|info|debug|trace>` to set the logging level. Defaults to `info`.
|
||||||
|
|
||||||
|
#### --log-format
|
||||||
|
|
||||||
|
Set this flag as `--log-format=<text|color|json>` to set the log format. Defaults to `color`.
|
||||||
|
|
||||||
#### --whitelist-var-run
|
#### --whitelist-var-run
|
||||||
|
|
||||||
|
|
@ -536,6 +564,11 @@ Ignore /var/run when taking image snapshot. Set it to false to preserve /var/run
|
||||||
|
|
||||||
Set this flag as `--label key=value` to set some metadata to the final image. This is equivalent as using the `LABEL` within the Dockerfile.
|
Set this flag as `--label key=value` to set some metadata to the final image. This is equivalent as using the `LABEL` within the Dockerfile.
|
||||||
|
|
||||||
|
#### --skip-unused-stages
|
||||||
|
|
||||||
|
This flag builds only used stages if defined to `true`.
|
||||||
|
Otherwise it builds by default all stages, even the unnecessaries ones until it reaches the target stage / end of Dockerfile
|
||||||
|
|
||||||
### Debug Image
|
### Debug Image
|
||||||
|
|
||||||
The kaniko executor image is based on scratch and doesn't contain a shell.
|
The kaniko executor image is based on scratch and doesn't contain a shell.
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.PersistentFlags().StringVarP(&logLevel, "verbosity", "v", logging.DefaultLevel, "Log level (debug, info, warn, error, fatal, panic")
|
RootCmd.PersistentFlags().StringVarP(&logLevel, "verbosity", "v", logging.DefaultLevel, "Log level (trace, debug, info, warn, error, fatal, panic)")
|
||||||
RootCmd.PersistentFlags().StringVar(&logFormat, "log-format", logging.FormatColor, "Log format (text, color, json)")
|
RootCmd.PersistentFlags().StringVar(&logFormat, "log-format", logging.FormatColor, "Log format (text, color, json)")
|
||||||
|
|
||||||
RootCmd.PersistentFlags().BoolVarP(&force, "force", "", false, "Force building outside of a container")
|
RootCmd.PersistentFlags().BoolVarP(&force, "force", "", false, "Force building outside of a container")
|
||||||
|
|
||||||
addKanikoOptionsFlags()
|
addKanikoOptionsFlags()
|
||||||
|
|
@ -161,6 +160,7 @@ func addKanikoOptionsFlags() {
|
||||||
RootCmd.PersistentFlags().StringVarP(&opts.RegistryMirror, "registry-mirror", "", "", "Registry mirror to use has pull-through cache instead of docker.io.")
|
RootCmd.PersistentFlags().StringVarP(&opts.RegistryMirror, "registry-mirror", "", "", "Registry mirror to use has pull-through cache instead of docker.io.")
|
||||||
RootCmd.PersistentFlags().BoolVarP(&opts.WhitelistVarRun, "whitelist-var-run", "", true, "Ignore /var/run directory when taking image snapshot. Set it to false to preserve /var/run/ in destination image. (Default true).")
|
RootCmd.PersistentFlags().BoolVarP(&opts.WhitelistVarRun, "whitelist-var-run", "", true, "Ignore /var/run directory when taking image snapshot. Set it to false to preserve /var/run/ in destination image. (Default true).")
|
||||||
RootCmd.PersistentFlags().VarP(&opts.Labels, "label", "", "Set metadata for an image. Set it repeatedly for multiple labels.")
|
RootCmd.PersistentFlags().VarP(&opts.Labels, "label", "", "Set metadata for an image. Set it repeatedly for multiple labels.")
|
||||||
|
RootCmd.PersistentFlags().BoolVarP(&opts.SkipUnusedStages, "skip-unused-stages", "", false, "Build only used stages if defined to true. Otherwise it builds by default all stages, even the unnecessaries ones until it reaches the target stage / end of Dockerfile")
|
||||||
}
|
}
|
||||||
|
|
||||||
// addHiddenFlags marks certain flags as hidden from the executor help text
|
// addHiddenFlags marks certain flags as hidden from the executor help text
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.PersistentFlags().StringVarP(&logLevel, "verbosity", "v", logging.DefaultLevel, "Log level (debug, info, warn, error, fatal, panic")
|
RootCmd.PersistentFlags().StringVarP(&logLevel, "verbosity", "v", logging.DefaultLevel, "Log level (trace, debug, info, warn, error, fatal, panic)")
|
||||||
RootCmd.PersistentFlags().StringVar(&logFormat, "log-format", logging.FormatColor, "Log format (text, color, json)")
|
RootCmd.PersistentFlags().StringVar(&logFormat, "log-format", logging.FormatColor, "Log format (text, color, json)")
|
||||||
|
|
||||||
addKanikoOptionsFlags()
|
addKanikoOptionsFlags()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
FROM docker.io/library/busybox:latest@sha256:afe605d272837ce1732f390966166c2afff5391208ddd57de10942748694049d
|
||||||
|
RUN echo ${s%s}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
FROM busybox:latest@sha256:b26cd013274a657b86e706210ddd5cc1f82f50155791199d29b9e86e935ce135
|
||||||
|
RUN ["/bin/ln", "-s", "nowhere", "/link"]
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
FROM scratch
|
||||||
|
MAINTAINER nobody@domain.test
|
||||||
|
# Add a file to the image to work around https://github.com/moby/moby/issues/38039
|
||||||
|
COPY context/foo /foo
|
||||||
|
|
@ -73,9 +73,10 @@ var additionalDockerFlagsMap = map[string][]string{
|
||||||
|
|
||||||
// Arguments to build Dockerfiles with when building with kaniko
|
// Arguments to build Dockerfiles with when building with kaniko
|
||||||
var additionalKanikoFlagsMap = map[string][]string{
|
var additionalKanikoFlagsMap = map[string][]string{
|
||||||
"Dockerfile_test_add": {"--single-snapshot"},
|
"Dockerfile_test_add": {"--single-snapshot"},
|
||||||
"Dockerfile_test_scratch": {"--single-snapshot"},
|
"Dockerfile_test_scratch": {"--single-snapshot"},
|
||||||
"Dockerfile_test_target": {"--target=second"},
|
"Dockerfile_test_maintainer": {"--single-snapshot"},
|
||||||
|
"Dockerfile_test_target": {"--target=second"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// output check to do when building with kaniko
|
// output check to do when building with kaniko
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,11 @@ import (
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var config *integrationTestConfig
|
var config *integrationTestConfig
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
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 integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||||
|
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildWithStdin(t *testing.T) {
|
||||||
|
_, ex, _, _ := runtime.Caller(0)
|
||||||
|
cwd := filepath.Dir(ex)
|
||||||
|
|
||||||
|
testDir := "test_dir"
|
||||||
|
testDirLongPath := filepath.Join(cwd, testDir)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(testDirLongPath, 0750); err != nil {
|
||||||
|
t.Errorf("Failed to create dir_where_to_extract: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerfile := "Dockerfile_test_stdin"
|
||||||
|
|
||||||
|
files := map[string]string{
|
||||||
|
dockerfile: "FROM debian:9.11\nRUN echo \"hey\"",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testutil.SetupFiles(testDir, files); err != nil {
|
||||||
|
t.Errorf("Failed to setup files %v on %s: %v", files, testDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chdir(testDir); err != nil {
|
||||||
|
t.Fatalf("Failed to Chdir on %s: %v", testDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tarPath := fmt.Sprintf("%s.tar.gz", dockerfile)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
// Create Tar Gz File with dockerfile inside
|
||||||
|
go func(wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
tarFile, err := os.Create(tarPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to create %s: %v", tarPath, err)
|
||||||
|
}
|
||||||
|
defer tarFile.Close()
|
||||||
|
|
||||||
|
gw := gzip.NewWriter(tarFile)
|
||||||
|
defer gw.Close()
|
||||||
|
|
||||||
|
tw := util.NewTar(gw)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
if err := tw.AddFileToTar(dockerfile); err != nil {
|
||||||
|
t.Errorf("Failed to add %s to %s: %v", dockerfile, tarPath, err)
|
||||||
|
}
|
||||||
|
}(&wg)
|
||||||
|
|
||||||
|
// Waiting for the Tar Gz file creation to be done before moving on
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Build with docker
|
||||||
|
|
||||||
|
dockerImage := GetDockerImage(config.imageRepo, dockerfile)
|
||||||
|
dockerCmd := exec.Command("docker",
|
||||||
|
append([]string{"build",
|
||||||
|
"-t", dockerImage,
|
||||||
|
"-f", dockerfile,
|
||||||
|
"."})...)
|
||||||
|
|
||||||
|
_, err := RunCommandWithoutTest(dockerCmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't run %s: %v", dockerCmd.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build with kaniko using Stdin
|
||||||
|
kanikoImageStdin := GetKanikoImage(config.imageRepo, dockerfile)
|
||||||
|
tarCmd := exec.Command("tar", "-cf", "-", dockerfile)
|
||||||
|
gzCmd := exec.Command("gzip", "-9")
|
||||||
|
|
||||||
|
dockerRunFlags := []string{"run", "--interactive", "--net=host", "-v", cwd + ":/workspace"}
|
||||||
|
dockerRunFlags = addServiceAccountFlags(dockerRunFlags, config.serviceAccount)
|
||||||
|
dockerRunFlags = append(dockerRunFlags,
|
||||||
|
ExecutorImage,
|
||||||
|
"-f", dockerfile,
|
||||||
|
"-c", "tar://stdin",
|
||||||
|
"-d", kanikoImageStdin)
|
||||||
|
|
||||||
|
kanikoCmdStdin := exec.Command("docker", dockerRunFlags...)
|
||||||
|
|
||||||
|
gzCmd.Stdin, err = tarCmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't set gzCmd stdin: %v", err)
|
||||||
|
}
|
||||||
|
kanikoCmdStdin.Stdin, err = gzCmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't set kanikoCmd stdin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kanikoCmdStdin.Start(); err != nil {
|
||||||
|
t.Fatalf("can't start %s: %v", kanikoCmdStdin.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gzCmd.Start(); err != nil {
|
||||||
|
t.Fatalf("can't start %s: %v", gzCmd.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tarCmd.Run(); err != nil {
|
||||||
|
t.Fatalf("can't start %s: %v", tarCmd.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gzCmd.Wait(); err != nil {
|
||||||
|
t.Fatalf("can't wait %s: %v", gzCmd.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kanikoCmdStdin.Wait(); err != nil {
|
||||||
|
t.Fatalf("can't wait %s: %v", kanikoCmdStdin.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := containerDiff(t, daemonPrefix+dockerImage, kanikoImageStdin, "--no-cache")
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(emptyContainerDiff, dockerImage, kanikoImageStdin, dockerImage, kanikoImageStdin)
|
||||||
|
checkContainerDiffOutput(t, diff, expected)
|
||||||
|
|
||||||
|
if err := os.RemoveAll(testDirLongPath); err != nil {
|
||||||
|
t.Errorf("Failed to remove %s: %v", testDirLongPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,25 +38,27 @@ type BuildContext interface {
|
||||||
// parser
|
// parser
|
||||||
func GetBuildContext(srcContext string) (BuildContext, error) {
|
func GetBuildContext(srcContext string) (BuildContext, error) {
|
||||||
split := strings.SplitAfter(srcContext, "://")
|
split := strings.SplitAfter(srcContext, "://")
|
||||||
prefix := split[0]
|
if len(split) > 1 {
|
||||||
context := split[1]
|
prefix := split[0]
|
||||||
|
context := split[1]
|
||||||
|
|
||||||
switch prefix {
|
switch prefix {
|
||||||
case constants.GCSBuildContextPrefix:
|
case constants.GCSBuildContextPrefix:
|
||||||
return &GCS{context: context}, nil
|
return &GCS{context: context}, nil
|
||||||
case constants.S3BuildContextPrefix:
|
case constants.S3BuildContextPrefix:
|
||||||
return &S3{context: context}, nil
|
return &S3{context: context}, nil
|
||||||
case constants.LocalDirBuildContextPrefix:
|
case constants.LocalDirBuildContextPrefix:
|
||||||
return &Dir{context: context}, nil
|
return &Dir{context: context}, nil
|
||||||
case constants.GitBuildContextPrefix:
|
case constants.GitBuildContextPrefix:
|
||||||
return &Git{context: context}, nil
|
return &Git{context: context}, nil
|
||||||
case constants.HTTPSBuildContextPrefix:
|
case constants.HTTPSBuildContextPrefix:
|
||||||
if util.ValidAzureBlobStorageHost(srcContext) {
|
if util.ValidAzureBlobStorageHost(srcContext) {
|
||||||
return &AzureBlob{context: srcContext}, nil
|
return &AzureBlob{context: srcContext}, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("url provided for https context is not in a supported format, please use the https url for Azure Blob Storage")
|
||||||
|
case TarBuildContextPrefix:
|
||||||
|
return &Tar{context: context}, nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("url provided for https context is not in a supported format, please use the https url for Azure Blob Storage")
|
|
||||||
case TarBuildContextPrefix:
|
|
||||||
return &Tar{context: context}, nil
|
|
||||||
}
|
}
|
||||||
return nil, errors.New("unknown build context prefix provided, please use one of the following: gs://, dir://, tar://, s3://, git://, https://")
|
return nil, errors.New("unknown build context prefix provided, please use one of the following: gs://, dir://, tar://, s3://, git://, https://")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,16 @@ import (
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
gitPullMethodEnvKey = "GIT_PULL_METHOD"
|
||||||
|
gitPullMethodHTTPS = "https"
|
||||||
|
gitPullMethodHTTP = "http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
supportedGitPullMethods = map[string]bool{gitPullMethodHTTPS: true, gitPullMethodHTTP: true}
|
||||||
|
)
|
||||||
|
|
||||||
// Git unifies calls to download and unpack the build context.
|
// Git unifies calls to download and unpack the build context.
|
||||||
type Git struct {
|
type Git struct {
|
||||||
context string
|
context string
|
||||||
|
|
@ -35,7 +45,7 @@ func (g *Git) UnpackTarFromBuildContext() (string, error) {
|
||||||
directory := constants.BuildContextDir
|
directory := constants.BuildContextDir
|
||||||
parts := strings.Split(g.context, "#")
|
parts := strings.Split(g.context, "#")
|
||||||
options := git.CloneOptions{
|
options := git.CloneOptions{
|
||||||
URL: "https://" + parts[0],
|
URL: getGitPullMethod() + "://" + parts[0],
|
||||||
Progress: os.Stdout,
|
Progress: os.Stdout,
|
||||||
}
|
}
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
|
|
@ -44,3 +54,11 @@ func (g *Git) UnpackTarFromBuildContext() (string, error) {
|
||||||
_, err := git.PlainClone(directory, false, &options)
|
_, err := git.PlainClone(directory, false, &options)
|
||||||
return directory, err
|
return directory, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getGitPullMethod() string {
|
||||||
|
gitPullMethod := os.Getenv(gitPullMethodEnvKey)
|
||||||
|
if ok := supportedGitPullMethods[gitPullMethod]; !ok {
|
||||||
|
gitPullMethod = gitPullMethodHTTPS
|
||||||
|
}
|
||||||
|
return gitPullMethod
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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 buildcontext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetGitPullMethod(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
testName string
|
||||||
|
setEnv func() (expectedValue string)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "noEnv",
|
||||||
|
setEnv: func() (expectedValue string) {
|
||||||
|
expectedValue = gitPullMethodHTTPS
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "emptyEnv",
|
||||||
|
setEnv: func() (expectedValue string) {
|
||||||
|
_ = os.Setenv(gitPullMethodEnvKey, "")
|
||||||
|
expectedValue = gitPullMethodHTTPS
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "httpEnv",
|
||||||
|
setEnv: func() (expectedValue string) {
|
||||||
|
err := os.Setenv(gitPullMethodEnvKey, gitPullMethodHTTP)
|
||||||
|
if nil != err {
|
||||||
|
expectedValue = gitPullMethodHTTPS
|
||||||
|
} else {
|
||||||
|
expectedValue = gitPullMethodHTTP
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "httpsEnv",
|
||||||
|
setEnv: func() (expectedValue string) {
|
||||||
|
_ = os.Setenv(gitPullMethodEnvKey, gitPullMethodHTTPS)
|
||||||
|
expectedValue = gitPullMethodHTTPS
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "unknownEnv",
|
||||||
|
setEnv: func() (expectedValue string) {
|
||||||
|
_ = os.Setenv(gitPullMethodEnvKey, "unknown")
|
||||||
|
expectedValue = gitPullMethodHTTPS
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.testName, func(t *testing.T) {
|
||||||
|
expectedValue := tt.setEnv()
|
||||||
|
testutil.CheckDeepEqual(t, expectedValue, getGitPullMethod())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,11 +17,15 @@ limitations under the License.
|
||||||
package buildcontext
|
package buildcontext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tar unifies calls to download and unpack the build context.
|
// Tar unifies calls to download and unpack the build context.
|
||||||
|
|
@ -35,6 +39,23 @@ func (t *Tar) UnpackTarFromBuildContext() (string, error) {
|
||||||
if err := os.MkdirAll(directory, 0750); err != nil {
|
if err := os.MkdirAll(directory, 0750); err != nil {
|
||||||
return "", errors.Wrap(err, "unpacking tar from build context")
|
return "", errors.Wrap(err, "unpacking tar from build context")
|
||||||
}
|
}
|
||||||
|
if t.context == "stdin" {
|
||||||
|
fi, _ := os.Stdin.Stat()
|
||||||
|
if (fi.Mode() & os.ModeCharDevice) != 0 {
|
||||||
|
return "", fmt.Errorf("no data found.. don't forget to add the '--interactive, -i' flag")
|
||||||
|
}
|
||||||
|
logrus.Infof("To simulate EOF and exit, press 'Ctrl+D'")
|
||||||
|
// if launched through docker in interactive mode and without piped data
|
||||||
|
// process will be stuck here until EOF is sent
|
||||||
|
data, err := util.GetInputFrom(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "fail to get standard input")
|
||||||
|
}
|
||||||
|
t.context = filepath.Join(directory, constants.ContextTar)
|
||||||
|
if err := ioutil.WriteFile(t.context, data, 0644); err != nil {
|
||||||
|
return "", errors.Wrap(err, "fail to redirect standard input into compressed tar file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return directory, util.UnpackCompressedTar(t.context, directory)
|
return directory, util.UnpackCompressedTar(t.context, directory)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,8 @@ func (cr *CachingCopyCommand) FilesUsedFromContext(config *v1.Config, buildArgs
|
||||||
|
|
||||||
func (cr *CachingCopyCommand) FilesToSnapshot() []string {
|
func (cr *CachingCopyCommand) FilesToSnapshot() []string {
|
||||||
f := cr.extractedFiles
|
f := cr.extractedFiles
|
||||||
logrus.Debugf("files extracted by caching copy command %s", f)
|
logrus.Debugf("%d files extracted by caching copy command", len(f))
|
||||||
|
logrus.Tracef("Extracted files: %s", f)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,8 @@ func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *docker
|
||||||
|
|
||||||
func (cr *CachingRunCommand) FilesToSnapshot() []string {
|
func (cr *CachingRunCommand) FilesToSnapshot() []string {
|
||||||
f := cr.extractedFiles
|
f := cr.extractedFiles
|
||||||
logrus.Debugf("files extracted from caching run command %s", f)
|
logrus.Debugf("%d files extracted by caching run command", len(f))
|
||||||
|
logrus.Tracef("Extracted files: %s", f)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ type KanikoOptions struct {
|
||||||
Cache bool
|
Cache bool
|
||||||
Cleanup bool
|
Cleanup bool
|
||||||
WhitelistVarRun bool
|
WhitelistVarRun bool
|
||||||
|
SkipUnusedStages bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarmerOptions are options that are set by command line arguments to the cache warmer.
|
// WarmerOptions are options that are set by command line arguments to the cache warmer.
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
|
@ -253,6 +254,9 @@ func MakeKanikoStages(opts *config.KanikoOptions, stages []instructions.Stage, m
|
||||||
if err := resolveStagesArgs(stages, args); err != nil {
|
if err := resolveStagesArgs(stages, args); err != nil {
|
||||||
return nil, errors.Wrap(err, "resolving args")
|
return nil, errors.Wrap(err, "resolving args")
|
||||||
}
|
}
|
||||||
|
if opts.SkipUnusedStages {
|
||||||
|
stages = skipUnusedStages(stages, &targetStage, opts.Target)
|
||||||
|
}
|
||||||
var kanikoStages []config.KanikoStage
|
var kanikoStages []config.KanikoStage
|
||||||
for index, stage := range stages {
|
for index, stage := range stages {
|
||||||
if len(stage.Name) > 0 {
|
if len(stage.Name) > 0 {
|
||||||
|
|
@ -312,3 +316,53 @@ func unifyArgs(metaArgs []instructions.ArgCommand, buildArgs []string) []string
|
||||||
}
|
}
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skipUnusedStages returns the list of used stages without the unnecessaries ones
|
||||||
|
func skipUnusedStages(stages []instructions.Stage, lastStageIndex *int, target string) []instructions.Stage {
|
||||||
|
stagesDependencies := make(map[string]bool)
|
||||||
|
var onlyUsedStages []instructions.Stage
|
||||||
|
idx := *lastStageIndex
|
||||||
|
|
||||||
|
lastStageBaseName := stages[idx].BaseName
|
||||||
|
|
||||||
|
for i := idx; i >= 0; i-- {
|
||||||
|
s := stages[i]
|
||||||
|
if (s.Name != "" && stagesDependencies[s.Name]) || s.Name == lastStageBaseName || i == idx {
|
||||||
|
for _, c := range s.Commands {
|
||||||
|
switch cmd := c.(type) {
|
||||||
|
case *instructions.CopyCommand:
|
||||||
|
stageName := cmd.From
|
||||||
|
if copyFromIndex, err := strconv.Atoi(stageName); err == nil {
|
||||||
|
stageName = stages[copyFromIndex].Name
|
||||||
|
}
|
||||||
|
if !stagesDependencies[stageName] {
|
||||||
|
stagesDependencies[stageName] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i != idx {
|
||||||
|
stagesDependencies[s.BaseName] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependenciesLen := len(stagesDependencies)
|
||||||
|
if target == "" && dependenciesLen == 0 {
|
||||||
|
return stages
|
||||||
|
} else if dependenciesLen > 0 {
|
||||||
|
for i := 0; i < idx; i++ {
|
||||||
|
if stages[i].Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := stages[i]
|
||||||
|
if stagesDependencies[s.Name] || s.Name == lastStageBaseName {
|
||||||
|
onlyUsedStages = append(onlyUsedStages, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onlyUsedStages = append(onlyUsedStages, stages[idx])
|
||||||
|
if idx > len(onlyUsedStages)-1 {
|
||||||
|
*lastStageIndex = len(onlyUsedStages) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return onlyUsedStages
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -456,3 +456,193 @@ func Test_ResolveStagesArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_SkipingUnusedStages(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
dockerfile string
|
||||||
|
targets []string
|
||||||
|
expectedSourceCodes map[string][]string
|
||||||
|
expectedTargetIndexBeforeSkip map[string]int
|
||||||
|
expectedTargetIndexAfterSkip map[string]int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "dockerfile_without_copyFrom",
|
||||||
|
dockerfile: `
|
||||||
|
FROM alpine:3.11 AS base-dev
|
||||||
|
RUN echo dev > /hi
|
||||||
|
FROM alpine:3.11 AS base-prod
|
||||||
|
RUN echo prod > /hi
|
||||||
|
FROM base-dev as final-stage
|
||||||
|
RUN cat /hi
|
||||||
|
`,
|
||||||
|
targets: []string{"base-dev", "base-prod", ""},
|
||||||
|
expectedSourceCodes: map[string][]string{
|
||||||
|
"base-dev": {"FROM alpine:3.11 AS base-dev"},
|
||||||
|
"base-prod": {"FROM alpine:3.11 AS base-prod"},
|
||||||
|
"": {"FROM alpine:3.11 AS base-dev", "FROM base-dev as final-stage"},
|
||||||
|
},
|
||||||
|
expectedTargetIndexBeforeSkip: map[string]int{
|
||||||
|
"base-dev": 0,
|
||||||
|
"base-prod": 1,
|
||||||
|
"": 2,
|
||||||
|
},
|
||||||
|
expectedTargetIndexAfterSkip: map[string]int{
|
||||||
|
"base-dev": 0,
|
||||||
|
"base-prod": 0,
|
||||||
|
"": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "dockerfile_with_copyFrom",
|
||||||
|
dockerfile: `
|
||||||
|
FROM alpine:3.11 AS base-dev
|
||||||
|
RUN echo dev > /hi
|
||||||
|
FROM alpine:3.11 AS base-prod
|
||||||
|
RUN echo prod > /hi
|
||||||
|
FROM alpine:3.11
|
||||||
|
COPY --from=base-prod /hi /finalhi
|
||||||
|
RUN cat /finalhi
|
||||||
|
`,
|
||||||
|
targets: []string{"base-dev", "base-prod", ""},
|
||||||
|
expectedSourceCodes: map[string][]string{
|
||||||
|
"base-dev": {"FROM alpine:3.11 AS base-dev"},
|
||||||
|
"base-prod": {"FROM alpine:3.11 AS base-prod"},
|
||||||
|
"": {"FROM alpine:3.11 AS base-prod", "FROM alpine:3.11"},
|
||||||
|
},
|
||||||
|
expectedTargetIndexBeforeSkip: map[string]int{
|
||||||
|
"base-dev": 0,
|
||||||
|
"base-prod": 1,
|
||||||
|
"": 2,
|
||||||
|
},
|
||||||
|
expectedTargetIndexAfterSkip: map[string]int{
|
||||||
|
"base-dev": 0,
|
||||||
|
"base-prod": 0,
|
||||||
|
"": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "dockerfile_with_two_copyFrom",
|
||||||
|
dockerfile: `
|
||||||
|
FROM alpine:3.11 AS base-dev
|
||||||
|
RUN echo dev > /hi
|
||||||
|
FROM alpine:3.11 AS base-prod
|
||||||
|
RUN echo prod > /hi
|
||||||
|
FROM alpine:3.11
|
||||||
|
COPY --from=base-dev /hi /finalhidev
|
||||||
|
COPY --from=base-prod /hi /finalhiprod
|
||||||
|
RUN cat /finalhidev
|
||||||
|
RUN cat /finalhiprod
|
||||||
|
`,
|
||||||
|
targets: []string{"base-dev", "base-prod", ""},
|
||||||
|
expectedSourceCodes: map[string][]string{
|
||||||
|
"base-dev": {"FROM alpine:3.11 AS base-dev"},
|
||||||
|
"base-prod": {"FROM alpine:3.11 AS base-prod"},
|
||||||
|
"": {"FROM alpine:3.11 AS base-dev", "FROM alpine:3.11 AS base-prod", "FROM alpine:3.11"},
|
||||||
|
},
|
||||||
|
expectedTargetIndexBeforeSkip: map[string]int{
|
||||||
|
"base-dev": 0,
|
||||||
|
"base-prod": 1,
|
||||||
|
"": 2,
|
||||||
|
},
|
||||||
|
expectedTargetIndexAfterSkip: map[string]int{
|
||||||
|
"base-dev": 0,
|
||||||
|
"base-prod": 0,
|
||||||
|
"": 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "dockerfile_with_two_copyFrom_and_arg",
|
||||||
|
dockerfile: `
|
||||||
|
FROM debian:9.11 as base
|
||||||
|
COPY . .
|
||||||
|
FROM scratch as second
|
||||||
|
ENV foopath context/foo
|
||||||
|
COPY --from=0 $foopath context/b* /foo/
|
||||||
|
FROM second as third
|
||||||
|
COPY --from=base /context/foo /new/foo
|
||||||
|
FROM base as fourth
|
||||||
|
# Make sure that we snapshot intermediate images correctly
|
||||||
|
RUN date > /date
|
||||||
|
ENV foo bar
|
||||||
|
# This base image contains symlinks with relative paths to whitelisted directories
|
||||||
|
# We need to test they're extracted correctly
|
||||||
|
FROM fedora@sha256:c4cc32b09c6ae3f1353e7e33a8dda93dc41676b923d6d89afa996b421cc5aa48
|
||||||
|
FROM fourth
|
||||||
|
ARG file=/foo2
|
||||||
|
COPY --from=second /foo ${file}
|
||||||
|
COPY --from=debian:9.11 /etc/os-release /new
|
||||||
|
`,
|
||||||
|
targets: []string{"base", ""},
|
||||||
|
expectedSourceCodes: map[string][]string{
|
||||||
|
"base": {"FROM debian:9.11 as base"},
|
||||||
|
"second": {"FROM debian:9.11 as base", "FROM scratch as second"},
|
||||||
|
"": {"FROM debian:9.11 as base", "FROM scratch as second", "FROM base as fourth", "FROM fourth"},
|
||||||
|
},
|
||||||
|
expectedTargetIndexBeforeSkip: map[string]int{
|
||||||
|
"base": 0,
|
||||||
|
"second": 1,
|
||||||
|
"": 5,
|
||||||
|
},
|
||||||
|
expectedTargetIndexAfterSkip: map[string]int{
|
||||||
|
"base": 0,
|
||||||
|
"second": 1,
|
||||||
|
"": 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "dockerfile_without_final_dependencies",
|
||||||
|
dockerfile: `
|
||||||
|
FROM alpine:3.11
|
||||||
|
FROM debian:9.11 as base
|
||||||
|
RUN echo foo > /foo
|
||||||
|
FROM debian:9.11 as fizz
|
||||||
|
RUN echo fizz >> /fizz
|
||||||
|
COPY --from=base /foo /fizz
|
||||||
|
FROM alpine:3.11 as buzz
|
||||||
|
RUN echo buzz > /buzz
|
||||||
|
FROM alpine:3.11 as final
|
||||||
|
RUN echo bar > /bar
|
||||||
|
`,
|
||||||
|
targets: []string{"final", "buzz", "fizz", ""},
|
||||||
|
expectedSourceCodes: map[string][]string{
|
||||||
|
"final": {"FROM alpine:3.11 as final"},
|
||||||
|
"buzz": {"FROM alpine:3.11 as buzz"},
|
||||||
|
"fizz": {"FROM debian:9.11 as base", "FROM debian:9.11 as fizz"},
|
||||||
|
"": {"FROM alpine:3.11", "FROM debian:9.11 as base", "FROM debian:9.11 as fizz", "FROM alpine:3.11 as buzz", "FROM alpine:3.11 as final"},
|
||||||
|
},
|
||||||
|
expectedTargetIndexBeforeSkip: map[string]int{
|
||||||
|
"final": 4,
|
||||||
|
"buzz": 3,
|
||||||
|
"fizz": 2,
|
||||||
|
"": 4,
|
||||||
|
},
|
||||||
|
expectedTargetIndexAfterSkip: map[string]int{
|
||||||
|
"final": 0,
|
||||||
|
"buzz": 0,
|
||||||
|
"fizz": 1,
|
||||||
|
"": 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
stages, _, err := Parse([]byte(test.dockerfile))
|
||||||
|
testutil.CheckError(t, false, err)
|
||||||
|
actualSourceCodes := make(map[string][]string)
|
||||||
|
for _, target := range test.targets {
|
||||||
|
targetIndex, err := targetStage(stages, target)
|
||||||
|
testutil.CheckError(t, false, err)
|
||||||
|
targetIndexBeforeSkip := targetIndex
|
||||||
|
onlyUsedStages := skipUnusedStages(stages, &targetIndex, target)
|
||||||
|
for _, s := range onlyUsedStages {
|
||||||
|
actualSourceCodes[target] = append(actualSourceCodes[target], s.SourceCode)
|
||||||
|
}
|
||||||
|
t.Run(test.description, func(t *testing.T) {
|
||||||
|
testutil.CheckDeepEqual(t, test.expectedSourceCodes[target], actualSourceCodes[target])
|
||||||
|
testutil.CheckDeepEqual(t, test.expectedTargetIndexBeforeSkip[target], targetIndexBeforeSkip)
|
||||||
|
testutil.CheckDeepEqual(t, test.expectedTargetIndexAfterSkip[target], targetIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -339,9 +339,11 @@ func (s *stageBuilder) build() error {
|
||||||
return errors.Wrap(err, "failed to get files used from context")
|
return errors.Wrap(err, "failed to get files used from context")
|
||||||
}
|
}
|
||||||
|
|
||||||
*compositeKey, err = s.populateCompositeKey(command, files, *compositeKey, s.args, s.cf.Config.Env)
|
if s.opts.Cache {
|
||||||
if err != nil {
|
*compositeKey, err = s.populateCompositeKey(command, files, *compositeKey, s.args, s.cf.Config.Env)
|
||||||
return err
|
if err != nil && s.opts.Cache {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Info(command.String())
|
logrus.Info(command.String())
|
||||||
|
|
@ -384,19 +386,21 @@ func (s *stageBuilder) build() error {
|
||||||
return errors.Wrap(err, "failed to take snapshot")
|
return errors.Wrap(err, "failed to take snapshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("build: composite key for command %v %v", command.String(), compositeKey)
|
if s.opts.Cache {
|
||||||
ck, err := compositeKey.Hash()
|
logrus.Debugf("build: composite key for command %v %v", command.String(), compositeKey)
|
||||||
if err != nil {
|
ck, err := compositeKey.Hash()
|
||||||
return errors.Wrap(err, "failed to hash composite key")
|
if err != nil {
|
||||||
}
|
return errors.Wrap(err, "failed to hash composite key")
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Debugf("build: cache key for command %v %v", command.String(), ck)
|
logrus.Debugf("build: cache key for command %v %v", command.String(), ck)
|
||||||
|
|
||||||
// Push layer to cache (in parallel) now along with new config file
|
// Push layer to cache (in parallel) now along with new config file
|
||||||
if s.opts.Cache && command.ShouldCacheOutput() {
|
if command.ShouldCacheOutput() {
|
||||||
cacheGroup.Go(func() error {
|
cacheGroup.Go(func() error {
|
||||||
return s.pushLayerToCache(s.opts, ck, tarPath, command.String())
|
return s.pushLayerToCache(s.opts, ck, tarPath, command.String())
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := s.saveSnapshotToImage(command.String(), tarPath); err != nil {
|
if err := s.saveSnapshotToImage(command.String(), tarPath); err != nil {
|
||||||
return errors.Wrap(err, "failed to save snapshot to image")
|
return errors.Wrap(err, "failed to save snapshot to image")
|
||||||
|
|
@ -428,7 +432,7 @@ func (s *stageBuilder) takeSnapshot(files []string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stageBuilder) shouldTakeSnapshot(index int, files []string, provideFiles bool) bool {
|
func (s *stageBuilder) shouldTakeSnapshot(index int, files []string, provideFiles bool) bool {
|
||||||
isLastCommand := index == len(s.stage.Commands)-1
|
isLastCommand := index == len(s.cmds)-1
|
||||||
|
|
||||||
// We only snapshot the very end with single snapshot mode on.
|
// We only snapshot the very end with single snapshot mode on.
|
||||||
if s.opts.SingleSnapshot {
|
if s.opts.SingleSnapshot {
|
||||||
|
|
@ -713,7 +717,7 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
|
||||||
t := timing.Start("Fetching Extra Stages")
|
t := timing.Start("Fetching Extra Stages")
|
||||||
defer timing.DefaultRun.Stop(t)
|
defer timing.DefaultRun.Stop(t)
|
||||||
|
|
||||||
var names = []string{}
|
var names []string
|
||||||
|
|
||||||
for stageIndex, s := range stages {
|
for stageIndex, s := range stages {
|
||||||
for _, cmd := range s.Commands {
|
for _, cmd := range s.Commands {
|
||||||
|
|
@ -730,11 +734,10 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Check if the name is the alias of a previous stage
|
// Check if the name is the alias of a previous stage
|
||||||
for _, name := range names {
|
if fromPreviousStage(c, names) {
|
||||||
if name == c.From {
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This must be an image name, fetch it.
|
// This must be an image name, fetch it.
|
||||||
logrus.Debugf("Found extra base image stage %s", c.From)
|
logrus.Debugf("Found extra base image stage %s", c.From)
|
||||||
sourceImage, err := util.RetrieveRemoteImage(c.From, opts)
|
sourceImage, err := util.RetrieveRemoteImage(c.From, opts)
|
||||||
|
|
@ -755,6 +758,16 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fromPreviousStage(copyCommand *instructions.CopyCommand, previousStageNames []string) bool {
|
||||||
|
for _, previousStageName := range previousStageNames {
|
||||||
|
if previousStageName == copyCommand.From {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func extractImageToDependencyDir(name string, image v1.Image) error {
|
func extractImageToDependencyDir(name string, image v1.Image) error {
|
||||||
t := timing.Start("Extracting Image to Dependency Dir")
|
t := timing.Start("Extracting Image to Dependency Dir")
|
||||||
defer timing.DefaultRun.Stop(t)
|
defer timing.DefaultRun.Stop(t)
|
||||||
|
|
|
||||||
|
|
@ -90,28 +90,17 @@ func stage(t *testing.T, d string) config.KanikoStage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockCommand struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockCommand) Name() string {
|
|
||||||
return m.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||||
commands := []instructions.Command{
|
cmds := []commands.DockerCommand{
|
||||||
&MockCommand{name: "command1"},
|
&MockDockerCommand{command: "command1"},
|
||||||
&MockCommand{name: "command2"},
|
&MockDockerCommand{command: "command2"},
|
||||||
&MockCommand{name: "command3"},
|
&MockDockerCommand{command: "command3"},
|
||||||
}
|
|
||||||
|
|
||||||
stage := instructions.Stage{
|
|
||||||
Commands: commands,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
stage config.KanikoStage
|
stage config.KanikoStage
|
||||||
opts *config.KanikoOptions
|
opts *config.KanikoOptions
|
||||||
|
cmds []commands.DockerCommand
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
index int
|
index int
|
||||||
|
|
@ -129,8 +118,8 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||||
fields: fields{
|
fields: fields{
|
||||||
stage: config.KanikoStage{
|
stage: config.KanikoStage{
|
||||||
Final: true,
|
Final: true,
|
||||||
Stage: stage,
|
|
||||||
},
|
},
|
||||||
|
cmds: cmds,
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
index: 1,
|
index: 1,
|
||||||
|
|
@ -142,11 +131,11 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||||
fields: fields{
|
fields: fields{
|
||||||
stage: config.KanikoStage{
|
stage: config.KanikoStage{
|
||||||
Final: false,
|
Final: false,
|
||||||
Stage: stage,
|
|
||||||
},
|
},
|
||||||
|
cmds: cmds,
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
index: len(commands) - 1,
|
index: len(cmds) - 1,
|
||||||
},
|
},
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
|
|
@ -155,8 +144,8 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||||
fields: fields{
|
fields: fields{
|
||||||
stage: config.KanikoStage{
|
stage: config.KanikoStage{
|
||||||
Final: false,
|
Final: false,
|
||||||
Stage: stage,
|
|
||||||
},
|
},
|
||||||
|
cmds: cmds,
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
index: 0,
|
index: 0,
|
||||||
|
|
@ -198,9 +187,9 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||||
fields: fields{
|
fields: fields{
|
||||||
stage: config.KanikoStage{
|
stage: config.KanikoStage{
|
||||||
Final: false,
|
Final: false,
|
||||||
Stage: stage,
|
|
||||||
},
|
},
|
||||||
opts: &config.KanikoOptions{Cache: true},
|
opts: &config.KanikoOptions{Cache: true},
|
||||||
|
cmds: cmds,
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
index: 0,
|
index: 0,
|
||||||
|
|
@ -217,6 +206,7 @@ func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
|
||||||
s := &stageBuilder{
|
s := &stageBuilder{
|
||||||
stage: tt.fields.stage,
|
stage: tt.fields.stage,
|
||||||
opts: tt.fields.opts,
|
opts: tt.fields.opts,
|
||||||
|
cmds: tt.fields.cmds,
|
||||||
}
|
}
|
||||||
if got := s.shouldTakeSnapshot(tt.args.index, tt.args.files, tt.args.hasFiles); got != tt.want {
|
if got := s.shouldTakeSnapshot(tt.args.index, tt.args.files, tt.args.hasFiles); got != tt.want {
|
||||||
t.Errorf("stageBuilder.shouldTakeSnapshot() = %v, want %v", got, tt.want)
|
t.Errorf("stageBuilder.shouldTakeSnapshot() = %v, want %v", got, tt.want)
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ import (
|
||||||
// output set.
|
// output set.
|
||||||
// * Add all ancestors of each path to the output set.
|
// * Add all ancestors of each path to the output set.
|
||||||
func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string, err error) {
|
func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string, err error) {
|
||||||
logrus.Info("Resolving paths")
|
logrus.Infof("Resolving %d paths", len(paths))
|
||||||
logrus.Debugf("Resolving paths %s", paths)
|
logrus.Tracef("Resolving paths %s", paths)
|
||||||
|
|
||||||
fileSet := make(map[string]bool)
|
fileSet := make(map[string]bool)
|
||||||
|
|
||||||
|
|
@ -73,6 +73,7 @@ func ResolvePaths(paths []string, wl []util.WhitelistEntry) (pathsToAdd []string
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("symlink path %s, target does not exist", f)
|
logrus.Debugf("symlink path %s, target does not exist", f)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the given path is a symlink and the target is part of the whitelist
|
// If the given path is a symlink and the target is part of the whitelist
|
||||||
|
|
|
||||||
|
|
@ -226,10 +226,28 @@ func writeToTar(t util.Tar, files, whiteouts []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addedPaths := make(map[string]bool)
|
||||||
for _, path := range files {
|
for _, path := range files {
|
||||||
|
if _, fileExists := addedPaths[path]; fileExists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, parentPath := range util.ParentDirectories(path) {
|
||||||
|
if parentPath == "/" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, dirExists := addedPaths[parentPath]; dirExists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := t.AddFileToTar(parentPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
addedPaths[parentPath] = true
|
||||||
|
}
|
||||||
if err := t.AddFileToTar(path); err != nil {
|
if err := t.AddFileToTar(path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
addedPaths[path] = true
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ func TestSnapshotFSFileChange(t *testing.T) {
|
||||||
fooPath: "newbaz1",
|
fooPath: "newbaz1",
|
||||||
batPath: "baz",
|
batPath: "baz",
|
||||||
}
|
}
|
||||||
|
for _, path := range util.ParentDirectoriesWithoutLeadingSlash(batPath) {
|
||||||
|
if path == "/" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
snapshotFiles[path+"/"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
actualFiles := []string{}
|
actualFiles := []string{}
|
||||||
for {
|
for {
|
||||||
|
|
@ -77,6 +83,9 @@ func TestSnapshotFSFileChange(t *testing.T) {
|
||||||
if _, isFile := snapshotFiles[hdr.Name]; !isFile {
|
if _, isFile := snapshotFiles[hdr.Name]; !isFile {
|
||||||
t.Fatalf("File %s unexpectedly in tar", hdr.Name)
|
t.Fatalf("File %s unexpectedly in tar", hdr.Name)
|
||||||
}
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeDir {
|
||||||
|
continue
|
||||||
|
}
|
||||||
contents, _ := ioutil.ReadAll(tr)
|
contents, _ := ioutil.ReadAll(tr)
|
||||||
if string(contents) != snapshotFiles[hdr.Name] {
|
if string(contents) != snapshotFiles[hdr.Name] {
|
||||||
t.Fatalf("Contents of %s incorrect, expected: %s, actual: %s", hdr.Name, snapshotFiles[hdr.Name], string(contents))
|
t.Fatalf("Contents of %s incorrect, expected: %s, actual: %s", hdr.Name, snapshotFiles[hdr.Name], string(contents))
|
||||||
|
|
@ -153,6 +162,12 @@ func TestSnapshotFSChangePermissions(t *testing.T) {
|
||||||
snapshotFiles := map[string]string{
|
snapshotFiles := map[string]string{
|
||||||
batPathWithoutLeadingSlash: "baz2",
|
batPathWithoutLeadingSlash: "baz2",
|
||||||
}
|
}
|
||||||
|
for _, path := range util.ParentDirectoriesWithoutLeadingSlash(batPathWithoutLeadingSlash) {
|
||||||
|
if path == "/" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
snapshotFiles[path+"/"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
foundFiles := []string{}
|
foundFiles := []string{}
|
||||||
for {
|
for {
|
||||||
|
|
@ -164,6 +179,9 @@ func TestSnapshotFSChangePermissions(t *testing.T) {
|
||||||
if _, isFile := snapshotFiles[hdr.Name]; !isFile {
|
if _, isFile := snapshotFiles[hdr.Name]; !isFile {
|
||||||
t.Fatalf("File %s unexpectedly in tar", hdr.Name)
|
t.Fatalf("File %s unexpectedly in tar", hdr.Name)
|
||||||
}
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeDir {
|
||||||
|
continue
|
||||||
|
}
|
||||||
contents, _ := ioutil.ReadAll(tr)
|
contents, _ := ioutil.ReadAll(tr)
|
||||||
if string(contents) != snapshotFiles[hdr.Name] {
|
if string(contents) != snapshotFiles[hdr.Name] {
|
||||||
t.Fatalf("Contents of %s incorrect, expected: %s, actual: %s", hdr.Name, snapshotFiles[hdr.Name], string(contents))
|
t.Fatalf("Contents of %s incorrect, expected: %s, actual: %s", hdr.Name, snapshotFiles[hdr.Name], string(contents))
|
||||||
|
|
@ -203,7 +221,9 @@ func TestSnapshotFiles(t *testing.T) {
|
||||||
expectedFiles := []string{
|
expectedFiles := []string{
|
||||||
filepath.Join(testDirWithoutLeadingSlash, "foo"),
|
filepath.Join(testDirWithoutLeadingSlash, "foo"),
|
||||||
}
|
}
|
||||||
expectedFiles = append(expectedFiles, util.ParentDirectoriesWithoutLeadingSlash(filepath.Join(testDir, "foo"))...)
|
for _, path := range util.ParentDirectoriesWithoutLeadingSlash(filepath.Join(testDir, "foo")) {
|
||||||
|
expectedFiles = append(expectedFiles, strings.TrimRight(path, "/")+"/")
|
||||||
|
}
|
||||||
|
|
||||||
f, err := os.Open(tarPath)
|
f, err := os.Open(tarPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -468,10 +468,10 @@ func ParentDirectories(path string) []string {
|
||||||
}
|
}
|
||||||
dir, _ = filepath.Split(dir)
|
dir, _ = filepath.Split(dir)
|
||||||
dir = filepath.Clean(dir)
|
dir = filepath.Clean(dir)
|
||||||
paths = append(paths, dir)
|
paths = append([]string{dir}, paths...)
|
||||||
}
|
}
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
paths = append(paths, config.RootDir)
|
paths = []string{config.RootDir}
|
||||||
}
|
}
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,8 +213,6 @@ func Test_ParentDirectories(t *testing.T) {
|
||||||
defer func() { config.RootDir = original }()
|
defer func() { config.RootDir = original }()
|
||||||
config.RootDir = tt.rootDir
|
config.RootDir = tt.rootDir
|
||||||
actual := ParentDirectories(tt.path)
|
actual := ParentDirectories(tt.path)
|
||||||
sort.Strings(actual)
|
|
||||||
sort.Strings(tt.expected)
|
|
||||||
|
|
||||||
testutil.CheckErrorAndDeepEqual(t, false, nil, tt.expected, actual)
|
testutil.CheckErrorAndDeepEqual(t, false, nil, tt.expected, actual)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,9 @@ func (t *Tar) AddFileToTar(p string) error {
|
||||||
hdr.Name = strings.TrimPrefix(p, config.RootDir)
|
hdr.Name = strings.TrimPrefix(p, config.RootDir)
|
||||||
hdr.Name = strings.TrimLeft(hdr.Name, "/")
|
hdr.Name = strings.TrimLeft(hdr.Name, "/")
|
||||||
}
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeDir && !strings.HasSuffix(hdr.Name, "/") {
|
||||||
|
hdr.Name = hdr.Name + "/"
|
||||||
|
}
|
||||||
// rootfs may not have been extracted when using cache, preventing uname/gname from resolving
|
// rootfs may not have been extracted when using cache, preventing uname/gname from resolving
|
||||||
// this makes this layer unnecessarily differ from a cached layer which does contain this information
|
// this makes this layer unnecessarily differ from a cached layer which does contain this information
|
||||||
hdr.Uname = ""
|
hdr.Uname = ""
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -134,3 +135,12 @@ func currentPlatform() v1.Platform {
|
||||||
Architecture: runtime.GOARCH,
|
Architecture: runtime.GOARCH,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInputFrom returns Reader content
|
||||||
|
func GetInputFrom(r io.Reader) ([]byte, error) {
|
||||||
|
output, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetInputFrom(t *testing.T) {
|
||||||
|
validInput := []byte("Valid\n")
|
||||||
|
validReader := bufio.NewReader(bytes.NewReader((validInput)))
|
||||||
|
validValue, err := GetInputFrom(validReader)
|
||||||
|
testutil.CheckErrorAndDeepEqual(t, false, err, validInput, validValue)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue