mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-21 11:24:54 +02:00
Updating the Fork
This commit is contained in:
@@ -17,14 +17,15 @@ limitations under the License.
|
||||
package buildcontext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// S3 unifies calls to download and unpack the build context.
|
||||
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
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 cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/authn/k8schain"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// RetrieveLayer checks the specified cache for a layer with the tag :cacheKey
|
||||
func RetrieveLayer(opts *config.KanikoOptions, cacheKey string) (v1.Image, error) {
|
||||
cache, err := Destination(opts, cacheKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting cache destination")
|
||||
}
|
||||
logrus.Infof("Checking for cached layer %s...", cache)
|
||||
|
||||
cacheRef, err := name.NewTag(cache, name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("getting reference for %s", cache))
|
||||
}
|
||||
k8sc, err := k8schain.NewNoClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kc := authn.NewMultiKeychain(authn.DefaultKeychain, k8sc)
|
||||
img, err := remote.Image(cacheRef, remote.WithAuthFromKeychain(kc))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = img.Layers()
|
||||
return img, err
|
||||
}
|
||||
|
||||
// Destination returns the repo where the layer should be stored
|
||||
// If no cache is specified, one is inferred from the destination provided
|
||||
func Destination(opts *config.KanikoOptions, cacheKey string) (string, error) {
|
||||
cache := opts.CacheRepo
|
||||
if cache == "" {
|
||||
destination := opts.Destinations[0]
|
||||
destRef, err := name.NewTag(destination, name.WeakValidation)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "getting tag for destination")
|
||||
}
|
||||
return fmt.Sprintf("%s/cache:%s", destRef.Context(), cacheKey), nil
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", cache, cacheKey), nil
|
||||
}
|
||||
+10
-12
@@ -18,7 +18,6 @@ package commands
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
|
||||
@@ -44,21 +43,15 @@ type AddCommand struct {
|
||||
// 2. If <src> is a local tar archive:
|
||||
// -If <src> is a local tar archive, it is unpacked at the dest, as 'tar -x' would
|
||||
func (a *AddCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
srcs := a.cmd.SourcesAndDest[:len(a.cmd.SourcesAndDest)-1]
|
||||
dest := a.cmd.SourcesAndDest[len(a.cmd.SourcesAndDest)-1]
|
||||
|
||||
logrus.Infof("cmd: Add %s", srcs)
|
||||
logrus.Infof("dest: %s", dest)
|
||||
|
||||
// First, resolve any environment replacement
|
||||
replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
|
||||
resolvedEnvs, err := util.ResolveEnvironmentReplacementList(a.cmd.SourcesAndDest, replacementEnvs, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest = resolvedEnvs[len(resolvedEnvs)-1]
|
||||
dest := resolvedEnvs[len(resolvedEnvs)-1]
|
||||
// Resolve wildcards and get a list of resolved sources
|
||||
srcs, err = util.ResolveSources(resolvedEnvs, a.buildcontext)
|
||||
srcs, err := util.ResolveSources(resolvedEnvs, a.buildcontext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -112,7 +105,12 @@ func (a *AddCommand) FilesToSnapshot() []string {
|
||||
return a.snapshotFiles
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config
|
||||
func (a *AddCommand) CreatedBy() string {
|
||||
return strings.Join(a.cmd.SourcesAndDest, " ")
|
||||
// String returns some information about the command for the image config
|
||||
func (a *AddCommand) String() string {
|
||||
return a.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (a *AddCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+8
-7
@@ -17,13 +17,10 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ArgCommand struct {
|
||||
@@ -32,7 +29,6 @@ type ArgCommand struct {
|
||||
|
||||
// ExecuteCommand only needs to add this ARG key/value as seen
|
||||
func (r *ArgCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Info("ARG")
|
||||
replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
|
||||
resolvedKey, err := util.ResolveEnvironmentReplacement(r.cmd.Key, replacementEnvs, false)
|
||||
if err != nil {
|
||||
@@ -55,7 +51,12 @@ func (r *ArgCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (r *ArgCommand) CreatedBy() string {
|
||||
return strings.Join([]string{r.cmd.Name(), r.cmd.Key}, " ")
|
||||
// String returns some information about the command for the image config history
|
||||
func (r *ArgCommand) String() string {
|
||||
return r.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (r *ArgCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+8
-14
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type CmdCommand struct {
|
||||
@@ -33,7 +32,6 @@ type CmdCommand struct {
|
||||
// ExecuteCommand executes the CMD command
|
||||
// Argument handling is the same as RUN.
|
||||
func (c *CmdCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Info("cmd: CMD")
|
||||
var newCommand []string
|
||||
if c.cmd.PrependShell {
|
||||
// This is the default shell on Linux
|
||||
@@ -49,7 +47,6 @@ func (c *CmdCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui
|
||||
newCommand = c.cmd.CmdLine
|
||||
}
|
||||
|
||||
logrus.Infof("Replacing CMD in config with %v", newCommand)
|
||||
config.Cmd = newCommand
|
||||
config.ArgsEscaped = true
|
||||
return nil
|
||||
@@ -60,15 +57,12 @@ func (c *CmdCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (c *CmdCommand) CreatedBy() string {
|
||||
cmd := []string{"CMD"}
|
||||
cmdLine := strings.Join(c.cmd.CmdLine, " ")
|
||||
if c.cmd.PrependShell {
|
||||
// TODO: Support shell command here
|
||||
shell := []string{"/bin/sh", "-c"}
|
||||
appendedShell := append(cmd, shell...)
|
||||
return strings.Join(append(appendedShell, cmdLine), " ")
|
||||
}
|
||||
return strings.Join(append(cmd, cmdLine), " ")
|
||||
// String returns some information about the command for the image config history
|
||||
func (c *CmdCommand) String() string {
|
||||
return c.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (c *CmdCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -30,10 +30,13 @@ type DockerCommand interface {
|
||||
// 2. Updating metadata fields in the config
|
||||
// It should not change the config history.
|
||||
ExecuteCommand(*v1.Config, *dockerfile.BuildArgs) error
|
||||
// The config history has a "created by" field, should return information about the command
|
||||
CreatedBy() string
|
||||
// Returns a string representation of the command
|
||||
String() string
|
||||
// A list of files to snapshot, empty for metadata commands or nil if we don't know
|
||||
FilesToSnapshot() []string
|
||||
// Return true if this command should be true
|
||||
// Currently only true for RUN
|
||||
CacheCommand() bool
|
||||
}
|
||||
|
||||
func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, error) {
|
||||
|
||||
+10
-13
@@ -19,7 +19,6 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
|
||||
@@ -27,7 +26,6 @@ import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type CopyCommand struct {
|
||||
@@ -37,12 +35,6 @@ type CopyCommand struct {
|
||||
}
|
||||
|
||||
func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
srcs := c.cmd.SourcesAndDest[:len(c.cmd.SourcesAndDest)-1]
|
||||
dest := c.cmd.SourcesAndDest[len(c.cmd.SourcesAndDest)-1]
|
||||
|
||||
logrus.Infof("cmd: copy %s", srcs)
|
||||
logrus.Infof("dest: %s", dest)
|
||||
|
||||
// Resolve from
|
||||
if c.cmd.From != "" {
|
||||
c.buildcontext = filepath.Join(constants.KanikoDir, c.cmd.From)
|
||||
@@ -53,9 +45,9 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest = resolvedEnvs[len(resolvedEnvs)-1]
|
||||
dest := resolvedEnvs[len(resolvedEnvs)-1]
|
||||
// Resolve wildcards and get a list of resolved sources
|
||||
srcs, err = util.ResolveSources(resolvedEnvs, c.buildcontext)
|
||||
srcs, err := util.ResolveSources(resolvedEnvs, c.buildcontext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -106,7 +98,12 @@ func (c *CopyCommand) FilesToSnapshot() []string {
|
||||
return c.snapshotFiles
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config
|
||||
func (c *CopyCommand) CreatedBy() string {
|
||||
return strings.Join(c.cmd.SourcesAndDest, " ")
|
||||
// String returns some information about the command for the image config
|
||||
func (c *CopyCommand) String() string {
|
||||
return c.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns true since this command should be cached
|
||||
func (c *CopyCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type EntrypointCommand struct {
|
||||
@@ -32,7 +31,6 @@ type EntrypointCommand struct {
|
||||
|
||||
// ExecuteCommand handles command processing similar to CMD and RUN,
|
||||
func (e *EntrypointCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Info("cmd: ENTRYPOINT")
|
||||
var newCommand []string
|
||||
if e.cmd.PrependShell {
|
||||
// This is the default shell on Linux
|
||||
@@ -48,7 +46,6 @@ func (e *EntrypointCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerf
|
||||
newCommand = e.cmd.CmdLine
|
||||
}
|
||||
|
||||
logrus.Infof("Replacing Entrypoint in config with %v", newCommand)
|
||||
config.Entrypoint = newCommand
|
||||
return nil
|
||||
}
|
||||
@@ -58,15 +55,12 @@ func (e *EntrypointCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (e *EntrypointCommand) CreatedBy() string {
|
||||
entrypoint := []string{"ENTRYPOINT"}
|
||||
cmdLine := strings.Join(e.cmd.CmdLine, " ")
|
||||
if e.cmd.PrependShell {
|
||||
// TODO: Support shell command here
|
||||
shell := []string{"/bin/sh", "-c"}
|
||||
appendedShell := append(entrypoint, shell...)
|
||||
return strings.Join(append(appendedShell, cmdLine), " ")
|
||||
}
|
||||
return strings.Join(append(entrypoint, cmdLine), " ")
|
||||
// String returns some information about the command for the image config history
|
||||
func (e *EntrypointCommand) String() string {
|
||||
return e.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (e *EntrypointCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+8
-11
@@ -17,14 +17,11 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type EnvCommand struct {
|
||||
@@ -32,7 +29,6 @@ type EnvCommand struct {
|
||||
}
|
||||
|
||||
func (e *EnvCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Info("cmd: ENV")
|
||||
newEnvs := e.cmd.Env
|
||||
replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
|
||||
return util.UpdateConfigEnv(newEnvs, config, replacementEnvs)
|
||||
@@ -43,11 +39,12 @@ func (e *EnvCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (e *EnvCommand) CreatedBy() string {
|
||||
envArray := []string{e.cmd.Name()}
|
||||
for _, pair := range e.cmd.Env {
|
||||
envArray = append(envArray, pair.Key+"="+pair.Value)
|
||||
}
|
||||
return strings.Join(envArray, " ")
|
||||
// String returns some information about the command for the image config history
|
||||
func (e *EnvCommand) String() string {
|
||||
return e.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (e *EnvCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -76,7 +76,11 @@ func (r *ExposeCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (r *ExposeCommand) CreatedBy() string {
|
||||
s := []string{r.cmd.Name()}
|
||||
return strings.Join(append(s, r.cmd.Ports...), " ")
|
||||
func (r *ExposeCommand) String() string {
|
||||
return r.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (r *ExposeCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -17,12 +17,9 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type HealthCheckCommand struct {
|
||||
@@ -31,8 +28,6 @@ type HealthCheckCommand struct {
|
||||
|
||||
// ExecuteCommand handles command processing similar to CMD and RUN,
|
||||
func (h *HealthCheckCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Info("cmd: HEALTHCHECK")
|
||||
|
||||
check := v1.HealthConfig(*h.cmd.Health)
|
||||
config.Healthcheck = &check
|
||||
|
||||
@@ -44,9 +39,12 @@ func (h *HealthCheckCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (h *HealthCheckCommand) CreatedBy() string {
|
||||
entrypoint := []string{"HEALTHCHECK"}
|
||||
// String returns some information about the command for the image config history
|
||||
func (h *HealthCheckCommand) String() string {
|
||||
return h.cmd.String()
|
||||
}
|
||||
|
||||
return strings.Join(append(entrypoint, strings.Join(h.cmd.Health.Test, " ")), " ")
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (h *HealthCheckCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+8
-10
@@ -17,8 +17,6 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
@@ -32,7 +30,6 @@ type LabelCommand struct {
|
||||
}
|
||||
|
||||
func (r *LabelCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Info("cmd: LABEL")
|
||||
return updateLabels(r.cmd.Labels, config, buildArgs)
|
||||
}
|
||||
|
||||
@@ -72,11 +69,12 @@ func (r *LabelCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (r *LabelCommand) CreatedBy() string {
|
||||
l := []string{r.cmd.Name()}
|
||||
for _, kvp := range r.cmd.Labels {
|
||||
l = append(l, kvp.String())
|
||||
}
|
||||
return strings.Join(l, " ")
|
||||
// String returns some information about the command for the image config history
|
||||
func (r *LabelCommand) String() string {
|
||||
return r.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (r *LabelCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -44,7 +44,12 @@ func (o *OnBuildCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (o *OnBuildCommand) CreatedBy() string {
|
||||
return o.cmd.Expression
|
||||
// String returns some information about the command for the image config history
|
||||
func (o *OnBuildCommand) String() string {
|
||||
return o.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (o *OnBuildCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+23
-13
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -116,19 +117,29 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui
|
||||
}
|
||||
|
||||
// addDefaultHOME adds the default value for HOME if it isn't already set
|
||||
func addDefaultHOME(user string, envs []string) []string {
|
||||
func addDefaultHOME(u string, envs []string) []string {
|
||||
for _, env := range envs {
|
||||
split := strings.SplitN(env, "=", 2)
|
||||
if split[0] == constants.HOME {
|
||||
return envs
|
||||
}
|
||||
}
|
||||
|
||||
// If user isn't set, set default value of HOME
|
||||
if user == "" {
|
||||
if u == "" {
|
||||
return append(envs, fmt.Sprintf("%s=%s", constants.HOME, constants.DefaultHOMEValue))
|
||||
}
|
||||
// If user is set, set value of HOME to /home/${user}
|
||||
return append(envs, fmt.Sprintf("%s=/home/%s", constants.HOME, user))
|
||||
|
||||
// If user is set to username, set value of HOME to /home/${user}
|
||||
// Otherwise the user is set to uid and HOME is /
|
||||
home := fmt.Sprintf("%s=/", constants.HOME)
|
||||
userObj, err := user.Lookup(u)
|
||||
if err == nil {
|
||||
u = userObj.Username
|
||||
home = fmt.Sprintf("%s=/home/%s", constants.HOME, u)
|
||||
}
|
||||
|
||||
return append(envs, home)
|
||||
}
|
||||
|
||||
// FilesToSnapshot returns nil for this command because we don't know which files
|
||||
@@ -137,13 +148,12 @@ func (r *RunCommand) FilesToSnapshot() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config
|
||||
func (r *RunCommand) CreatedBy() string {
|
||||
cmdLine := strings.Join(r.cmd.CmdLine, " ")
|
||||
if r.cmd.PrependShell {
|
||||
// TODO: Support shell command here
|
||||
shell := []string{"/bin/sh", "-c"}
|
||||
return strings.Join(append(shell, cmdLine), " ")
|
||||
}
|
||||
return cmdLine
|
||||
// String returns some information about the command for the image config
|
||||
func (r *RunCommand) String() string {
|
||||
return r.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns true since this command should be cached
|
||||
func (r *RunCommand) CacheCommand() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func Test_addDefaultHOME(t *testing.T) {
|
||||
},
|
||||
expected: []string{
|
||||
"PATH=/something/else",
|
||||
"HOME=/home/newuser",
|
||||
"HOME=/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+8
-15
@@ -17,12 +17,9 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ShellCommand struct {
|
||||
@@ -31,13 +28,7 @@ type ShellCommand struct {
|
||||
|
||||
// ExecuteCommand handles command processing similar to CMD and RUN,
|
||||
func (s *ShellCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||
logrus.Info("cmd: SHELL")
|
||||
var newShell []string
|
||||
|
||||
newShell = s.cmd.Shell
|
||||
|
||||
logrus.Infof("Replacing Shell in config with %v", newShell)
|
||||
config.Shell = newShell
|
||||
config.Shell = s.cmd.Shell
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -46,10 +37,12 @@ func (s *ShellCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (s *ShellCommand) CreatedBy() string {
|
||||
entrypoint := []string{"SHELL"}
|
||||
cmdLine := strings.Join(s.cmd.Shell, " ")
|
||||
// String returns some information about the command for the image config history
|
||||
func (s *ShellCommand) String() string {
|
||||
return s.cmd.String()
|
||||
}
|
||||
|
||||
return strings.Join(append(entrypoint, cmdLine), " ")
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (s *ShellCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
@@ -59,9 +57,12 @@ func (s *StopSignalCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (s *StopSignalCommand) CreatedBy() string {
|
||||
entrypoint := []string{"STOPSIGNAL"}
|
||||
// String returns some information about the command for the image config history
|
||||
func (s *StopSignalCommand) String() string {
|
||||
return s.cmd.String()
|
||||
}
|
||||
|
||||
return strings.Join(append(entrypoint, s.cmd.Signal), " ")
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (s *StopSignalCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -63,7 +63,11 @@ func (r *UserCommand) FilesToSnapshot() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (r *UserCommand) CreatedBy() string {
|
||||
s := []string{r.cmd.Name(), r.cmd.User}
|
||||
return strings.Join(s, " ")
|
||||
func (r *UserCommand) String() string {
|
||||
return r.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (r *UserCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
+12
-12
@@ -27,57 +27,57 @@ import (
|
||||
|
||||
var userTests = []struct {
|
||||
user string
|
||||
expectedUid string
|
||||
expectedUID string
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
user: "root",
|
||||
expectedUid: "root",
|
||||
expectedUID: "root",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "0",
|
||||
expectedUid: "0",
|
||||
expectedUID: "0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "fakeUser",
|
||||
expectedUid: "",
|
||||
expectedUID: "",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
user: "root:root",
|
||||
expectedUid: "root:root",
|
||||
expectedUID: "root:root",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "0:root",
|
||||
expectedUid: "0:root",
|
||||
expectedUID: "0:root",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "root:0",
|
||||
expectedUid: "root:0",
|
||||
expectedUID: "root:0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "0:0",
|
||||
expectedUid: "0:0",
|
||||
expectedUID: "0:0",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "root:fakeGroup",
|
||||
expectedUid: "",
|
||||
expectedUID: "",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
user: "$envuser",
|
||||
expectedUid: "root",
|
||||
expectedUID: "root",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
user: "root:$envgroup",
|
||||
expectedUid: "root:root",
|
||||
expectedUID: "root:root",
|
||||
shouldError: false,
|
||||
},
|
||||
}
|
||||
@@ -97,6 +97,6 @@ func TestUpdateUser(t *testing.T) {
|
||||
}
|
||||
buildArgs := dockerfile.NewBuildArgs([]string{})
|
||||
err := cmd.ExecuteCommand(cfg, buildArgs)
|
||||
testutil.CheckErrorAndDeepEqual(t, test.shouldError, err, test.expectedUid, cfg.User)
|
||||
testutil.CheckErrorAndDeepEqual(t, test.shouldError, err, test.expectedUID, cfg.User)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package commands
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
|
||||
@@ -49,7 +48,7 @@ func (v *VolumeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.
|
||||
for _, volume := range resolvedVolumes {
|
||||
var x struct{}
|
||||
existingVolumes[volume] = x
|
||||
err := util.AddPathToVolumeWhitelist(volume)
|
||||
err := util.AddVolumePathToWhitelist(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -57,7 +56,7 @@ func (v *VolumeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.
|
||||
// Only create and snapshot the dir if it didn't exist already
|
||||
if _, err := os.Stat(volume); os.IsNotExist(err) {
|
||||
logrus.Infof("Creating directory %s", volume)
|
||||
v.snapshotFiles = []string{volume}
|
||||
v.snapshotFiles = append(v.snapshotFiles, volume)
|
||||
if err := os.MkdirAll(volume, 0755); err != nil {
|
||||
return fmt.Errorf("Could not create directory for volume %s: %s", volume, err)
|
||||
}
|
||||
@@ -72,6 +71,11 @@ func (v *VolumeCommand) FilesToSnapshot() []string {
|
||||
return v.snapshotFiles
|
||||
}
|
||||
|
||||
func (v *VolumeCommand) CreatedBy() string {
|
||||
return strings.Join(append([]string{v.cmd.Name()}, v.cmd.Volumes...), " ")
|
||||
func (v *VolumeCommand) String() string {
|
||||
return v.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (v *VolumeCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -62,7 +62,12 @@ func (w *WorkdirCommand) FilesToSnapshot() []string {
|
||||
return w.snapshotFiles
|
||||
}
|
||||
|
||||
// CreatedBy returns some information about the command for the image config history
|
||||
func (w *WorkdirCommand) CreatedBy() string {
|
||||
return w.cmd.Name() + " " + w.cmd.Path
|
||||
// String returns some information about the command for the image config history
|
||||
func (w *WorkdirCommand) String() string {
|
||||
return w.cmd.String()
|
||||
}
|
||||
|
||||
// CacheCommand returns false since this command shouldn't be cached
|
||||
func (w *WorkdirCommand) CacheCommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package options
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -34,7 +34,7 @@ func (b *multiArg) String() string {
|
||||
|
||||
// The second method is Set(value string) error
|
||||
func (b *multiArg) Set(value string) error {
|
||||
logrus.Infof("appending to multi args %s", value)
|
||||
logrus.Debugf("appending to multi args %s", value)
|
||||
*b = append(*b, value)
|
||||
return nil
|
||||
}
|
||||
@@ -14,21 +14,24 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package options
|
||||
package config
|
||||
|
||||
// KanikoOptions are options that are set by command line arguments
|
||||
type KanikoOptions struct {
|
||||
DockerfilePath string
|
||||
Destinations multiArg
|
||||
SrcContext string
|
||||
SnapshotMode string
|
||||
Bucket string
|
||||
InsecurePush bool
|
||||
SkipTlsVerify bool
|
||||
BuildArgs multiArg
|
||||
TarPath string
|
||||
Target string
|
||||
CacheRepo string
|
||||
Destinations multiArg
|
||||
BuildArgs multiArg
|
||||
InsecurePush bool
|
||||
SkipTLSVerify bool
|
||||
SingleSnapshot bool
|
||||
Reproducible bool
|
||||
Target string
|
||||
NoPush bool
|
||||
Cache bool
|
||||
Cleanup bool
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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 config
|
||||
|
||||
import "github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
|
||||
// KanikoStage wraps a stage of the Dockerfile and provides extra information
|
||||
type KanikoStage struct {
|
||||
instructions.Stage
|
||||
BaseImageIndex int
|
||||
Final bool
|
||||
BaseImageStoredLocally bool
|
||||
SaveStage bool
|
||||
}
|
||||
@@ -56,9 +56,16 @@ const (
|
||||
LocalDirBuildContextPrefix = "dir://"
|
||||
GitBuildContextPrefix = "git://"
|
||||
|
||||
HOME = "HOME"
|
||||
// DefaultHOMEValue is the default value Docker sets for $HOME
|
||||
HOME = "HOME"
|
||||
DefaultHOMEValue = "/root"
|
||||
|
||||
// Docker command names
|
||||
Cmd = "cmd"
|
||||
Entrypoint = "entrypoint"
|
||||
|
||||
// VolumeCmdName is the name of the volume command
|
||||
VolumeCmdName = "volume"
|
||||
)
|
||||
|
||||
// KanikoBuildFiles is the list of files required to build kaniko
|
||||
|
||||
@@ -17,8 +17,9 @@ limitations under the License.
|
||||
package dockerfile
|
||||
|
||||
import (
|
||||
d "github.com/docker/docker/builder/dockerfile"
|
||||
"strings"
|
||||
|
||||
d "github.com/docker/docker/builder/dockerfile"
|
||||
)
|
||||
|
||||
type BuildArgs struct {
|
||||
@@ -36,14 +37,14 @@ func NewBuildArgs(args []string) *BuildArgs {
|
||||
}
|
||||
}
|
||||
return &BuildArgs{
|
||||
*d.NewBuildArgs(argsFromOptions),
|
||||
BuildArgs: *d.NewBuildArgs(argsFromOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BuildArgs) Clone() *BuildArgs {
|
||||
clone := b.BuildArgs.Clone()
|
||||
return &BuildArgs{
|
||||
*clone,
|
||||
BuildArgs: *clone,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,26 +23,61 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Stages reads the Dockerfile, validates it's contents, and returns stages
|
||||
func Stages(dockerfilePath, target string) ([]instructions.Stage, error) {
|
||||
d, err := ioutil.ReadFile(dockerfilePath)
|
||||
// Stages parses a Dockerfile and returns an array of KanikoStage
|
||||
func Stages(opts *config.KanikoOptions) ([]config.KanikoStage, error) {
|
||||
d, err := ioutil.ReadFile(opts.DockerfilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("reading dockerfile at path %s", opts.DockerfilePath))
|
||||
}
|
||||
|
||||
stages, err := Parse(d)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parsing dockerfile")
|
||||
}
|
||||
targetStage, err := targetStage(stages, opts.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ValidateTarget(stages, target); err != nil {
|
||||
return nil, err
|
||||
resolveStages(stages)
|
||||
var kanikoStages []config.KanikoStage
|
||||
for index, stage := range stages {
|
||||
resolvedBaseName, err := util.ResolveEnvironmentReplacement(stage.BaseName, opts.BuildArgs, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "resolving base name")
|
||||
}
|
||||
stage.Name = resolvedBaseName
|
||||
kanikoStages = append(kanikoStages, config.KanikoStage{
|
||||
Stage: stage,
|
||||
BaseImageIndex: baseImageIndex(index, stages),
|
||||
BaseImageStoredLocally: (baseImageIndex(index, stages) != -1),
|
||||
SaveStage: saveStage(index, stages),
|
||||
Final: index == targetStage,
|
||||
})
|
||||
if index == targetStage {
|
||||
break
|
||||
}
|
||||
}
|
||||
ResolveStages(stages)
|
||||
return stages, nil
|
||||
return kanikoStages, nil
|
||||
}
|
||||
|
||||
// baseImageIndex returns the index of the stage the current stage is built off
|
||||
// returns -1 if the current stage isn't built off a previous stage
|
||||
func baseImageIndex(currentStage int, stages []instructions.Stage) int {
|
||||
for i, stage := range stages {
|
||||
if i > currentStage {
|
||||
break
|
||||
}
|
||||
if stage.Name == stages[currentStage].BaseName {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Parse parses the contents of a Dockerfile and returns a list of commands
|
||||
@@ -58,21 +93,22 @@ func Parse(b []byte) ([]instructions.Stage, error) {
|
||||
return stages, err
|
||||
}
|
||||
|
||||
func ValidateTarget(stages []instructions.Stage, target string) error {
|
||||
// targetStage returns the index of the target stage kaniko is trying to build
|
||||
func targetStage(stages []instructions.Stage, target string) (int, error) {
|
||||
if target == "" {
|
||||
return nil
|
||||
return len(stages) - 1, nil
|
||||
}
|
||||
for _, stage := range stages {
|
||||
for i, stage := range stages {
|
||||
if stage.Name == target {
|
||||
return nil
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s is not a valid target build stage", target)
|
||||
return -1, fmt.Errorf("%s is not a valid target build stage", target)
|
||||
}
|
||||
|
||||
// ResolveStages resolves any calls to previous stages with names to indices
|
||||
// resolveStages resolves any calls to previous stages with names to indices
|
||||
// Ex. --from=second_stage should be --from=1 for easier processing later on
|
||||
func ResolveStages(stages []instructions.Stage) {
|
||||
func resolveStages(stages []instructions.Stage) {
|
||||
nameToIndex := make(map[string]string)
|
||||
for i, stage := range stages {
|
||||
index := strconv.Itoa(i)
|
||||
@@ -111,7 +147,7 @@ func ParseCommands(cmdArray []string) ([]instructions.Command, error) {
|
||||
}
|
||||
|
||||
// SaveStage returns true if the current stage will be needed later in the Dockerfile
|
||||
func SaveStage(index int, stages []instructions.Stage) bool {
|
||||
func saveStage(index int, stages []instructions.Stage) bool {
|
||||
for stageIndex, stage := range stages {
|
||||
if stageIndex <= index {
|
||||
continue
|
||||
|
||||
@@ -17,9 +17,6 @@ limitations under the License.
|
||||
package dockerfile
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -27,7 +24,7 @@ import (
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
)
|
||||
|
||||
func Test_ResolveStages(t *testing.T) {
|
||||
func Test_resolveStages(t *testing.T) {
|
||||
dockerfile := `
|
||||
FROM scratch
|
||||
RUN echo hi > /hi
|
||||
@@ -42,7 +39,7 @@ func Test_ResolveStages(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ResolveStages(stages)
|
||||
resolveStages(stages)
|
||||
for index, stage := range stages {
|
||||
if index == 0 {
|
||||
continue
|
||||
@@ -55,7 +52,7 @@ func Test_ResolveStages(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ValidateTarget(t *testing.T) {
|
||||
func Test_targetStage(t *testing.T) {
|
||||
dockerfile := `
|
||||
FROM scratch
|
||||
RUN echo hi > /hi
|
||||
@@ -71,70 +68,44 @@ func Test_ValidateTarget(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
target string
|
||||
shouldErr bool
|
||||
name string
|
||||
target string
|
||||
targetIndex int
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
name: "test valid target",
|
||||
target: "second",
|
||||
shouldErr: false,
|
||||
name: "test valid target",
|
||||
target: "second",
|
||||
targetIndex: 1,
|
||||
shouldErr: false,
|
||||
},
|
||||
{
|
||||
name: "test invalid target",
|
||||
target: "invalid",
|
||||
shouldErr: true,
|
||||
name: "test no target",
|
||||
target: "",
|
||||
targetIndex: 2,
|
||||
shouldErr: false,
|
||||
},
|
||||
{
|
||||
name: "test invalid target",
|
||||
target: "invalid",
|
||||
targetIndex: -1,
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actualErr := ValidateTarget(stages, test.target)
|
||||
testutil.CheckError(t, test.shouldErr, actualErr)
|
||||
target, err := targetStage(stages, test.target)
|
||||
testutil.CheckError(t, test.shouldErr, err)
|
||||
if !test.shouldErr {
|
||||
if target != test.targetIndex {
|
||||
t.Errorf("got incorrect target, expected %d got %d", test.targetIndex, target)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SaveStage(t *testing.T) {
|
||||
tempDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
files := map[string]string{
|
||||
"Dockerfile": `
|
||||
FROM scratch
|
||||
RUN echo hi > /hi
|
||||
|
||||
FROM scratch AS second
|
||||
COPY --from=0 /hi /hi2
|
||||
|
||||
FROM second
|
||||
RUN xxx
|
||||
|
||||
FROM scratch
|
||||
COPY --from=second /hi2 /hi3
|
||||
|
||||
FROM ubuntu:16.04 AS base
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
FROM base AS development
|
||||
ENV PS1 " 🐳 \[\033[1;36m\]\W\[\033[0;35m\] # \[\033[0m\]"
|
||||
|
||||
FROM development AS test
|
||||
ENV ORG_ENV UnitTest
|
||||
|
||||
FROM base AS production
|
||||
COPY . /code
|
||||
`,
|
||||
}
|
||||
if err := testutil.SetupFiles(tempDir, files); err != nil {
|
||||
t.Fatalf("couldn't create dockerfile: %v", err)
|
||||
}
|
||||
stages, err := Stages(filepath.Join(tempDir, "Dockerfile"), "")
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't retrieve stages from Dockerfile: %v", err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
index int
|
||||
@@ -171,10 +142,51 @@ func Test_SaveStage(t *testing.T) {
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
stages, err := Parse([]byte(testutil.Dockerfile))
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't retrieve stages from Dockerfile: %v", err)
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := SaveStage(test.index, stages)
|
||||
actual := saveStage(test.index, stages)
|
||||
testutil.CheckErrorAndDeepEqual(t, false, nil, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_baseImageIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
currentStage int
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "stage that is built off of a previous stage",
|
||||
currentStage: 2,
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "another stage that is built off of a previous stage",
|
||||
currentStage: 5,
|
||||
expected: 4,
|
||||
},
|
||||
{
|
||||
name: "stage that isn't built off of a previous stage",
|
||||
currentStage: 4,
|
||||
expected: -1,
|
||||
},
|
||||
}
|
||||
|
||||
stages, err := Parse([]byte(testutil.Dockerfile))
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't retrieve stages from Dockerfile: %v", err)
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := baseImageIndex(test.currentStage, stages)
|
||||
if actual != test.expected {
|
||||
t.Fatalf("unexpected result, expected %d got %d", test.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+226
-98
@@ -18,6 +18,7 @@ package executor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -30,127 +31,238 @@ import (
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/cache"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/commands"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/options"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/snapshot"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
)
|
||||
|
||||
func DoBuild(opts *options.KanikoOptions) (v1.Image, error) {
|
||||
// Parse dockerfile and unpack base image to root
|
||||
stages, err := dockerfile.Stages(opts.DockerfilePath, opts.Target)
|
||||
// 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
|
||||
baseImageDigest string
|
||||
}
|
||||
|
||||
// newStageBuilder returns a new type stageBuilder which contains all the information required to build the stage
|
||||
func newStageBuilder(opts *config.KanikoOptions, stage config.KanikoStage) (*stageBuilder, error) {
|
||||
sourceImage, err := util.RetrieveSourceImage(stage, opts.BuildArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imageConfig, err := util.RetrieveConfigFile(sourceImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasher, err := getHasher(opts.SnapshotMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for index, stage := range stages {
|
||||
finalStage := finalStage(index, opts.Target, stages)
|
||||
// Unpack file system to root
|
||||
sourceImage, err := util.RetrieveSourceImage(index, opts.BuildArgs, stages)
|
||||
l := snapshot.NewLayeredMap(hasher, util.CacheHasher())
|
||||
snapshotter := snapshot.NewSnapshotter(l, constants.RootDir)
|
||||
|
||||
digest, err := sourceImage.Digest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &stageBuilder{
|
||||
stage: stage,
|
||||
image: sourceImage,
|
||||
cf: imageConfig,
|
||||
snapshotter: snapshotter,
|
||||
baseImageDigest: digest.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// key will return a string representation of the build at the cmd
|
||||
func (s *stageBuilder) key(cmd string) (string, error) {
|
||||
fsKey, err := s.snapshotter.Key()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c := bytes.NewBuffer([]byte{})
|
||||
enc := json.NewEncoder(c)
|
||||
enc.Encode(s.cf)
|
||||
cf, err := util.SHA256(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
logrus.Debugf("%s\n%s\n%s\n%s\n", s.baseImageDigest, fsKey, cf, cmd)
|
||||
return util.SHA256(bytes.NewReader([]byte(s.baseImageDigest + fsKey + cf + cmd)))
|
||||
}
|
||||
|
||||
// extractCachedLayer will extract the cached layer and append it to the config file
|
||||
func (s *stageBuilder) extractCachedLayer(layer v1.Image, createdBy string) error {
|
||||
logrus.Infof("Found cached layer, extracting to filesystem")
|
||||
extractedFiles, err := util.GetFSFromImage(constants.RootDir, layer)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "extracting fs from image")
|
||||
}
|
||||
if _, err := s.snapshotter.TakeSnapshot(extractedFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Infof("Appending cached layer to base image")
|
||||
l, err := layer.Layers()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting cached layer from image")
|
||||
}
|
||||
s.image, err = mutate.Append(s.image,
|
||||
mutate.Addendum{
|
||||
Layer: l[0],
|
||||
History: v1.History{
|
||||
Author: constants.Author,
|
||||
CreatedBy: createdBy,
|
||||
},
|
||||
},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *stageBuilder) build(opts *config.KanikoOptions) error {
|
||||
// Unpack file system to root
|
||||
if _, err := util.GetFSFromImage(constants.RootDir, s.image); err != nil {
|
||||
return err
|
||||
}
|
||||
// Take initial snapshot
|
||||
if err := s.snapshotter.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
var volumes []string
|
||||
args := dockerfile.NewBuildArgs(opts.BuildArgs)
|
||||
for index, cmd := range s.stage.Commands {
|
||||
finalCmd := index == len(s.stage.Commands)-1
|
||||
command, err := commands.GetCommand(cmd, opts.SrcContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if err := util.GetFSFromImage(constants.RootDir, sourceImage); err != nil {
|
||||
return nil, err
|
||||
if command == nil {
|
||||
continue
|
||||
}
|
||||
l := snapshot.NewLayeredMap(hasher)
|
||||
snapshotter := snapshot.NewSnapshotter(l, constants.RootDir)
|
||||
// Take initial snapshot
|
||||
if err := snapshotter.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageConfig, err := util.RetrieveConfigFile(sourceImage)
|
||||
logrus.Info(command.String())
|
||||
cacheKey, err := s.key(command.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return errors.Wrap(err, "getting key")
|
||||
}
|
||||
if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buildArgs := dockerfile.NewBuildArgs(opts.BuildArgs)
|
||||
for index, cmd := range stage.Commands {
|
||||
finalCmd := index == len(stage.Commands)-1
|
||||
dockerCommand, err := commands.GetCommand(cmd, opts.SrcContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dockerCommand == nil {
|
||||
if command.CacheCommand() && opts.Cache {
|
||||
image, err := cache.RetrieveLayer(opts, cacheKey)
|
||||
if err == nil {
|
||||
if err := s.extractCachedLayer(image, command.String()); err != nil {
|
||||
return errors.Wrap(err, "extracting cached layer")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := dockerCommand.ExecuteCommand(&imageConfig.Config, buildArgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snapshotFiles := dockerCommand.FilesToSnapshot()
|
||||
var contents []byte
|
||||
logrus.Info("No cached layer found, executing command...")
|
||||
}
|
||||
if err := command.ExecuteCommand(&s.cf.Config, args); err != nil {
|
||||
return err
|
||||
}
|
||||
files := command.FilesToSnapshot()
|
||||
if cmd.Name() == constants.VolumeCmdName {
|
||||
volumes = append(volumes, files...)
|
||||
continue
|
||||
}
|
||||
var contents []byte
|
||||
|
||||
// If this is an intermediate stage, we only snapshot for the last command and we
|
||||
// want to snapshot the entire filesystem since we aren't tracking what was changed
|
||||
// by previous commands.
|
||||
if !finalStage {
|
||||
// If this is an intermediate stage, we only snapshot for the last command and we
|
||||
// want to snapshot the entire filesystem since we aren't tracking what was changed
|
||||
// by previous commands.
|
||||
if !s.stage.Final {
|
||||
if finalCmd {
|
||||
contents, err = s.snapshotter.TakeSnapshotFS()
|
||||
}
|
||||
} else {
|
||||
// If we are in single snapshot mode, we only take a snapshot once, after all
|
||||
// commands have completed.
|
||||
if opts.SingleSnapshot {
|
||||
if finalCmd {
|
||||
contents, err = snapshotter.TakeSnapshotFS()
|
||||
contents, err = s.snapshotter.TakeSnapshotFS()
|
||||
}
|
||||
} else {
|
||||
// If we are in single snapshot mode, we only take a snapshot once, after all
|
||||
// commands have completed.
|
||||
if opts.SingleSnapshot {
|
||||
if finalCmd {
|
||||
contents, err = snapshotter.TakeSnapshotFS()
|
||||
// Otherwise, in the final stage we take a snapshot at each command. If we know
|
||||
// the files that were changed, we'll snapshot those explicitly, otherwise we'll
|
||||
// check if anything in the filesystem changed.
|
||||
if files != nil {
|
||||
if len(files) > 0 {
|
||||
files = append(files, volumes...)
|
||||
volumes = []string{}
|
||||
}
|
||||
contents, err = s.snapshotter.TakeSnapshot(files)
|
||||
} else {
|
||||
// Otherwise, in the final stage we take a snapshot at each command. If we know
|
||||
// the files that were changed, we'll snapshot those explicitly, otherwise we'll
|
||||
// check if anything in the filesystem changed.
|
||||
if snapshotFiles != nil {
|
||||
contents, err = snapshotter.TakeSnapshot(snapshotFiles)
|
||||
} else {
|
||||
contents, err = snapshotter.TakeSnapshotFS()
|
||||
}
|
||||
contents, err = s.snapshotter.TakeSnapshotFS()
|
||||
volumes = []string{}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error taking snapshot of files for command %s: %s", dockerCommand, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error taking snapshot of files for command %s: %s", command, err)
|
||||
}
|
||||
|
||||
util.MoveVolumeWhitelistToWhitelist()
|
||||
if contents == nil {
|
||||
logrus.Info("No files were changed, appending empty layer to config. No layer added to image.")
|
||||
continue
|
||||
}
|
||||
// Append the layer to the image
|
||||
opener := func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(contents)), nil
|
||||
}
|
||||
layer, err := tarball.LayerFromOpener(opener)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sourceImage, err = mutate.Append(sourceImage,
|
||||
mutate.Addendum{
|
||||
Layer: layer,
|
||||
History: v1.History{
|
||||
Author: constants.Author,
|
||||
CreatedBy: dockerCommand.CreatedBy(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if contents == nil {
|
||||
logrus.Info("No files were changed, appending empty layer to config. No layer added to image.")
|
||||
continue
|
||||
}
|
||||
// Append the layer to the image
|
||||
opener := func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(contents)), nil
|
||||
}
|
||||
layer, err := tarball.LayerFromOpener(opener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Push layer to cache now along with new config file
|
||||
if command.CacheCommand() && opts.Cache {
|
||||
if err := pushLayerToCache(opts, cacheKey, layer, command.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sourceImage, err = mutate.Config(sourceImage, imageConfig.Config)
|
||||
s.image, err = mutate.Append(s.image,
|
||||
mutate.Addendum{
|
||||
Layer: layer,
|
||||
History: v1.History{
|
||||
Author: constants.Author,
|
||||
CreatedBy: command.String(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoBuild executes building the Dockerfile
|
||||
func DoBuild(opts *config.KanikoOptions) (v1.Image, error) {
|
||||
// Parse dockerfile and unpack base image to root
|
||||
stages, err := dockerfile.Stages(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for index, stage := range stages {
|
||||
sb, err := newStageBuilder(opts, stage)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("getting stage builder for stage %d", index))
|
||||
}
|
||||
if err := sb.build(opts); err != nil {
|
||||
return nil, errors.Wrap(err, "error building stage")
|
||||
}
|
||||
reviewConfig(stage, &sb.cf.Config)
|
||||
sourceImage, err := mutate.Config(sb.image, sb.cf.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if finalStage {
|
||||
if stage.Final {
|
||||
sourceImage, err = mutate.CreatedAt(sourceImage, v1.Time{Time: time.Now()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -161,9 +273,14 @@ func DoBuild(opts *options.KanikoOptions) (v1.Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opts.Cleanup {
|
||||
if err = util.DeleteFilesystem(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sourceImage, nil
|
||||
}
|
||||
if dockerfile.SaveStage(index, stages) {
|
||||
if stage.SaveStage {
|
||||
if err := saveStageAsTarball(index, sourceImage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -179,23 +296,14 @@ func DoBuild(opts *options.KanikoOptions) (v1.Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func finalStage(index int, target string, stages []instructions.Stage) bool {
|
||||
if index == len(stages)-1 {
|
||||
return true
|
||||
}
|
||||
if target == "" {
|
||||
return false
|
||||
}
|
||||
return target == stages[index].Name
|
||||
}
|
||||
|
||||
func extractImageToDependecyDir(index int, image v1.Image) error {
|
||||
dependencyDir := filepath.Join(constants.KanikoDir, strconv.Itoa(index))
|
||||
if err := os.MkdirAll(dependencyDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Infof("trying to extract to %s", dependencyDir)
|
||||
return util.GetFSFromImage(dependencyDir, image)
|
||||
_, err := util.GetFSFromImage(dependencyDir, image)
|
||||
return err
|
||||
}
|
||||
|
||||
func saveStageAsTarball(stageIndex int, image v1.Image) error {
|
||||
@@ -208,7 +316,7 @@ func saveStageAsTarball(stageIndex int, image v1.Image) error {
|
||||
}
|
||||
tarPath := filepath.Join(constants.KanikoIntermediateStagesDir, strconv.Itoa(stageIndex))
|
||||
logrus.Infof("Storing source image from stage %d at path %s", stageIndex, tarPath)
|
||||
return tarball.WriteToFile(tarPath, destRef, image, nil)
|
||||
return tarball.WriteToFile(tarPath, destRef, image)
|
||||
}
|
||||
|
||||
func getHasher(snapshotMode string) (func(string) (string, error), error) {
|
||||
@@ -222,7 +330,7 @@ func getHasher(snapshotMode string) (func(string) (string, error), error) {
|
||||
return nil, fmt.Errorf("%s is not a valid snapshot mode", snapshotMode)
|
||||
}
|
||||
|
||||
func resolveOnBuild(stage *instructions.Stage, config *v1.Config) error {
|
||||
func resolveOnBuild(stage *config.KanikoStage, config *v1.Config) error {
|
||||
if config.OnBuild == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -239,3 +347,23 @@ func resolveOnBuild(stage *instructions.Stage, config *v1.Config) error {
|
||||
config.OnBuild = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// reviewConfig makes sure the value of CMD is correct after building the stage
|
||||
// If ENTRYPOINT was set in this stage but CMD wasn't, then CMD should be cleared out
|
||||
// See Issue #346 for more info
|
||||
func reviewConfig(stage config.KanikoStage, config *v1.Config) {
|
||||
entrypoint := false
|
||||
cmd := false
|
||||
|
||||
for _, c := range stage.Commands {
|
||||
if c.Name() == constants.Cmd {
|
||||
cmd = true
|
||||
}
|
||||
if c.Name() == constants.Entrypoint {
|
||||
entrypoint = true
|
||||
}
|
||||
}
|
||||
if entrypoint && !cmd {
|
||||
config.Cmd = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
)
|
||||
|
||||
func Test_reviewConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dockerfile string
|
||||
originalCmd []string
|
||||
originalEntrypoint []string
|
||||
expectedCmd []string
|
||||
}{
|
||||
{
|
||||
name: "entrypoint and cmd declared",
|
||||
dockerfile: `
|
||||
FROM scratch
|
||||
CMD ["mycmd"]
|
||||
ENTRYPOINT ["myentrypoint"]`,
|
||||
originalEntrypoint: []string{"myentrypoint"},
|
||||
originalCmd: []string{"mycmd"},
|
||||
expectedCmd: []string{"mycmd"},
|
||||
},
|
||||
{
|
||||
name: "only entrypoint declared",
|
||||
dockerfile: `
|
||||
FROM scratch
|
||||
ENTRYPOINT ["myentrypoint"]`,
|
||||
originalEntrypoint: []string{"myentrypoint"},
|
||||
originalCmd: []string{"mycmd"},
|
||||
expectedCmd: nil,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
config := &v1.Config{
|
||||
Cmd: test.originalCmd,
|
||||
Entrypoint: test.originalEntrypoint,
|
||||
}
|
||||
reviewConfig(stage(t, test.dockerfile), config)
|
||||
testutil.CheckErrorAndDeepEqual(t, false, nil, test.expectedCmd, config.Cmd)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stage(t *testing.T, d string) config.KanikoStage {
|
||||
stages, err := dockerfile.Parse([]byte(d))
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing dockerfile: %v", err)
|
||||
}
|
||||
return config.KanikoStage{
|
||||
Stage: stages[0],
|
||||
}
|
||||
}
|
||||
+35
-5
@@ -21,12 +21,16 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/options"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/cache"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/version"
|
||||
"github.com/google/go-containerregistry/pkg/authn"
|
||||
"github.com/google/go-containerregistry/pkg/authn/k8schain"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||
"github.com/pkg/errors"
|
||||
@@ -43,7 +47,7 @@ func (w *withUserAgent) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
}
|
||||
|
||||
// DoPush is responsible for pushing image to the destinations specified in opts
|
||||
func DoPush(image v1.Image, opts *options.KanikoOptions) error {
|
||||
func DoPush(image v1.Image, opts *config.KanikoOptions) error {
|
||||
if opts.NoPush {
|
||||
logrus.Info("Skipping push to container registry due to --no-push flag")
|
||||
return nil
|
||||
@@ -62,7 +66,7 @@ func DoPush(image v1.Image, opts *options.KanikoOptions) error {
|
||||
for _, destRef := range destRefs {
|
||||
tagToImage[destRef] = image
|
||||
}
|
||||
return tarball.MultiWriteToFile(opts.TarPath, tagToImage, nil)
|
||||
return tarball.MultiWriteToFile(opts.TarPath, tagToImage)
|
||||
}
|
||||
|
||||
// continue pushing unless an error occurs
|
||||
@@ -87,16 +91,42 @@ func DoPush(image v1.Image, opts *options.KanikoOptions) error {
|
||||
|
||||
// Create a transport to set our user-agent.
|
||||
tr := http.DefaultTransport
|
||||
if opts.SkipTlsVerify {
|
||||
if opts.SkipTLSVerify {
|
||||
tr.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
rt := &withUserAgent{t: tr}
|
||||
|
||||
if err := remote.Write(destRef, image, pushAuth, rt, remote.WriteOptions{}); err != nil {
|
||||
if err := remote.Write(destRef, image, pushAuth, rt); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to push to destination %s", destRef))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pushLayerToCache pushes layer (tagged with cacheKey) to opts.Cache
|
||||
// if opts.Cache doesn't exist, infer the cache from the given destination
|
||||
func pushLayerToCache(opts *config.KanikoOptions, cacheKey string, layer v1.Layer, createdBy string) error {
|
||||
cache, err := cache.Destination(opts, cacheKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting cache destination")
|
||||
}
|
||||
logrus.Infof("Pushing layer %s to cache now", cache)
|
||||
empty := empty.Image
|
||||
empty, err = mutate.Append(empty,
|
||||
mutate.Addendum{
|
||||
Layer: layer,
|
||||
History: v1.History{
|
||||
Author: constants.Author,
|
||||
CreatedBy: createdBy,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "appending layer onto empty image")
|
||||
}
|
||||
return DoPush(empty, &config.KanikoOptions{
|
||||
Destinations: []string{cache},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,20 +17,28 @@ limitations under the License.
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
)
|
||||
|
||||
type LayeredMap struct {
|
||||
layers []map[string]string
|
||||
whiteouts []map[string]string
|
||||
added []map[string]string
|
||||
hasher func(string) (string, error)
|
||||
// cacheHasher doesn't include mtime in it's hash so that filesystem cache keys are stable
|
||||
cacheHasher func(string) (string, error)
|
||||
}
|
||||
|
||||
func NewLayeredMap(h func(string) (string, error)) *LayeredMap {
|
||||
func NewLayeredMap(h func(string) (string, error), c func(string) (string, error)) *LayeredMap {
|
||||
l := LayeredMap{
|
||||
hasher: h,
|
||||
hasher: h,
|
||||
cacheHasher: c,
|
||||
}
|
||||
l.layers = []map[string]string{}
|
||||
return &l
|
||||
@@ -39,8 +47,18 @@ func NewLayeredMap(h func(string) (string, error)) *LayeredMap {
|
||||
func (l *LayeredMap) Snapshot() {
|
||||
l.whiteouts = append(l.whiteouts, map[string]string{})
|
||||
l.layers = append(l.layers, map[string]string{})
|
||||
l.added = append(l.added, map[string]string{})
|
||||
}
|
||||
|
||||
// Key returns a hash for added files
|
||||
func (l *LayeredMap) Key() (string, error) {
|
||||
c := bytes.NewBuffer([]byte{})
|
||||
enc := json.NewEncoder(c)
|
||||
enc.Encode(l.added)
|
||||
return util.SHA256(c)
|
||||
}
|
||||
|
||||
// GetFlattenedPathsForWhiteOut returns all paths in the current FS
|
||||
func (l *LayeredMap) GetFlattenedPathsForWhiteOut() map[string]struct{} {
|
||||
paths := map[string]struct{}{}
|
||||
for _, l := range l.layers {
|
||||
@@ -85,11 +103,18 @@ func (l *LayeredMap) MaybeAddWhiteout(s string) (bool, error) {
|
||||
|
||||
// Add will add the specified file s to the layered map.
|
||||
func (l *LayeredMap) Add(s string) error {
|
||||
// Use hash function and add to layers
|
||||
newV, err := l.hasher(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating hash for %s: %s", s, err)
|
||||
return fmt.Errorf("Error creating hash for %s: %v", s, err)
|
||||
}
|
||||
l.layers[len(l.layers)-1][s] = newV
|
||||
// Use cache hash function and add to added
|
||||
cacheV, err := l.cacheHasher(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating cache hash for %s: %v", s, err)
|
||||
}
|
||||
l.added[len(l.added)-1][s] = cacheV
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 snapshot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_CacheKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
map1 map[string]string
|
||||
map2 map[string]string
|
||||
equal bool
|
||||
}{
|
||||
{
|
||||
name: "maps are the same",
|
||||
map1: map[string]string{
|
||||
"a": "apple",
|
||||
"b": "bat",
|
||||
"c": "cat",
|
||||
"d": "dog",
|
||||
"e": "egg",
|
||||
},
|
||||
map2: map[string]string{
|
||||
"c": "cat",
|
||||
"d": "dog",
|
||||
"b": "bat",
|
||||
"a": "apple",
|
||||
"e": "egg",
|
||||
},
|
||||
equal: true,
|
||||
},
|
||||
{
|
||||
name: "maps are different",
|
||||
map1: map[string]string{
|
||||
"a": "apple",
|
||||
"b": "bat",
|
||||
"c": "cat",
|
||||
},
|
||||
map2: map[string]string{
|
||||
"c": "",
|
||||
"b": "bat",
|
||||
"a": "apple",
|
||||
},
|
||||
equal: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
lm1 := LayeredMap{added: []map[string]string{test.map1}}
|
||||
lm2 := LayeredMap{added: []map[string]string{test.map2}}
|
||||
k1, err := lm1.Key()
|
||||
if err != nil {
|
||||
t.Fatalf("error getting key for map 1: %v", err)
|
||||
}
|
||||
k2, err := lm2.Key()
|
||||
if err != nil {
|
||||
t.Fatalf("error getting key for map 2: %v", err)
|
||||
}
|
||||
if test.equal != (k1 == k2) {
|
||||
t.Fatalf("unexpected result: \nExpected\n%s\nActual\n%s\n", k1, k2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+11
-38
@@ -25,7 +25,6 @@ import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -49,6 +48,11 @@ func (s *Snapshotter) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Key returns a string based on the current state of the file system
|
||||
func (s *Snapshotter) Key() (string, error) {
|
||||
return s.l.Key()
|
||||
}
|
||||
|
||||
// TakeSnapshot takes a snapshot of the specified files, avoiding directories in the whitelist, and creates
|
||||
// a tarball of the changed files. Return contents of the tarball, and whether or not any files were changed
|
||||
func (s *Snapshotter) TakeSnapshot(files []string) ([]byte, error) {
|
||||
@@ -79,21 +83,6 @@ func (s *Snapshotter) TakeSnapshotFS() ([]byte, error) {
|
||||
return contents, err
|
||||
}
|
||||
|
||||
func shouldSnapshot(file string, snapshottedFiles map[string]bool) (bool, error) {
|
||||
if val, ok := snapshottedFiles[file]; ok && val {
|
||||
return false, nil
|
||||
}
|
||||
whitelisted, err := util.CheckWhitelist(file)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error checking for %s in whitelist: %s", file, err)
|
||||
}
|
||||
if whitelisted && !isBuildFile(file) {
|
||||
logrus.Infof("Not adding %s to layer, as it's whitelisted", file)
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// snapshotFiles creates a snapshot (tar) and adds the specified files.
|
||||
// It will not add files which are whitelisted.
|
||||
func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) {
|
||||
@@ -102,7 +91,8 @@ func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) {
|
||||
logrus.Info("No files changed in this command, skipping snapshotting.")
|
||||
return false, nil
|
||||
}
|
||||
logrus.Infof("Taking snapshot of files %v...", files)
|
||||
logrus.Info("Taking snapshot of files...")
|
||||
logrus.Debugf("Taking snapshot of files %v", files)
|
||||
snapshottedFiles := make(map[string]bool)
|
||||
filesAdded := false
|
||||
|
||||
@@ -117,11 +107,7 @@ func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) {
|
||||
}
|
||||
for _, file := range parentDirs {
|
||||
file = filepath.Clean(file)
|
||||
shouldSnapshot, err := shouldSnapshot(file, snapshottedFiles)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error checking if parent dir %s can be snapshotted: %s", file, err)
|
||||
}
|
||||
if !shouldSnapshot {
|
||||
if val, ok := snapshottedFiles[file]; ok && val {
|
||||
continue
|
||||
}
|
||||
snapshottedFiles[file] = true
|
||||
@@ -142,19 +128,15 @@ func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) {
|
||||
// Next add the files themselves to the tar
|
||||
for _, file := range files {
|
||||
file = filepath.Clean(file)
|
||||
shouldSnapshot, err := shouldSnapshot(file, snapshottedFiles)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error checking if file %s can be snapshotted: %s", file, err)
|
||||
}
|
||||
if !shouldSnapshot {
|
||||
if val, ok := snapshottedFiles[file]; ok && val {
|
||||
continue
|
||||
}
|
||||
snapshottedFiles[file] = true
|
||||
|
||||
if err = s.l.Add(file); err != nil {
|
||||
if err := s.l.Add(file); err != nil {
|
||||
return false, fmt.Errorf("Unable to add file %s to layered map: %s", file, err)
|
||||
}
|
||||
if err = t.AddFileToTar(file); err != nil {
|
||||
if err := t.AddFileToTar(file); err != nil {
|
||||
return false, fmt.Errorf("Error adding file %s to tar: %s", file, err)
|
||||
}
|
||||
filesAdded = true
|
||||
@@ -162,15 +144,6 @@ func (s *Snapshotter) snapshotFiles(f io.Writer, files []string) (bool, error) {
|
||||
return filesAdded, nil
|
||||
}
|
||||
|
||||
func isBuildFile(file string) bool {
|
||||
for _, buildFile := range constants.KanikoBuildFiles {
|
||||
if file == buildFile {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// shapShotFS creates a snapshot (tar) of all files in the system which are not
|
||||
// whitelisted and which have changed.
|
||||
func (s *Snapshotter) snapShotFS(f io.Writer) (bool, error) {
|
||||
|
||||
@@ -198,7 +198,7 @@ func setUpTestDir() (string, *Snapshotter, error) {
|
||||
}
|
||||
|
||||
// Take the initial snapshot
|
||||
l := NewLayeredMap(util.Hasher())
|
||||
l := NewLayeredMap(util.Hasher(), util.CacheHasher())
|
||||
snapshotter := NewSnapshotter(l, testDir)
|
||||
if err := snapshotter.Init(); err != nil {
|
||||
return testDir, nil, errors.Wrap(err, "initializing snapshotter")
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
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 timing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// For testing
|
||||
var currentTimeFunc = time.Now
|
||||
|
||||
// DefaultRun is the default "singleton" TimedRun instance.
|
||||
var DefaultRun = NewTimedRun()
|
||||
|
||||
// TimedRun provides a running store of how long is spent in each category.
|
||||
type TimedRun struct {
|
||||
cl sync.Mutex
|
||||
categories map[string]time.Duration // protected by cl
|
||||
}
|
||||
|
||||
// Stop stops the specified timer and increments the time spent in that category.
|
||||
func (tr *TimedRun) Stop(t *Timer) {
|
||||
stop := currentTimeFunc()
|
||||
if _, ok := tr.categories[t.category]; !ok {
|
||||
tr.categories[t.category] = 0
|
||||
}
|
||||
fmt.Println(stop)
|
||||
tr.cl.Lock()
|
||||
defer tr.cl.Unlock()
|
||||
tr.categories[t.category] += stop.Sub(t.startTime)
|
||||
}
|
||||
|
||||
// Start starts a new Timer and returns it.
|
||||
func Start(category string) *Timer {
|
||||
t := Timer{
|
||||
category: category,
|
||||
startTime: currentTimeFunc(),
|
||||
}
|
||||
return &t
|
||||
}
|
||||
|
||||
// NewTimedRun returns an initialized TimedRun instance.
|
||||
func NewTimedRun() *TimedRun {
|
||||
tr := TimedRun{
|
||||
categories: map[string]time.Duration{},
|
||||
}
|
||||
return &tr
|
||||
}
|
||||
|
||||
// Timer represents a running timer.
|
||||
type Timer struct {
|
||||
category string
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// DefaultFormat is a default format string used by Summary.
|
||||
var DefaultFormat = template.Must(template.New("").Parse("{{range $c, $t := .}}{{$c}}: {{$t}}\n{{end}}"))
|
||||
|
||||
// Summary outputs a summary of the DefaultTimedRun.
|
||||
func Summary() string {
|
||||
return DefaultRun.Summary()
|
||||
}
|
||||
|
||||
// Summary outputs a summary of the specified TimedRun.
|
||||
func (tr *TimedRun) Summary() string {
|
||||
b := bytes.Buffer{}
|
||||
|
||||
tr.cl.Lock()
|
||||
defer tr.cl.Unlock()
|
||||
DefaultFormat.Execute(&b, tr.categories)
|
||||
return b.String()
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
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 timing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func patchTime(timeFunc func() time.Time) func() {
|
||||
old := currentTimeFunc
|
||||
currentTimeFunc = timeFunc
|
||||
return func() {
|
||||
currentTimeFunc = old
|
||||
}
|
||||
}
|
||||
|
||||
func mockTimeFunc(t time.Time) func() time.Time {
|
||||
return func() time.Time {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimedRun_StartStop(t *testing.T) {
|
||||
type args struct {
|
||||
categories map[string]time.Duration
|
||||
category string
|
||||
waitTime time.Duration
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want time.Duration
|
||||
}{
|
||||
{
|
||||
name: "new category",
|
||||
args: args{
|
||||
categories: map[string]time.Duration{},
|
||||
category: "foo",
|
||||
waitTime: 3 * time.Second,
|
||||
},
|
||||
want: 3 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "existing category",
|
||||
args: args{
|
||||
categories: map[string]time.Duration{
|
||||
"foo": 4 * time.Second,
|
||||
},
|
||||
category: "foo",
|
||||
waitTime: 2 * time.Second,
|
||||
},
|
||||
want: 6 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tr := &TimedRun{
|
||||
categories: tt.args.categories,
|
||||
}
|
||||
|
||||
timer := Timer{
|
||||
category: tt.args.category,
|
||||
startTime: time.Time{},
|
||||
}
|
||||
|
||||
defer patchTime(mockTimeFunc(timer.startTime.Add(tt.args.waitTime)))()
|
||||
tr.Stop(&timer)
|
||||
if got := tr.categories[tt.args.category]; got != tt.want {
|
||||
t.Errorf("Expected %d, got %d", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimedRun_Summary(t *testing.T) {
|
||||
type fields struct {
|
||||
categories map[string]time.Duration
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "single key",
|
||||
fields: fields{
|
||||
categories: map[string]time.Duration{
|
||||
"foo": 3 * time.Second,
|
||||
},
|
||||
},
|
||||
want: "foo: 3s\n",
|
||||
},
|
||||
{
|
||||
name: "two keys",
|
||||
fields: fields{
|
||||
categories: map[string]time.Duration{
|
||||
"foo": 3 * time.Second,
|
||||
"bar": 1 * time.Second,
|
||||
},
|
||||
},
|
||||
want: "bar: 1s\nfoo: 3s\n",
|
||||
},
|
||||
{
|
||||
name: "units",
|
||||
fields: fields{
|
||||
categories: map[string]time.Duration{
|
||||
"foo": 3 * time.Second,
|
||||
"bar": 1 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
want: "bar: 1ms\nfoo: 3s\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tr := &TimedRun{
|
||||
categories: tt.fields.categories,
|
||||
}
|
||||
if got := tr.Summary(); got != tt.want {
|
||||
t.Errorf("TimedRun.Summary() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,9 @@ limitations under the License.
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
)
|
||||
|
||||
func GetBucketAndItem(context string) (string, string) {
|
||||
|
||||
@@ -228,10 +228,7 @@ func IsSrcRemoteFileURL(rawurl string) bool {
|
||||
return false
|
||||
}
|
||||
_, err = http.Get(rawurl)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func UpdateConfigEnv(newEnvs []instructions.KeyValuePair, config *v1.Config, replacementEnvs []string) error {
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
)
|
||||
|
||||
var testUrl = "https://github.com/GoogleContainerTools/runtimes-common/blob/master/LICENSE"
|
||||
var testURL = "https://github.com/GoogleContainerTools/runtimes-common/blob/master/LICENSE"
|
||||
|
||||
var testEnvReplacement = []struct {
|
||||
path string
|
||||
@@ -220,7 +220,7 @@ var matchSourcesTests = []struct {
|
||||
srcs: []string{
|
||||
"pkg/*",
|
||||
"/root/dir?",
|
||||
testUrl,
|
||||
testURL,
|
||||
},
|
||||
files: []string{
|
||||
"pkg/a",
|
||||
@@ -234,7 +234,7 @@ var matchSourcesTests = []struct {
|
||||
"/root/dir1",
|
||||
"pkg/a",
|
||||
"pkg/b",
|
||||
testUrl,
|
||||
testURL,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -344,11 +344,11 @@ var isSrcValidTests = []struct {
|
||||
},
|
||||
{
|
||||
srcsAndDest: []string{
|
||||
testUrl,
|
||||
testURL,
|
||||
"dest",
|
||||
},
|
||||
resolvedSources: []string{
|
||||
testUrl,
|
||||
testURL,
|
||||
},
|
||||
shouldErr: false,
|
||||
},
|
||||
@@ -369,13 +369,13 @@ var testResolveSources = []struct {
|
||||
srcsAndDest: []string{
|
||||
"context/foo",
|
||||
"context/b*",
|
||||
testUrl,
|
||||
testURL,
|
||||
"dest/",
|
||||
},
|
||||
expectedList: []string{
|
||||
"context/foo",
|
||||
"context/bar",
|
||||
testUrl,
|
||||
testURL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+96
-62
@@ -34,31 +34,50 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var whitelist = []string{
|
||||
"/kaniko",
|
||||
// /var/run is a special case. It's common to mount in /var/run/docker.sock or something similar
|
||||
// which leads to a special mount on the /var/run/docker.sock file itself, but the directory to exist
|
||||
// in the image with no way to tell if it came from the base image or not.
|
||||
"/var/run",
|
||||
type WhitelistEntry struct {
|
||||
Path string
|
||||
PrefixMatchOnly bool
|
||||
}
|
||||
var volumeWhitelist = []string{}
|
||||
|
||||
func GetFSFromImage(root string, img v1.Image) error {
|
||||
var whitelist = []WhitelistEntry{
|
||||
{
|
||||
Path: "/kaniko",
|
||||
PrefixMatchOnly: false,
|
||||
},
|
||||
{
|
||||
// /var/run is a special case. It's common to mount in /var/run/docker.sock or something similar
|
||||
// which leads to a special mount on the /var/run/docker.sock file itself, but the directory to exist
|
||||
// in the image with no way to tell if it came from the base image or not.
|
||||
Path: "/var/run",
|
||||
PrefixMatchOnly: false,
|
||||
},
|
||||
{
|
||||
// similarly, we whitelist /etc/mtab, since there is no way to know if the file was mounted or came
|
||||
// from the base image
|
||||
Path: "/etc/mtab",
|
||||
PrefixMatchOnly: false,
|
||||
},
|
||||
}
|
||||
|
||||
// GetFSFromImage extracts the layers of img to root
|
||||
// It returns a list of all files extracted
|
||||
func GetFSFromImage(root string, img v1.Image) ([]string, error) {
|
||||
whitelist, err := fileSystemWhitelist(constants.WhitelistPath)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
logrus.Infof("Mounted directories: %v", whitelist)
|
||||
logrus.Debugf("Mounted directories: %v", whitelist)
|
||||
layers, err := img.Layers()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
extractedFiles := []string{}
|
||||
|
||||
for i, l := range layers {
|
||||
logrus.Infof("Extracting layer %d", i)
|
||||
r, err := l.Uncompressed()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
tr := tar.NewReader(r)
|
||||
for {
|
||||
@@ -67,7 +86,7 @@ func GetFSFromImage(root string, img v1.Image) error {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
path := filepath.Join(root, filepath.Clean(hdr.Name))
|
||||
base := filepath.Base(path)
|
||||
@@ -76,40 +95,23 @@ func GetFSFromImage(root string, img v1.Image) error {
|
||||
logrus.Debugf("Whiting out %s", path)
|
||||
name := strings.TrimPrefix(base, ".wh.")
|
||||
if err := os.RemoveAll(filepath.Join(dir, name)); err != nil {
|
||||
return errors.Wrapf(err, "removing whiteout %s", hdr.Name)
|
||||
return nil, errors.Wrapf(err, "removing whiteout %s", hdr.Name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
whitelisted, err := CheckWhitelist(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if whitelisted && !checkWhitelistRoot(root) {
|
||||
logrus.Debugf("Not adding %s because it is whitelisted", path)
|
||||
continue
|
||||
}
|
||||
if hdr.Typeflag == tar.TypeSymlink {
|
||||
whitelisted, err := CheckWhitelist(hdr.Linkname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if whitelisted {
|
||||
logrus.Debugf("skipping symlink from %s to %s because %s is whitelisted", hdr.Linkname, path, hdr.Linkname)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := extractFile(root, hdr, tr); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
extractedFiles = append(extractedFiles, filepath.Join(root, filepath.Clean(hdr.Name)))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return extractedFiles, nil
|
||||
}
|
||||
|
||||
// DeleteFilesystem deletes the extracted image file system
|
||||
func DeleteFilesystem() error {
|
||||
logrus.Info("Deleting filesystem...")
|
||||
err := filepath.Walk(constants.RootDir, func(path string, info os.FileInfo, err error) error {
|
||||
return filepath.Walk(constants.RootDir, func(path string, info os.FileInfo, _ error) error {
|
||||
whitelisted, err := CheckWhitelist(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -123,20 +125,19 @@ func DeleteFilesystem() error {
|
||||
}
|
||||
return os.RemoveAll(path)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ChildDirInWhitelist returns true if there is a child file or directory of the path in the whitelist
|
||||
func ChildDirInWhitelist(path, directory string) bool {
|
||||
for _, d := range constants.KanikoBuildFiles {
|
||||
dirPath := filepath.Join(directory, d)
|
||||
if HasFilepathPrefix(dirPath, path) {
|
||||
if HasFilepathPrefix(dirPath, path, false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, d := range whitelist {
|
||||
dirPath := filepath.Join(directory, d)
|
||||
if HasFilepathPrefix(dirPath, path) {
|
||||
dirPath := filepath.Join(directory, d.Path)
|
||||
if HasFilepathPrefix(dirPath, path, d.PrefixMatchOnly) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -170,6 +171,15 @@ func extractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
||||
mode := hdr.FileInfo().Mode()
|
||||
uid := hdr.Uid
|
||||
gid := hdr.Gid
|
||||
|
||||
whitelisted, err := CheckWhitelist(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if whitelisted && !checkWhitelistRoot(dest) {
|
||||
logrus.Debugf("Not adding %s because it is whitelisted", path)
|
||||
return nil
|
||||
}
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeReg:
|
||||
logrus.Debugf("creating file %s", path)
|
||||
@@ -180,6 +190,13 @@ func extractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Check if something already exists at path (symlinks etc.)
|
||||
// If so, delete it
|
||||
if FilepathExists(path) {
|
||||
if err := os.Remove(path); err != nil {
|
||||
return errors.Wrapf(err, "error removing %s to make way for new file.", path)
|
||||
}
|
||||
}
|
||||
currFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -195,7 +212,6 @@ func extractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
||||
return err
|
||||
}
|
||||
currFile.Close()
|
||||
|
||||
case tar.TypeDir:
|
||||
logrus.Debugf("creating dir %s", path)
|
||||
if err := os.MkdirAll(path, mode); err != nil {
|
||||
@@ -211,10 +227,26 @@ func extractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
||||
|
||||
case tar.TypeLink:
|
||||
logrus.Debugf("link from %s to %s", hdr.Linkname, path)
|
||||
whitelisted, err := CheckWhitelist(hdr.Linkname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if whitelisted {
|
||||
logrus.Debugf("skipping symlink from %s to %s because %s is whitelisted", hdr.Linkname, path, hdr.Linkname)
|
||||
return nil
|
||||
}
|
||||
// The base directory for a link may not exist before it is created.
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
// Check if something already exists at path
|
||||
// If so, delete it
|
||||
if FilepathExists(path) {
|
||||
if err := os.Remove(path); err != nil {
|
||||
return errors.Wrapf(err, "error removing %s to make way for new link", hdr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Link(filepath.Clean(filepath.Join("/", hdr.Linkname)), path); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -246,7 +278,7 @@ func CheckWhitelist(path string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
for _, wl := range whitelist {
|
||||
if HasFilepathPrefix(abs, wl) {
|
||||
if HasFilepathPrefix(abs, wl.Path, wl.PrefixMatchOnly) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
@@ -258,7 +290,7 @@ func checkWhitelistRoot(root string) bool {
|
||||
return false
|
||||
}
|
||||
for _, wl := range whitelist {
|
||||
if HasFilepathPrefix(root, wl) {
|
||||
if HasFilepathPrefix(root, wl.Path, wl.PrefixMatchOnly) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -271,7 +303,7 @@ func checkWhitelistRoot(root string) bool {
|
||||
// (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
|
||||
// Where (5) is the mount point relative to the process's root
|
||||
// From: https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
func fileSystemWhitelist(path string) ([]string, error) {
|
||||
func fileSystemWhitelist(path string) ([]WhitelistEntry, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -294,7 +326,10 @@ func fileSystemWhitelist(path string) ([]string, error) {
|
||||
}
|
||||
if lineArr[4] != constants.RootDir {
|
||||
logrus.Debugf("Appending %s from line: %s", lineArr[4], line)
|
||||
whitelist = append(whitelist, lineArr[4])
|
||||
whitelist = append(whitelist, WhitelistEntry{
|
||||
Path: lineArr[4],
|
||||
PrefixMatchOnly: false,
|
||||
})
|
||||
}
|
||||
if err == io.EOF {
|
||||
logrus.Debugf("Reached end of file %s", path)
|
||||
@@ -310,11 +345,14 @@ func RelativeFiles(fp string, root string) ([]string, error) {
|
||||
fullPath := filepath.Join(root, fp)
|
||||
logrus.Debugf("Getting files and contents at root %s", fullPath)
|
||||
err := filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
whitelisted, err := CheckWhitelist(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if whitelisted && !HasFilepathPrefix(path, root) {
|
||||
if whitelisted && !HasFilepathPrefix(path, root, false) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -377,22 +415,15 @@ func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid
|
||||
return dest.Chown(int(uid), int(gid))
|
||||
}
|
||||
|
||||
// AddPathToVolumeWhitelist adds the given path to the volume whitelist
|
||||
// It will get snapshotted when the VOLUME command is run then ignored
|
||||
// for subsequent commands.
|
||||
func AddPathToVolumeWhitelist(path string) error {
|
||||
logrus.Infof("adding %s to volume whitelist", path)
|
||||
volumeWhitelist = append(volumeWhitelist, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveVolumeWhitelistToWhitelist copies over all directories that were volume mounted
|
||||
// in this step to be whitelisted for all subsequent docker commands.
|
||||
func MoveVolumeWhitelistToWhitelist() error {
|
||||
if len(volumeWhitelist) > 0 {
|
||||
whitelist = append(whitelist, volumeWhitelist...)
|
||||
volumeWhitelist = []string{}
|
||||
}
|
||||
// AddVolumePathToWhitelist adds the given path to the whitelist with
|
||||
// PrefixMatchOnly set to true. Snapshotting will ignore paths prefixed
|
||||
// with the volume, but the volume itself will not be ignored.
|
||||
func AddVolumePathToWhitelist(path string) error {
|
||||
logrus.Infof("adding volume %s to whitelist", path)
|
||||
whitelist = append(whitelist, WhitelistEntry{
|
||||
Path: path,
|
||||
PrefixMatchOnly: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -492,7 +523,7 @@ func CopyFile(src, dest string) error {
|
||||
}
|
||||
|
||||
// HasFilepathPrefix checks if the given file path begins with prefix
|
||||
func HasFilepathPrefix(path, prefix string) bool {
|
||||
func HasFilepathPrefix(path, prefix string, prefixMatchOnly bool) bool {
|
||||
path = filepath.Clean(path)
|
||||
prefix = filepath.Clean(prefix)
|
||||
pathArray := strings.Split(path, "/")
|
||||
@@ -501,6 +532,9 @@ func HasFilepathPrefix(path, prefix string) bool {
|
||||
if len(pathArray) < len(prefixArray) {
|
||||
return false
|
||||
}
|
||||
if prefixMatchOnly && len(pathArray) == len(prefixArray) {
|
||||
return false
|
||||
}
|
||||
for index := range prefixArray {
|
||||
if prefixArray[index] == pathArray[index] {
|
||||
continue
|
||||
|
||||
+50
-23
@@ -50,9 +50,21 @@ func Test_fileSystemWhitelist(t *testing.T) {
|
||||
}
|
||||
|
||||
actualWhitelist, err := fileSystemWhitelist(path)
|
||||
expectedWhitelist := []string{"/kaniko", "/proc", "/dev", "/dev/pts", "/sys", "/var/run"}
|
||||
sort.Strings(actualWhitelist)
|
||||
sort.Strings(expectedWhitelist)
|
||||
expectedWhitelist := []WhitelistEntry{
|
||||
{"/kaniko", false},
|
||||
{"/proc", false},
|
||||
{"/dev", false},
|
||||
{"/dev/pts", false},
|
||||
{"/sys", false},
|
||||
{"/var/run", false},
|
||||
{"/etc/mtab", false},
|
||||
}
|
||||
sort.Slice(actualWhitelist, func(i, j int) bool {
|
||||
return actualWhitelist[i].Path < actualWhitelist[j].Path
|
||||
})
|
||||
sort.Slice(expectedWhitelist, func(i, j int) bool {
|
||||
return expectedWhitelist[i].Path < expectedWhitelist[j].Path
|
||||
})
|
||||
testutil.CheckErrorAndDeepEqual(t, false, err, expectedWhitelist, actualWhitelist)
|
||||
}
|
||||
|
||||
@@ -167,7 +179,7 @@ func Test_ParentDirectories(t *testing.T) {
|
||||
func Test_CheckWhitelist(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
whitelist []string
|
||||
whitelist []WhitelistEntry
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -178,7 +190,7 @@ func Test_CheckWhitelist(t *testing.T) {
|
||||
name: "file whitelisted",
|
||||
args: args{
|
||||
path: "/foo",
|
||||
whitelist: []string{"/foo"},
|
||||
whitelist: []WhitelistEntry{{"/foo", false}},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
@@ -186,7 +198,7 @@ func Test_CheckWhitelist(t *testing.T) {
|
||||
name: "directory whitelisted",
|
||||
args: args{
|
||||
path: "/foo/bar",
|
||||
whitelist: []string{"/foo"},
|
||||
whitelist: []WhitelistEntry{{"/foo", false}},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
@@ -194,7 +206,7 @@ func Test_CheckWhitelist(t *testing.T) {
|
||||
name: "grandparent whitelisted",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
whitelist: []string{"/foo"},
|
||||
whitelist: []WhitelistEntry{{"/foo", false}},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
@@ -202,7 +214,7 @@ func Test_CheckWhitelist(t *testing.T) {
|
||||
name: "sibling whitelisted",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
whitelist: []string{"/foo/bat"},
|
||||
whitelist: []WhitelistEntry{{"/foo/bat", false}},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
@@ -227,8 +239,9 @@ func Test_CheckWhitelist(t *testing.T) {
|
||||
|
||||
func TestHasFilepathPrefix(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
prefix string
|
||||
path string
|
||||
prefix string
|
||||
prefixMatchOnly bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -238,47 +251,61 @@ func TestHasFilepathPrefix(t *testing.T) {
|
||||
{
|
||||
name: "parent",
|
||||
args: args{
|
||||
path: "/foo/bar",
|
||||
prefix: "/foo",
|
||||
path: "/foo/bar",
|
||||
prefix: "/foo",
|
||||
prefixMatchOnly: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "nested parent",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
prefix: "/foo/bar",
|
||||
path: "/foo/bar/baz",
|
||||
prefix: "/foo/bar",
|
||||
prefixMatchOnly: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "sibling",
|
||||
args: args{
|
||||
path: "/foo/bar",
|
||||
prefix: "/bar",
|
||||
path: "/foo/bar",
|
||||
prefix: "/bar",
|
||||
prefixMatchOnly: false,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "nested sibling",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
prefix: "/foo/bar",
|
||||
path: "/foo/bar/baz",
|
||||
prefix: "/foo/bar",
|
||||
prefixMatchOnly: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "name prefix",
|
||||
args: args{
|
||||
path: "/foo2/bar",
|
||||
prefix: "/foo",
|
||||
path: "/foo2/bar",
|
||||
prefix: "/foo",
|
||||
prefixMatchOnly: false,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "prefix match only (volume)",
|
||||
args: args{
|
||||
path: "/foo",
|
||||
prefix: "/foo",
|
||||
prefixMatchOnly: true,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := HasFilepathPrefix(tt.args.path, tt.args.prefix); got != tt.want {
|
||||
if got := HasFilepathPrefix(tt.args.path, tt.args.prefix, tt.args.prefixMatchOnly); got != tt.want {
|
||||
t.Errorf("HasFilepathPrefix() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
@@ -343,8 +370,8 @@ func filesAreHardlinks(first, second string) checker {
|
||||
if err != nil {
|
||||
t.Fatalf("error getting file %s", second)
|
||||
}
|
||||
stat1 := getSyscallStat_t(fi1)
|
||||
stat2 := getSyscallStat_t(fi2)
|
||||
stat1 := getSyscallStatT(fi1)
|
||||
stat2 := getSyscallStatT(fi2)
|
||||
if stat1.Ino != stat2.Ino {
|
||||
t.Errorf("%s and %s aren't hardlinks as they dont' have the same inode", first, second)
|
||||
}
|
||||
|
||||
+8
-12
@@ -25,11 +25,12 @@ import (
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
)
|
||||
|
||||
@@ -40,9 +41,8 @@ var (
|
||||
)
|
||||
|
||||
// RetrieveSourceImage returns the base image of the stage at index
|
||||
func RetrieveSourceImage(index int, buildArgs []string, stages []instructions.Stage) (v1.Image, error) {
|
||||
currentStage := stages[index]
|
||||
currentBaseName, err := ResolveEnvironmentReplacement(currentStage.BaseName, buildArgs, false)
|
||||
func RetrieveSourceImage(stage config.KanikoStage, buildArgs []string) (v1.Image, error) {
|
||||
currentBaseName, err := ResolveEnvironmentReplacement(stage.BaseName, buildArgs, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -53,20 +53,16 @@ func RetrieveSourceImage(index int, buildArgs []string, stages []instructions.St
|
||||
}
|
||||
// Next, check if the base image of the current stage is built from a previous stage
|
||||
// If so, retrieve the image from the stored tarball
|
||||
for i, stage := range stages {
|
||||
if i > index {
|
||||
continue
|
||||
}
|
||||
if stage.Name == currentBaseName {
|
||||
return retrieveTarImage(i)
|
||||
}
|
||||
if stage.BaseImageStoredLocally {
|
||||
return retrieveTarImage(stage.BaseImageIndex)
|
||||
}
|
||||
|
||||
// Otherwise, initialize image as usual
|
||||
return retrieveRemoteImage(currentBaseName)
|
||||
}
|
||||
|
||||
// RetrieveConfigFile returns the config file for an image
|
||||
func RetrieveConfigFile(sourceImage v1.Image) (*v1.ConfigFile, error) {
|
||||
func RetrieveConfigFile(sourceImage partial.WithConfigFile) (*v1.ConfigFile, error) {
|
||||
imageConfig, err := sourceImage.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/empty"
|
||||
@@ -54,7 +55,9 @@ func Test_StandardImage(t *testing.T) {
|
||||
return nil, nil
|
||||
}
|
||||
retrieveRemoteImage = mock
|
||||
actual, err := RetrieveSourceImage(0, nil, stages)
|
||||
actual, err := RetrieveSourceImage(config.KanikoStage{
|
||||
Stage: stages[0],
|
||||
}, nil)
|
||||
testutil.CheckErrorAndDeepEqual(t, false, err, nil, actual)
|
||||
}
|
||||
func Test_ScratchImage(t *testing.T) {
|
||||
@@ -62,7 +65,9 @@ func Test_ScratchImage(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
actual, err := RetrieveSourceImage(1, nil, stages)
|
||||
actual, err := RetrieveSourceImage(config.KanikoStage{
|
||||
Stage: stages[1],
|
||||
}, nil)
|
||||
expected := empty.Image
|
||||
testutil.CheckErrorAndDeepEqual(t, false, err, expected, actual)
|
||||
}
|
||||
@@ -80,7 +85,11 @@ func Test_TarImage(t *testing.T) {
|
||||
return nil, nil
|
||||
}
|
||||
retrieveTarImage = mock
|
||||
actual, err := RetrieveSourceImage(2, nil, stages)
|
||||
actual, err := RetrieveSourceImage(config.KanikoStage{
|
||||
BaseImageStoredLocally: true,
|
||||
BaseImageIndex: 0,
|
||||
Stage: stages[2],
|
||||
}, nil)
|
||||
testutil.CheckErrorAndDeepEqual(t, false, err, nil, actual)
|
||||
}
|
||||
|
||||
|
||||
+5
-10
@@ -118,7 +118,7 @@ func (t *Tar) Whiteout(p string) error {
|
||||
func (t *Tar) checkHardlink(p string, i os.FileInfo) (bool, string) {
|
||||
hardlink := false
|
||||
linkDst := ""
|
||||
stat := getSyscallStat_t(i)
|
||||
stat := getSyscallStatT(i)
|
||||
if stat != nil {
|
||||
nlinks := stat.Nlink
|
||||
if nlinks > 1 {
|
||||
@@ -135,7 +135,7 @@ func (t *Tar) checkHardlink(p string, i os.FileInfo) (bool, string) {
|
||||
return hardlink, linkDst
|
||||
}
|
||||
|
||||
func getSyscallStat_t(i os.FileInfo) *syscall.Stat_t {
|
||||
func getSyscallStatT(i os.FileInfo) *syscall.Stat_t {
|
||||
if sys := i.Sys(); sys != nil {
|
||||
if stat, ok := sys.(*syscall.Stat_t); ok {
|
||||
return stat
|
||||
@@ -195,10 +195,10 @@ func fileIsCompressedTar(src string) (bool, archive.Compression) {
|
||||
|
||||
func fileIsUncompressedTar(src string) bool {
|
||||
r, err := os.Open(src)
|
||||
defer r.Close()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer r.Close()
|
||||
fi, err := os.Lstat(src)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -210,13 +210,8 @@ func fileIsUncompressedTar(src string) bool {
|
||||
if tr == nil {
|
||||
return false
|
||||
}
|
||||
for {
|
||||
_, err := tr.Next()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
_, err = tr.Next()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// UnpackCompressedTar unpacks the compressed tar at path to dir
|
||||
|
||||
@@ -18,6 +18,7 @@ package util
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
@@ -72,6 +73,36 @@ func Hasher() func(string) (string, error) {
|
||||
return hasher
|
||||
}
|
||||
|
||||
// CacheHasher takes into account everything the regular hasher does except for mtime
|
||||
func CacheHasher() func(string) (string, error) {
|
||||
hasher := func(p string) (string, error) {
|
||||
h := md5.New()
|
||||
fi, err := os.Lstat(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h.Write([]byte(fi.Mode().String()))
|
||||
|
||||
h.Write([]byte(strconv.FormatUint(uint64(fi.Sys().(*syscall.Stat_t).Uid), 36)))
|
||||
h.Write([]byte(","))
|
||||
h.Write([]byte(strconv.FormatUint(uint64(fi.Sys().(*syscall.Stat_t).Gid), 36)))
|
||||
|
||||
if fi.Mode().IsRegular() {
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
return hasher
|
||||
}
|
||||
|
||||
// MtimeHasher returns a hash function, which only looks at mtime to determine if a file has changed.
|
||||
// Note that the mtime can lag, so it's possible that a file will have changed but the mtime may look the same.
|
||||
func MtimeHasher() func(string) (string, error) {
|
||||
@@ -86,3 +117,13 @@ func MtimeHasher() func(string) (string, error) {
|
||||
}
|
||||
return hasher
|
||||
}
|
||||
|
||||
// SHA256 returns the shasum of the contents of r
|
||||
func SHA256(r io.Reader) (string, error) {
|
||||
hasher := sha256.New()
|
||||
_, err := io.Copy(hasher, r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(hasher.Sum(make([]byte, 0, hasher.Size()))), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user