diff --git a/README.md b/README.md index b796117242..5515ed4fcf 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ When running kaniko, use the `--context` flag with the appropriate prefix to spe | Source | Prefix | Example | |---------|---------|---------| | 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` | | 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` | | Azure Blob Storage| https://[account].[azureblobhostsuffix]/[container]/[path to .tar.gz] | `https://myaccount.blob.core.windows.net/container/path/to/context.tar.gz` | diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 87c661e294..ff198060d3 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -233,7 +233,7 @@ func copyDockerfile() error { return nil } -// resolveSourceContext unpacks the source context if it is a tar in a bucket +// resolveSourceContext unpacks the source context if it is a tar in a bucket or in kaniko container // it resets srcContext to be the path to the unpacked build context within the image func resolveSourceContext() error { if opts.SrcContext == "" && opts.Bucket == "" { diff --git a/integration/integration_test.go b/integration/integration_test.go index 357b46cb0f..6781f7fe78 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -33,11 +33,10 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/daemon" - "github.com/pkg/errors" - "github.com/GoogleContainerTools/kaniko/pkg/timing" "github.com/GoogleContainerTools/kaniko/pkg/util" "github.com/GoogleContainerTools/kaniko/testutil" + "github.com/pkg/errors" ) var config *integrationTestConfig diff --git a/pkg/buildcontext/buildcontext.go b/pkg/buildcontext/buildcontext.go index 3142239a83..ef39c21e27 100644 --- a/pkg/buildcontext/buildcontext.go +++ b/pkg/buildcontext/buildcontext.go @@ -24,6 +24,10 @@ import ( "github.com/GoogleContainerTools/kaniko/pkg/util" ) +const ( + TarBuildContextPrefix = "tar://" +) + // BuildContext unifies calls to download and unpack the build context. type BuildContext interface { // Unpacks a build context and returns the directory where it resides @@ -51,6 +55,8 @@ func GetBuildContext(srcContext string) (BuildContext, error) { 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("unknown build context prefix provided, please use one of the following: gs://, dir://, s3://, git://, https://") + return nil, errors.New("unknown build context prefix provided, please use one of the following: gs://, dir://, tar://, s3://, git://, https://") } diff --git a/pkg/buildcontext/tar.go b/pkg/buildcontext/tar.go new file mode 100644 index 0000000000..d5d1c250ad --- /dev/null +++ b/pkg/buildcontext/tar.go @@ -0,0 +1,40 @@ +/* +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 buildcontext + +import ( + "os" + + "github.com/GoogleContainerTools/kaniko/pkg/constants" + "github.com/GoogleContainerTools/kaniko/pkg/util" + "github.com/pkg/errors" +) + +// Tar unifies calls to download and unpack the build context. +type Tar struct { + context string +} + +// UnpackTarFromBuildContext unpack the compressed tar file +func (t *Tar) UnpackTarFromBuildContext() (string, error) { + directory := constants.BuildContextDir + if err := os.MkdirAll(directory, 0750); err != nil { + return "", errors.Wrap(err, "unpacking tar from build context") + } + + return directory, util.UnpackCompressedTar(t.context, directory) +} diff --git a/pkg/buildcontext/tar_test.go b/pkg/buildcontext/tar_test.go new file mode 100644 index 0000000000..5904b4e603 --- /dev/null +++ b/pkg/buildcontext/tar_test.go @@ -0,0 +1,158 @@ +/* +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 buildcontext + +import ( + "bytes" + "compress/gzip" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "sync" + "testing" + + "github.com/GoogleContainerTools/kaniko/pkg/util" + "github.com/GoogleContainerTools/kaniko/testutil" +) + +func TestBuildWithLocalTar(t *testing.T) { + _, ex, _, _ := runtime.Caller(0) + cwd := filepath.Dir(ex) + + testDir := "test_dir" + testDirLongPath := filepath.Join(cwd, testDir) + dirUnpack := filepath.Join(testDirLongPath, "dir_where_to_unpack") + + if err := os.MkdirAll(dirUnpack, 0750); err != nil { + t.Errorf("Failed to create dir_where_to_extract: %v", err) + } + + validDockerfile := "Dockerfile_valid" + invalidDockerfile := "Dockerfile_invalid" + nonExistingDockerfile := "Dockerfile_non_existing" + + files := map[string]string{ + validDockerfile: "FROM debian:9.11\nRUN echo \"valid\"", + invalidDockerfile: "FROM debian:9.11\nRUN echo \"invalid\"", + } + + 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) + } + + validTarPath := fmt.Sprintf("%s.tar.gz", validDockerfile) + invalidTarPath := fmt.Sprintf("%s.tar.gz", invalidDockerfile) + nonExistingTarPath := fmt.Sprintf("%s.tar.gz", nonExistingDockerfile) + + var wg sync.WaitGroup + wg.Add(1) + // Create Tar Gz File with dockerfile inside + go func(wg *sync.WaitGroup) { + defer wg.Done() + validTarFile, err := os.Create(validTarPath) + if err != nil { + t.Errorf("Failed to create %s: %v", validTarPath, err) + } + defer validTarFile.Close() + + invalidTarFile, err := os.Create(invalidTarPath) + if err != nil { + t.Errorf("Failed to create %s: %v", invalidTarPath, err) + } + defer invalidTarFile.Close() + + gw := gzip.NewWriter(validTarFile) + defer gw.Close() + + tw := util.NewTar(gw) + defer tw.Close() + + if err := tw.AddFileToTar(validDockerfile); err != nil { + t.Errorf("Failed to add %s to %s: %v", validDockerfile, validTarPath, err) + } + }(&wg) + + // Waiting for the Tar Gz file creation to be done before moving on + wg.Wait() + + tests := []struct { + dockerfile string + srcContext string + unpackShouldErr bool + srcShaShouldErr bool + destShaShouldErr bool + }{ + { + dockerfile: validDockerfile, + srcContext: filepath.Join(testDir, validTarPath), + unpackShouldErr: false, + srcShaShouldErr: false, + destShaShouldErr: false, + }, + { + dockerfile: invalidDockerfile, + srcContext: filepath.Join(testDir, invalidTarPath), + unpackShouldErr: true, + srcShaShouldErr: false, + destShaShouldErr: true, + }, + { + dockerfile: nonExistingDockerfile, + srcContext: filepath.Join(testDir, nonExistingTarPath), + unpackShouldErr: true, + srcShaShouldErr: true, + destShaShouldErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.dockerfile, func(t *testing.T) { + err := util.UnpackCompressedTar(filepath.Join(cwd, tt.srcContext), dirUnpack) + testutil.CheckError(t, tt.unpackShouldErr, err) + srcSHA, err := getSHAFromFilePath(tt.dockerfile) + testutil.CheckError(t, tt.srcShaShouldErr, err) + destSHA, err := getSHAFromFilePath(filepath.Join(dirUnpack, tt.dockerfile)) + testutil.CheckError(t, tt.destShaShouldErr, err) + if err == nil { + testutil.CheckDeepEqual(t, srcSHA, destSHA) + } + }) + } + + if err := os.RemoveAll(testDirLongPath); err != nil { + t.Errorf("Failed to remove %s: %v", testDirLongPath, err) + } +} + +func getSHAFromFilePath(f string) (string, error) { + data, err := ioutil.ReadFile(f) + if err != nil { + return "", err + } + sha, err := util.SHA256(bytes.NewReader(data)) + if err != nil { + return "", err + } + + return sha, nil +}