mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-18 16:06:02 +02:00
Fixed merge conflicts, support multistage builds
This commit is contained in:
+3
-2
@@ -20,8 +20,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -40,7 +41,7 @@ type AddCommand struct {
|
||||
// - If dest doesn't end with a slash, the filepath is inferred to be <dest>/<filename>
|
||||
// 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 *manifest.Schema2Config) error {
|
||||
func (a *AddCommand) ExecuteCommand(config *v1.Config) error {
|
||||
srcs := a.cmd.SourcesAndDest[:len(a.cmd.SourcesAndDest)-1]
|
||||
dest := a.cmd.SourcesAndDest[len(a.cmd.SourcesAndDest)-1]
|
||||
|
||||
|
||||
+5
-4
@@ -17,10 +17,11 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type CmdCommand struct {
|
||||
@@ -29,7 +30,7 @@ type CmdCommand struct {
|
||||
|
||||
// ExecuteCommand executes the CMD command
|
||||
// Argument handling is the same as RUN.
|
||||
func (c *CmdCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (c *CmdCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: CMD")
|
||||
var newCommand []string
|
||||
if c.cmd.PrependShell {
|
||||
|
||||
@@ -18,32 +18,32 @@ package commands
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/pkg/strslice"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
)
|
||||
|
||||
var cmdTests = []struct {
|
||||
prependShell bool
|
||||
cmdLine []string
|
||||
expectedCmd strslice.StrSlice
|
||||
expectedCmd []string
|
||||
}{
|
||||
{
|
||||
prependShell: true,
|
||||
cmdLine: []string{"echo", "cmd1"},
|
||||
expectedCmd: strslice.StrSlice{"/bin/sh", "-c", "echo cmd1"},
|
||||
expectedCmd: []string{"/bin/sh", "-c", "echo cmd1"},
|
||||
},
|
||||
{
|
||||
prependShell: false,
|
||||
cmdLine: []string{"echo", "cmd2"},
|
||||
expectedCmd: strslice.StrSlice{"echo", "cmd2"},
|
||||
expectedCmd: []string{"echo", "cmd2"},
|
||||
},
|
||||
}
|
||||
|
||||
func TestExecuteCmd(t *testing.T) {
|
||||
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
Cmd: nil,
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -28,7 +28,7 @@ type DockerCommand interface {
|
||||
// 1. Making required changes to the filesystem (ex. copying files for ADD/COPY or setting ENV variables)
|
||||
// 2. Updating metadata fields in the config
|
||||
// It should not change the config history.
|
||||
ExecuteCommand(*manifest.Schema2Config) error
|
||||
ExecuteCommand(*v1.Config) error
|
||||
// The config history has a "created by" field, should return information about the command
|
||||
CreatedBy() string
|
||||
// A list of files to snapshot, empty for metadata commands or nil if we don't know
|
||||
|
||||
@@ -18,13 +18,14 @@ package commands
|
||||
|
||||
import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type CopyCommand struct {
|
||||
@@ -33,7 +34,7 @@ type CopyCommand struct {
|
||||
snapshotFiles []string
|
||||
}
|
||||
|
||||
func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (c *CopyCommand) ExecuteCommand(config *v1.Config) error {
|
||||
srcs := c.cmd.SourcesAndDest[:len(c.cmd.SourcesAndDest)-1]
|
||||
dest := c.cmd.SourcesAndDest[len(c.cmd.SourcesAndDest)-1]
|
||||
|
||||
@@ -44,7 +45,6 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
if c.cmd.From != "" {
|
||||
c.buildcontext = filepath.Join(constants.BuildContextDir, c.cmd.From)
|
||||
}
|
||||
|
||||
// First, resolve any environment replacement
|
||||
resolvedEnvs, err := util.ResolveEnvironmentReplacementList(c.cmd.SourcesAndDest, config.Env, true)
|
||||
if err != nil {
|
||||
@@ -67,7 +67,7 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
if cwd == "" {
|
||||
cwd = constants.RootDir
|
||||
}
|
||||
destPath, err := util.DestinationFilepath(src, dest, cwd)
|
||||
destPath, err := util.DestinationFilepath(src, dest, config.WorkingDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,10 +17,11 @@ limitations under the License.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type EntrypointCommand struct {
|
||||
@@ -28,7 +29,7 @@ type EntrypointCommand struct {
|
||||
}
|
||||
|
||||
// ExecuteCommand handles command processing similar to CMD and RUN,
|
||||
func (e *EntrypointCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (e *EntrypointCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: ENTRYPOINT")
|
||||
var newCommand []string
|
||||
if e.cmd.PrependShell {
|
||||
|
||||
@@ -19,31 +19,30 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/pkg/strslice"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
)
|
||||
|
||||
var entrypointTests = []struct {
|
||||
prependShell bool
|
||||
cmdLine []string
|
||||
expectedCmd strslice.StrSlice
|
||||
expectedCmd []string
|
||||
}{
|
||||
{
|
||||
prependShell: true,
|
||||
cmdLine: []string{"echo", "cmd1"},
|
||||
expectedCmd: strslice.StrSlice{"/bin/sh", "-c", "echo cmd1"},
|
||||
expectedCmd: []string{"/bin/sh", "-c", "echo cmd1"},
|
||||
},
|
||||
{
|
||||
prependShell: false,
|
||||
cmdLine: []string{"echo", "cmd2"},
|
||||
expectedCmd: strslice.StrSlice{"echo", "cmd2"},
|
||||
expectedCmd: []string{"echo", "cmd2"},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEntrypointExecuteCmd(t *testing.T) {
|
||||
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
Cmd: nil,
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -20,8 +20,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewEnvCommand(cmd *instructions.EnvCommand) EnvCommand {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EnvCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (e *EnvCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: ENV")
|
||||
newEnvs := e.cmd.Env
|
||||
for index, pair := range newEnvs {
|
||||
@@ -55,7 +55,7 @@ func (e *EnvCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
return updateConfigEnv(newEnvs, config)
|
||||
}
|
||||
|
||||
func updateConfigEnv(newEnvs []instructions.KeyValuePair, config *manifest.Schema2Config) error {
|
||||
func updateConfigEnv(newEnvs []instructions.KeyValuePair, config *v1.Config) error {
|
||||
// First, convert config.Env array to []instruction.KeyValuePair
|
||||
var kvps []instructions.KeyValuePair
|
||||
for _, env := range config.Env {
|
||||
|
||||
@@ -19,12 +19,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
)
|
||||
|
||||
func TestUpdateEnvConfig(t *testing.T) {
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
Env: []string{
|
||||
"PATH=/path/to/dir",
|
||||
"hey=hey",
|
||||
@@ -55,7 +55,7 @@ func TestUpdateEnvConfig(t *testing.T) {
|
||||
testutil.CheckErrorAndDeepEqual(t, false, nil, expectedEnvArray, cfg.Env)
|
||||
}
|
||||
func Test_EnvExecute(t *testing.T) {
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
Env: []string{
|
||||
"path=/usr/",
|
||||
"home=/root",
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -30,12 +30,12 @@ type ExposeCommand struct {
|
||||
cmd *instructions.ExposeCommand
|
||||
}
|
||||
|
||||
func (r *ExposeCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (r *ExposeCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: EXPOSE")
|
||||
// Grab the currently exposed ports
|
||||
existingPorts := config.ExposedPorts
|
||||
if existingPorts == nil {
|
||||
existingPorts = make(map[manifest.Schema2Port]struct{})
|
||||
existingPorts = make(map[string]struct{})
|
||||
}
|
||||
// Add any new ones in
|
||||
for _, p := range r.cmd.Ports {
|
||||
@@ -53,8 +53,7 @@ func (r *ExposeCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
return fmt.Errorf("Invalid protocol: %s", protocol)
|
||||
}
|
||||
logrus.Infof("Adding exposed port: %s", p)
|
||||
var x struct{}
|
||||
existingPorts[manifest.Schema2Port(p)] = x
|
||||
existingPorts[p] = struct{}{}
|
||||
}
|
||||
config.ExposedPorts = existingPorts
|
||||
return nil
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
)
|
||||
|
||||
func TestUpdateExposedPorts(t *testing.T) {
|
||||
cfg := &manifest.Schema2Config{
|
||||
ExposedPorts: manifest.Schema2PortSet{
|
||||
cfg := &v1.Config{
|
||||
ExposedPorts: map[string]struct{}{
|
||||
"8080/tcp": {},
|
||||
},
|
||||
Env: []string{
|
||||
@@ -51,7 +51,7 @@ func TestUpdateExposedPorts(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
expectedPorts := manifest.Schema2PortSet{
|
||||
expectedPorts := map[string]struct{}{
|
||||
"8080/tcp": {},
|
||||
"8081/tcp": {},
|
||||
"8082/tcp": {},
|
||||
@@ -66,8 +66,8 @@ func TestUpdateExposedPorts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInvalidProtocol(t *testing.T) {
|
||||
cfg := &manifest.Schema2Config{
|
||||
ExposedPorts: manifest.Schema2PortSet{},
|
||||
cfg := &v1.Config{
|
||||
ExposedPorts: map[string]struct{}{},
|
||||
}
|
||||
|
||||
ports := []string{
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -29,12 +29,12 @@ type LabelCommand struct {
|
||||
cmd *instructions.LabelCommand
|
||||
}
|
||||
|
||||
func (r *LabelCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (r *LabelCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: LABEL")
|
||||
return updateLabels(r.cmd.Labels, config)
|
||||
}
|
||||
|
||||
func updateLabels(labels []instructions.KeyValuePair, config *manifest.Schema2Config) error {
|
||||
func updateLabels(labels []instructions.KeyValuePair, config *v1.Config) error {
|
||||
existingLabels := config.Labels
|
||||
if existingLabels == nil {
|
||||
existingLabels = make(map[string]string)
|
||||
|
||||
@@ -20,12 +20,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
)
|
||||
|
||||
func TestUpdateLabels(t *testing.T) {
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
|
||||
@@ -18,8 +18,8 @@ package commands
|
||||
|
||||
import (
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ type OnBuildCommand struct {
|
||||
}
|
||||
|
||||
//ExecuteCommand adds the specified expression in Onbuild to the config
|
||||
func (o *OnBuildCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (o *OnBuildCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: ONBUILD")
|
||||
logrus.Infof("args: %s", o.cmd.Expression)
|
||||
resolvedExpression, err := util.ResolveEnvironmentReplacement(o.cmd.Expression, config.Env, false)
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
)
|
||||
|
||||
var onbuildTests = []struct {
|
||||
@@ -50,7 +50,7 @@ var onbuildTests = []struct {
|
||||
|
||||
func TestExecuteOnbuild(t *testing.T) {
|
||||
for _, test := range onbuildTests {
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
Env: []string{
|
||||
"dir=/some/dir",
|
||||
},
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ type RunCommand struct {
|
||||
cmd *instructions.RunCommand
|
||||
}
|
||||
|
||||
func (r *RunCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (r *RunCommand) ExecuteCommand(config *v1.Config) error {
|
||||
var newCommand []string
|
||||
if r.cmd.PrependShell {
|
||||
// This is the default shell on Linux
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -30,7 +30,7 @@ type UserCommand struct {
|
||||
cmd *instructions.UserCommand
|
||||
}
|
||||
|
||||
func (r *UserCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (r *UserCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: USER")
|
||||
u := r.cmd.User
|
||||
userAndGroup := strings.Split(u, ":")
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
)
|
||||
|
||||
var userTests = []struct {
|
||||
@@ -82,7 +82,7 @@ var userTests = []struct {
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
for _, test := range userTests {
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
Env: []string{
|
||||
"envuser=root",
|
||||
"envgroup=root",
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ type VolumeCommand struct {
|
||||
snapshotFiles []string
|
||||
}
|
||||
|
||||
func (v *VolumeCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (v *VolumeCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: VOLUME")
|
||||
volumes := v.cmd.Volumes
|
||||
resolvedVolumes, err := util.ResolveEnvironmentReplacementList(volumes, config.Env, true)
|
||||
|
||||
@@ -19,12 +19,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
)
|
||||
|
||||
func TestUpdateVolume(t *testing.T) {
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
Env: []string{
|
||||
"VOLUME=/etc",
|
||||
},
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ type WorkdirCommand struct {
|
||||
snapshotFiles []string
|
||||
}
|
||||
|
||||
func (w *WorkdirCommand) ExecuteCommand(config *manifest.Schema2Config) error {
|
||||
func (w *WorkdirCommand) ExecuteCommand(config *v1.Config) error {
|
||||
logrus.Info("cmd: workdir")
|
||||
workdirPath := w.cmd.Path
|
||||
resolvedWorkingDir, err := util.ResolveEnvironmentReplacement(workdirPath, config.Env, true)
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
)
|
||||
|
||||
// Each test here changes the same WorkingDir field in the config
|
||||
@@ -63,7 +63,7 @@ var workdirTests = []struct {
|
||||
|
||||
func TestWorkdirCommand(t *testing.T) {
|
||||
|
||||
cfg := &manifest.Schema2Config{
|
||||
cfg := &v1.Config{
|
||||
WorkingDir: "/",
|
||||
Env: []string{
|
||||
"path=usr/",
|
||||
|
||||
@@ -20,10 +20,15 @@ import (
|
||||
"bytes"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/commands"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/image"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/docker/docker/builder/dockerfile/parser"
|
||||
"github.com/google/go-containerregistry/authn"
|
||||
"github.com/google/go-containerregistry/name"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/google/go-containerregistry/v1/empty"
|
||||
"github.com/google/go-containerregistry/v1/remote"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -86,7 +91,26 @@ func Dependencies(index int, stages []instructions.Stage) ([]string, error) {
|
||||
if stageIndex <= index {
|
||||
continue
|
||||
}
|
||||
ms, err := image.NewSourceImage(stage.BaseName)
|
||||
var sourceImage v1.Image
|
||||
if stage.BaseName == constants.NoBaseImage {
|
||||
sourceImage = empty.Image
|
||||
} else {
|
||||
// Initialize source image
|
||||
ref, err := name.ParseReference(stage.BaseName, name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
auth, err := authn.DefaultKeychain.Resolve(ref.Context().Registry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sourceImage, err = remote.Image(ref, auth, http.DefaultTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
imageConfig, err := sourceImage.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -94,7 +118,7 @@ func Dependencies(index int, stages []instructions.Stage) ([]string, error) {
|
||||
switch c := cmd.(type) {
|
||||
case *instructions.EnvCommand:
|
||||
envCommand := commands.NewEnvCommand(c)
|
||||
if err := envCommand.ExecuteCommand(ms.Config()); err != nil {
|
||||
if err := envCommand.ExecuteCommand(&imageConfig.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *instructions.CopyCommand:
|
||||
@@ -102,7 +126,7 @@ func Dependencies(index int, stages []instructions.Stage) ([]string, error) {
|
||||
continue
|
||||
}
|
||||
// First, resolve any environment replacement
|
||||
resolvedEnvs, err := util.ResolveEnvironmentReplacementList(c.SourcesAndDest, ms.Config().Env, true)
|
||||
resolvedEnvs, err := util.ResolveEnvironmentReplacementList(c.SourcesAndDest, imageConfig.Config.Env, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -27,18 +27,17 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var dockerfile = `
|
||||
FROM scratch
|
||||
RUN echo hi > /hi
|
||||
|
||||
FROM scratch AS second
|
||||
COPY --from=0 /hi /hi2
|
||||
|
||||
FROM scratch
|
||||
COPY --from=second /hi2 /hi3
|
||||
`
|
||||
|
||||
func Test_ResolveStages(t *testing.T) {
|
||||
dockerfile := `
|
||||
FROM scratch
|
||||
RUN echo hi > /hi
|
||||
|
||||
FROM scratch AS second
|
||||
COPY --from=0 /hi /hi2
|
||||
|
||||
FROM scratch
|
||||
COPY --from=second /hi2 /hi3
|
||||
`
|
||||
stages, err := Parse([]byte(dockerfile))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
+98
-39
@@ -17,78 +17,103 @@ limitations under the License.
|
||||
package executor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
img "github.com/GoogleContainerTools/container-diff/pkg/image"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/commands"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/image"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/snapshot"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/go-containerregistry/v1/empty"
|
||||
|
||||
"github.com/google/go-containerregistry/v1/tarball"
|
||||
|
||||
"github.com/google/go-containerregistry/authn"
|
||||
"github.com/google/go-containerregistry/name"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
"github.com/google/go-containerregistry/v1/mutate"
|
||||
"github.com/google/go-containerregistry/v1/remote"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/commands"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||
"github.com/docker/docker/builder/dockerfile/instructions"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// DoBuild executes each stage of the build
|
||||
func DoBuild(dockerfilePath, srcContext, snapshotMode string) (*img.MutableSource, error) {
|
||||
func DoBuild(dockerfilePath, srcContext, snapshotMode string) (name.Reference, v1.Image, error) {
|
||||
// Parse dockerfile and unpack base image to root
|
||||
d, err := ioutil.ReadFile(dockerfilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
stages, err := dockerfile.Parse(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
dockerfile.ResolveStages(stages)
|
||||
|
||||
hasher, err := getHasher(snapshotMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for index, stage := range stages {
|
||||
baseImage := stage.BaseName
|
||||
finalStage := index == len(stages)-1
|
||||
// Unpack file system to root
|
||||
logrus.Infof("Unpacking filesystem of %s...", baseImage)
|
||||
if err := util.ExtractFileSystemFromImage(baseImage); err != nil {
|
||||
return nil, err
|
||||
var sourceImage v1.Image
|
||||
var ref name.Reference
|
||||
if baseImage == constants.NoBaseImage {
|
||||
logrus.Info("No base image, nothing to extract")
|
||||
sourceImage = empty.Image
|
||||
} else {
|
||||
// Initialize source image
|
||||
ref, err = name.ParseReference(baseImage, name.WeakValidation)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
auth, err := authn.DefaultKeychain.Resolve(ref.Context().Registry)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sourceImage, err = remote.Image(ref, auth, http.DefaultTransport)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if err := util.GetFSFromImage(sourceImage); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
l := snapshot.NewLayeredMap(hasher)
|
||||
snapshotter := snapshot.NewSnapshotter(l, constants.RootDir)
|
||||
|
||||
// Take initial snapshot
|
||||
if err := snapshotter.Init(); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// Initialize source image
|
||||
sourceImage, err := image.NewSourceImage(baseImage)
|
||||
imageConfig, err := sourceImage.ConfigFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
imageConfig := sourceImage.Config()
|
||||
if err := resolveOnBuild(&stage, imageConfig); err != nil {
|
||||
return nil, err
|
||||
if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, cmd := range stage.Commands {
|
||||
dockerCommand, err := commands.GetCommand(cmd, srcContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if dockerCommand == nil {
|
||||
continue
|
||||
}
|
||||
if err := dockerCommand.ExecuteCommand(imageConfig); err != nil {
|
||||
return nil, err
|
||||
if err := dockerCommand.ExecuteCommand(&imageConfig.Config); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !finalStage {
|
||||
continue
|
||||
@@ -97,33 +122,67 @@ func DoBuild(dockerfilePath, srcContext, snapshotMode string) (*img.MutableSourc
|
||||
snapshotFiles := dockerCommand.FilesToSnapshot()
|
||||
contents, err := snapshotter.TakeSnapshot(snapshotFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
util.MoveVolumeWhitelistToWhitelist()
|
||||
if contents == nil {
|
||||
logrus.Info("No files were changed, appending empty layer to config.")
|
||||
sourceImage.AppendConfigHistory(constants.Author, true)
|
||||
continue
|
||||
}
|
||||
// Append the layer to the image
|
||||
if err := sourceImage.AppendLayer(contents, constants.Author); err != nil {
|
||||
return nil, err
|
||||
opener := func() (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(contents)), nil
|
||||
}
|
||||
layer, err := tarball.LayerFromOpener(opener)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sourceImage, err = mutate.Append(sourceImage,
|
||||
mutate.Addendum{
|
||||
Layer: layer,
|
||||
History: v1.History{
|
||||
Author: constants.Author,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if finalStage {
|
||||
return sourceImage, nil
|
||||
sourceImage, err = mutate.Config(sourceImage, imageConfig.Config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return ref, sourceImage, nil
|
||||
}
|
||||
if err := saveStageDependencies(index, stages); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// Delete the filesystem
|
||||
if err := util.DeleteFilesystem(); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
func DoPush(ref name.Reference, image v1.Image, destination string) error {
|
||||
// Push the image
|
||||
destRef, err := name.ParseReference(destination, name.WeakValidation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wo := remote.WriteOptions{}
|
||||
if ref != nil {
|
||||
wo.MountPaths = []name.Repository{ref.Context()}
|
||||
}
|
||||
pushAuth, err := authn.DefaultKeychain.Resolve(destRef.Context().Registry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return remote.Write(destRef, image, pushAuth, http.DefaultTransport, wo)
|
||||
}
|
||||
func saveStageDependencies(index int, stages []instructions.Stage) error {
|
||||
// First, get the files in this stage later stages will need
|
||||
dependencies, err := dockerfile.Dependencies(index, stages)
|
||||
@@ -172,7 +231,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 *manifest.Schema2Config) error {
|
||||
func resolveOnBuild(stage *instructions.Stage, config *v1.Config) error {
|
||||
if config.OnBuild == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
+12
-65
@@ -17,80 +17,27 @@ limitations under the License.
|
||||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/version"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
|
||||
img "github.com/GoogleContainerTools/container-diff/pkg/image"
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/containers/image/copy"
|
||||
"github.com/containers/image/docker"
|
||||
"github.com/containers/image/signature"
|
||||
"github.com/containers/image/transports/alltransports"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// sourceImage is the image that will be modified by the executor
|
||||
|
||||
// NewSourceImage initializes the source image with the base image
|
||||
func NewSourceImage(srcImg string) (*img.MutableSource, error) {
|
||||
if srcImg == constants.NoBaseImage {
|
||||
return img.NewMutableSource(nil)
|
||||
}
|
||||
logrus.Infof("Initializing source image %s", srcImg)
|
||||
ref, err := docker.ParseReference("//" + srcImg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return img.NewMutableSource(ref)
|
||||
}
|
||||
|
||||
// PushImage pushes the final image
|
||||
func PushImage(ms *img.MutableSource, destImg string, dockerInsecureSkipTLSVerify bool) error {
|
||||
srcRef := &img.ProxyReference{
|
||||
ImageReference: nil,
|
||||
Src: ms,
|
||||
}
|
||||
destRef, err := alltransports.ParseImageName("docker://" + destImg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
policyContext, err := getPolicyContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Infof("Pushing image to %s", destImg)
|
||||
|
||||
opts := ©.Options{
|
||||
DestinationCtx: &types.SystemContext{
|
||||
DockerRegistryUserAgent: fmt.Sprintf("kaniko/executor-%s", version.Version()),
|
||||
DockerInsecureSkipTLSVerify: dockerInsecureSkipTLSVerify,
|
||||
},
|
||||
}
|
||||
return copy.Image(policyContext, destRef, srcRef, opts)
|
||||
}
|
||||
|
||||
// SetEnvVariables sets environment variables as specified in the image
|
||||
func SetEnvVariables(ms *img.MutableSource) error {
|
||||
envVars := ms.Env()
|
||||
for key, val := range envVars {
|
||||
if err := os.Setenv(key, val); err != nil {
|
||||
func SetEnvVariables(img v1.Image) error {
|
||||
cfg, err := img.ConfigFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envVars := cfg.Config.Env
|
||||
for _, envVar := range envVars {
|
||||
split := strings.SplitN(envVar, "=", 2)
|
||||
if err := os.Setenv(split[0], split[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("Setting environment variable %s=%s", key, val)
|
||||
logrus.Infof("Setting environment variable %s", envVar)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPolicyContext() (*signature.PolicyContext, error) {
|
||||
policyContext, err := signature.NewPolicyContext(&signature.Policy{
|
||||
Default: signature.PolicyRequirements{signature.NewPRInsecureAcceptAnything()},
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Debugf("Error retrieving policy context: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
return policyContext, nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@ limitations under the License.
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LayeredMap struct {
|
||||
layers []map[string]string
|
||||
hasher func(string) (string, error)
|
||||
@@ -33,6 +38,21 @@ func (l *LayeredMap) Snapshot() {
|
||||
l.layers = append(l.layers, map[string]string{})
|
||||
}
|
||||
|
||||
func (l *LayeredMap) GetFlattenedPathsForWhiteOut() map[string]struct{} {
|
||||
paths := map[string]struct{}{}
|
||||
for _, l := range l.layers {
|
||||
for p := range l {
|
||||
if strings.HasPrefix(filepath.Base(p), ".wh.") {
|
||||
delete(paths, p)
|
||||
} else {
|
||||
paths[p] = struct{}{}
|
||||
}
|
||||
paths[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func (l *LayeredMap) Get(s string) (string, bool) {
|
||||
for i := len(l.layers) - 1; i >= 0; i-- {
|
||||
if v, ok := l.layers[i][s]; ok {
|
||||
|
||||
+39
-12
@@ -33,6 +33,7 @@ import (
|
||||
type Snapshotter struct {
|
||||
l *LayeredMap
|
||||
directory string
|
||||
hardlinks map[uint64]string
|
||||
}
|
||||
|
||||
// NewSnapshotter creates a new snapshotter rooted at d
|
||||
@@ -60,10 +61,7 @@ func (s *Snapshotter) TakeSnapshot(files []string) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contents, err := ioutil.ReadAll(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contents := buf.Bytes()
|
||||
if !filesAdded {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -99,7 +97,7 @@ func (s *Snapshotter) TakeSnapshotOfFiles(files []string) ([]byte, error) {
|
||||
}
|
||||
if maybeAdd {
|
||||
filesAdded = true
|
||||
util.AddToTar(file, info, w)
|
||||
util.AddToTar(file, info, s.hardlinks, w)
|
||||
}
|
||||
}
|
||||
if !filesAdded {
|
||||
@@ -109,27 +107,56 @@ func (s *Snapshotter) TakeSnapshotOfFiles(files []string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (s *Snapshotter) snapShotFS(f io.Writer) (bool, error) {
|
||||
s.hardlinks = map[uint64]string{}
|
||||
s.l.Snapshot()
|
||||
existingPaths := s.l.GetFlattenedPathsForWhiteOut()
|
||||
filesAdded := false
|
||||
w := tar.NewWriter(f)
|
||||
defer w.Close()
|
||||
|
||||
err := filepath.Walk(s.directory, func(path string, info os.FileInfo, err error) error {
|
||||
// Save the fs state in a map to iterate over later.
|
||||
memFs := map[string]os.FileInfo{}
|
||||
filepath.Walk(s.directory, func(path string, info os.FileInfo, err error) error {
|
||||
memFs[path] = info
|
||||
return nil
|
||||
})
|
||||
|
||||
// First handle whiteouts
|
||||
for p := range memFs {
|
||||
delete(existingPaths, p)
|
||||
}
|
||||
for path := range existingPaths {
|
||||
// Only add the whiteout if the directory for the file still exists.
|
||||
dir := filepath.Dir(path)
|
||||
if _, ok := memFs[dir]; ok {
|
||||
logrus.Infof("Adding whiteout for %s", path)
|
||||
filesAdded = true
|
||||
if err := util.Whiteout(path, w); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now create the tar.
|
||||
for path, info := range memFs {
|
||||
if util.PathInWhitelist(path, s.directory) {
|
||||
logrus.Debugf("Not adding %s to layer, as it's whitelisted", path)
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
|
||||
// Only add to the tar if we add it to the layeredmap.
|
||||
maybeAdd, err := s.l.MaybeAdd(path)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
if maybeAdd {
|
||||
logrus.Debugf("Adding %s to layer, because it was changed.", path)
|
||||
filesAdded = true
|
||||
return util.AddToTar(path, info, w)
|
||||
if err := util.AddToTar(path, info, s.hardlinks, w); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return filesAdded, err
|
||||
}
|
||||
|
||||
return filesAdded, nil
|
||||
}
|
||||
|
||||
+189
-18
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package util
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -25,35 +26,82 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
|
||||
"github.com/google/go-containerregistry/v1"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
"github.com/containers/image/docker"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var whitelist = []string{"/kaniko"}
|
||||
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",
|
||||
}
|
||||
var volumeWhitelist = []string{}
|
||||
|
||||
// ExtractFileSystemFromImage pulls an image and unpacks it to a file system at root
|
||||
func ExtractFileSystemFromImage(img string) error {
|
||||
func GetFSFromImage(img v1.Image) error {
|
||||
whitelist, err := fileSystemWhitelist(constants.WhitelistPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Infof("Whitelisted directories are %s", whitelist)
|
||||
if img == constants.NoBaseImage {
|
||||
logrus.Info("No base image, nothing to extract")
|
||||
return nil
|
||||
}
|
||||
ref, err := docker.ParseReference("//" + img)
|
||||
logrus.Infof("Mounted directories: %v", whitelist)
|
||||
layers, err := img.Layers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imgSrc, err := ref.NewImageSource(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
fs := map[string]struct{}{}
|
||||
whiteouts := map[string]struct{}{}
|
||||
|
||||
for i := len(layers) - 1; i >= 0; i-- {
|
||||
logrus.Infof("Unpacking layer: %d", i)
|
||||
l := layers[i]
|
||||
r, err := l.Uncompressed()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr := tar.NewReader(r)
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join("/", filepath.Clean(hdr.Name))
|
||||
base := filepath.Base(path)
|
||||
dir := filepath.Dir(path)
|
||||
if strings.HasPrefix(base, ".wh.") {
|
||||
logrus.Infof("Whiting out %s", path)
|
||||
name := strings.TrimPrefix(base, ".wh.")
|
||||
whiteouts[filepath.Join(dir, name)] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
if checkWhiteouts(path, whiteouts) {
|
||||
logrus.Infof("Not adding %s because it is whited out", path)
|
||||
continue
|
||||
}
|
||||
if _, ok := fs[path]; ok {
|
||||
logrus.Infof("Not adding %s because it was added by a prior layer", path)
|
||||
continue
|
||||
}
|
||||
|
||||
if checkWhitelist(path, whitelist) {
|
||||
logrus.Infof("Not adding %s because it is whitelisted", path)
|
||||
continue
|
||||
}
|
||||
fs[path] = struct{}{}
|
||||
|
||||
if err := extractFile("/", hdr, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return pkgutil.GetFileSystemFromReference(ref, imgSrc, constants.RootDir, whitelist)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteFilesystem deletes the extracted image file system
|
||||
@@ -76,14 +124,92 @@ func DeleteFilesystem() error {
|
||||
func ChildDirInWhitelist(path, directory string) bool {
|
||||
for _, d := range whitelist {
|
||||
dirPath := filepath.Join(directory, d)
|
||||
if pkgutil.HasFilepathPrefix(dirPath, path) {
|
||||
if HasFilepathPrefix(dirPath, path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PathInWhitelist returns true if the path is whitelisted
|
||||
func unTar(r io.Reader, dest string) error {
|
||||
tr := tar.NewReader(r)
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := extractFile(dest, hdr, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
||||
path := filepath.Join(dest, filepath.Clean(hdr.Name))
|
||||
base := filepath.Base(path)
|
||||
dir := filepath.Dir(path)
|
||||
mode := hdr.FileInfo().Mode()
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeReg:
|
||||
logrus.Debugf("creating file %s", path)
|
||||
// It's possible a file is in the tar before it's directory.
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
logrus.Debugf("base %s for file %s does not exist. Creating.", base, path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
currFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// manually set permissions on file, since the default umask (022) will interfere
|
||||
if err = os.Chmod(path, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.Copy(currFile, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
currFile.Close()
|
||||
|
||||
case tar.TypeDir:
|
||||
logrus.Debugf("creating dir %s", path)
|
||||
if err := os.MkdirAll(path, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
// In some cases, MkdirAll doesn't change the permissions, so run Chmod
|
||||
if err := os.Chmod(path, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeLink:
|
||||
logrus.Debugf("link from %s to %s", hdr.Linkname, path)
|
||||
// The base directory for a link may not exist before it is created.
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Symlink(filepath.Clean(filepath.Join("/", hdr.Linkname)), path); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeSymlink:
|
||||
logrus.Debugf("symlink from %s to %s", hdr.Linkname, path)
|
||||
// The base directory for a symlink may not exist before it is created.
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Symlink(hdr.Linkname, path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PathInWhitelist(path, directory string) bool {
|
||||
for _, c := range constants.KanikoBuildFiles {
|
||||
if path == c {
|
||||
@@ -92,7 +218,30 @@ func PathInWhitelist(path, directory string) bool {
|
||||
}
|
||||
for _, d := range whitelist {
|
||||
dirPath := filepath.Join(directory, d)
|
||||
if pkgutil.HasFilepathPrefix(path, dirPath) {
|
||||
if HasFilepathPrefix(path, dirPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkWhiteouts(path string, whiteouts map[string]struct{}) bool {
|
||||
// Don't add the file if it or it's directory are whited out.
|
||||
if _, ok := whiteouts[path]; ok {
|
||||
return true
|
||||
}
|
||||
for wd := range whiteouts {
|
||||
if HasFilepathPrefix(path, wd) {
|
||||
logrus.Infof("Not adding %s because it's directory is whited out", path)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkWhitelist(path string, whitelist []string) bool {
|
||||
for _, wl := range whitelist {
|
||||
if HasFilepathPrefix(path, wl) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -150,6 +299,9 @@ func RelativeFiles(fp string, root string) ([]string, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if PathInWhitelist(path, root) {
|
||||
return nil
|
||||
}
|
||||
relPath, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -301,3 +453,22 @@ func CopyFile(src, dest string) error {
|
||||
defer srcFile.Close()
|
||||
return CreateFile(dest, srcFile, fi.Mode())
|
||||
}
|
||||
|
||||
// HasFilepathPrefix checks if the given file path begins with prefix
|
||||
func HasFilepathPrefix(path, prefix string) bool {
|
||||
path = filepath.Clean(path)
|
||||
prefix = filepath.Clean(prefix)
|
||||
pathArray := strings.Split(path, "/")
|
||||
prefixArray := strings.Split(prefix, "/")
|
||||
|
||||
if len(pathArray) < len(prefixArray) {
|
||||
return false
|
||||
}
|
||||
for index := range prefixArray {
|
||||
if prefixArray[index] == pathArray[index] {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
+359
-2
@@ -17,9 +17,12 @@ limitations under the License.
|
||||
package util
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
@@ -47,7 +50,7 @@ func Test_fileSystemWhitelist(t *testing.T) {
|
||||
}
|
||||
|
||||
actualWhitelist, err := fileSystemWhitelist(path)
|
||||
expectedWhitelist := []string{"/kaniko", "/proc", "/dev", "/dev/pts", "/sys"}
|
||||
expectedWhitelist := []string{"/kaniko", "/proc", "/dev", "/dev/pts", "/sys", "/var/run"}
|
||||
sort.Strings(actualWhitelist)
|
||||
sort.Strings(expectedWhitelist)
|
||||
testutil.CheckErrorAndDeepEqual(t, false, err, expectedWhitelist, actualWhitelist)
|
||||
@@ -100,7 +103,6 @@ var tests = []struct {
|
||||
files: map[string]string{
|
||||
"/workspace/foo/a": "baz1",
|
||||
"/workspace/foo/b": "baz2",
|
||||
"/kaniko/file": "file",
|
||||
},
|
||||
directory: "",
|
||||
expectedFiles: []string{
|
||||
@@ -129,3 +131,358 @@ func Test_RelativeFiles(t *testing.T) {
|
||||
testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedFiles, actualFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkWhiteouts(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
whiteouts map[string]struct{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "file whited out",
|
||||
args: args{
|
||||
path: "/foo",
|
||||
whiteouts: map[string]struct{}{"/foo": {}},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "directory whited out",
|
||||
args: args{
|
||||
path: "/foo/bar",
|
||||
whiteouts: map[string]struct{}{"/foo": {}},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "grandparent whited out",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
whiteouts: map[string]struct{}{"/foo": {}},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "sibling whited out",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
whiteouts: map[string]struct{}{"/foo/bat": {}},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := checkWhiteouts(tt.args.path, tt.args.whiteouts); got != tt.want {
|
||||
t.Errorf("checkWhiteouts() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkWhitelist(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
whitelist []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "file whitelisted",
|
||||
args: args{
|
||||
path: "/foo",
|
||||
whitelist: []string{"/foo"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "directory whitelisted",
|
||||
args: args{
|
||||
path: "/foo/bar",
|
||||
whitelist: []string{"/foo"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "grandparent whitelisted",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
whitelist: []string{"/foo"},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "sibling whitelisted",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
whitelist: []string{"/foo/bat"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := checkWhitelist(tt.args.path, tt.args.whitelist); got != tt.want {
|
||||
t.Errorf("checkWhitelist() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasFilepathPrefix(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
prefix string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "parent",
|
||||
args: args{
|
||||
path: "/foo/bar",
|
||||
prefix: "/foo",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "nested parent",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
prefix: "/foo/bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "sibling",
|
||||
args: args{
|
||||
path: "/foo/bar",
|
||||
prefix: "/bar",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "nested sibling",
|
||||
args: args{
|
||||
path: "/foo/bar/baz",
|
||||
prefix: "/foo/bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "name prefix",
|
||||
args: args{
|
||||
path: "/foo2/bar",
|
||||
prefix: "/foo",
|
||||
},
|
||||
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 {
|
||||
t.Errorf("HasFilepathPrefix() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type checker func(root string, t *testing.T)
|
||||
|
||||
func fileExists(p string) checker {
|
||||
return func(root string, t *testing.T) {
|
||||
_, err := os.Stat(filepath.Join(root, p))
|
||||
if err != nil {
|
||||
t.Fatalf("File does not exist")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fileMatches(p string, c []byte) checker {
|
||||
return func(root string, t *testing.T) {
|
||||
actual, err := ioutil.ReadFile(filepath.Join(root, p))
|
||||
if err != nil {
|
||||
t.Fatalf("error reading file: %s", p)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, c) {
|
||||
t.Errorf("file contents do not match. %v!=%v", actual, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func permissionsMatch(p string, perms os.FileMode) checker {
|
||||
return func(root string, t *testing.T) {
|
||||
fi, err := os.Stat(filepath.Join(root, p))
|
||||
if err != nil {
|
||||
t.Fatalf("error statting file %s", p)
|
||||
}
|
||||
if fi.Mode() != perms {
|
||||
t.Errorf("Permissions do not match. %s != %s", fi.Mode(), perms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func linkPointsTo(src, dst string) checker {
|
||||
return func(root string, t *testing.T) {
|
||||
link := filepath.Join(root, src)
|
||||
got, err := os.Readlink(link)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading link %s: %s", link, err)
|
||||
}
|
||||
if got != dst {
|
||||
t.Errorf("link destination does not match: %s != %s", got, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fileHeader(name string, contents string, mode int64) *tar.Header {
|
||||
return &tar.Header{
|
||||
Name: name,
|
||||
Size: int64(len(contents)),
|
||||
Mode: mode,
|
||||
Typeflag: tar.TypeReg,
|
||||
}
|
||||
}
|
||||
|
||||
func linkHeader(name, linkname string) *tar.Header {
|
||||
return &tar.Header{
|
||||
Name: name,
|
||||
Size: 0,
|
||||
Typeflag: tar.TypeSymlink,
|
||||
Linkname: linkname,
|
||||
}
|
||||
}
|
||||
|
||||
func hardlinkHeader(name, linkname string) *tar.Header {
|
||||
return &tar.Header{
|
||||
Name: name,
|
||||
Size: 0,
|
||||
Typeflag: tar.TypeLink,
|
||||
Linkname: linkname,
|
||||
}
|
||||
}
|
||||
|
||||
func dirHeader(name string, mode int64) *tar.Header {
|
||||
return &tar.Header{
|
||||
Name: name,
|
||||
Size: 0,
|
||||
Typeflag: tar.TypeDir,
|
||||
Mode: mode,
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractFile(t *testing.T) {
|
||||
type tc struct {
|
||||
name string
|
||||
hdrs []*tar.Header
|
||||
contents []byte
|
||||
checkers []checker
|
||||
}
|
||||
|
||||
tcs := []tc{
|
||||
{
|
||||
name: "normal file",
|
||||
contents: []byte("helloworld"),
|
||||
hdrs: []*tar.Header{fileHeader("./bar", "helloworld", 0644)},
|
||||
checkers: []checker{
|
||||
fileExists("/bar"),
|
||||
fileMatches("/bar", []byte("helloworld")),
|
||||
permissionsMatch("/bar", 0644),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "normal file, directory does not exist",
|
||||
contents: []byte("helloworld"),
|
||||
hdrs: []*tar.Header{fileHeader("./foo/bar", "helloworld", 0644)},
|
||||
checkers: []checker{
|
||||
fileExists("/foo/bar"),
|
||||
fileMatches("/foo/bar", []byte("helloworld")),
|
||||
permissionsMatch("/foo/bar", 0644),
|
||||
permissionsMatch("/foo", 0755|os.ModeDir),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "normal file, directory is created after",
|
||||
contents: []byte("helloworld"),
|
||||
hdrs: []*tar.Header{
|
||||
fileHeader("./foo/bar", "helloworld", 0644),
|
||||
dirHeader("./foo", 0722),
|
||||
},
|
||||
checkers: []checker{
|
||||
fileExists("/foo/bar"),
|
||||
fileMatches("/foo/bar", []byte("helloworld")),
|
||||
permissionsMatch("/foo/bar", 0644),
|
||||
permissionsMatch("/foo", 0722|os.ModeDir),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink",
|
||||
hdrs: []*tar.Header{linkHeader("./bar", "bar/bat")},
|
||||
checkers: []checker{
|
||||
linkPointsTo("/bar", "bar/bat"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink relative path",
|
||||
hdrs: []*tar.Header{linkHeader("./bar", "./foo/bar/baz")},
|
||||
checkers: []checker{
|
||||
linkPointsTo("/bar", "./foo/bar/baz"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink parent does not exist",
|
||||
hdrs: []*tar.Header{linkHeader("./foo/bar/baz", "../../bat")},
|
||||
checkers: []checker{
|
||||
linkPointsTo("/foo/bar/baz", "../../bat"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink parent does not exist",
|
||||
hdrs: []*tar.Header{linkHeader("./foo/bar/baz", "../../bat")},
|
||||
checkers: []checker{
|
||||
linkPointsTo("/foo/bar/baz", "../../bat"),
|
||||
permissionsMatch("/foo", 0755|os.ModeDir),
|
||||
permissionsMatch("/foo/bar", 0755|os.ModeDir),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hardlink",
|
||||
hdrs: []*tar.Header{
|
||||
fileHeader("/bin/gzip", "gzip-binary", 0751),
|
||||
hardlinkHeader("/bin/uncompress", "/bin/gzip"),
|
||||
},
|
||||
checkers: []checker{
|
||||
linkPointsTo("/bin/uncompress", "/bin/gzip"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc := tc
|
||||
t.Parallel()
|
||||
r, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(r)
|
||||
for _, hdr := range tc.hdrs {
|
||||
if err := extractFile(r, hdr, bytes.NewReader(tc.contents)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for _, checker := range tc.checkers {
|
||||
checker(r, t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+22
-9
@@ -23,18 +23,16 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var hardlinks = make(map[uint64]string)
|
||||
|
||||
// AddToTar adds the file i to tar w at path p
|
||||
func AddToTar(p string, i os.FileInfo, w *tar.Writer) error {
|
||||
func AddToTar(p string, i os.FileInfo, hardlinks map[uint64]string, w *tar.Writer) error {
|
||||
linkDst := ""
|
||||
if i.Mode()&os.ModeSymlink != 0 {
|
||||
var err error
|
||||
@@ -49,7 +47,7 @@ func AddToTar(p string, i os.FileInfo, w *tar.Writer) error {
|
||||
}
|
||||
hdr.Name = p
|
||||
|
||||
hardlink, linkDst := checkHardlink(p, i)
|
||||
hardlink, linkDst := checkHardlink(p, hardlinks, i)
|
||||
if hardlink {
|
||||
hdr.Linkname = linkDst
|
||||
hdr.Typeflag = tar.TypeLink
|
||||
@@ -72,8 +70,23 @@ func AddToTar(p string, i os.FileInfo, w *tar.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Whiteout(p string, w *tar.Writer) error {
|
||||
dir := filepath.Dir(p)
|
||||
name := ".wh." + filepath.Base(p)
|
||||
|
||||
th := &tar.Header{
|
||||
Name: filepath.Join(dir, name),
|
||||
Size: 0,
|
||||
}
|
||||
if err := w.WriteHeader(th); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if path is hardlink, and the link destination
|
||||
func checkHardlink(p string, i os.FileInfo) (bool, string) {
|
||||
func checkHardlink(p string, hardlinks map[uint64]string, i os.FileInfo) (bool, string) {
|
||||
hardlink := false
|
||||
linkDst := ""
|
||||
if sys := i.Sys(); sys != nil {
|
||||
@@ -108,7 +121,7 @@ func UnpackLocalTarArchive(path, dest string) error {
|
||||
return UnpackCompressedTar(path, dest)
|
||||
} else if compressionLevel == archive.Bzip2 {
|
||||
bzr := bzip2.NewReader(file)
|
||||
return pkgutil.UnTar(bzr, dest, nil)
|
||||
return unTar(bzr, dest)
|
||||
}
|
||||
}
|
||||
if fileIsUncompressedTar(path) {
|
||||
@@ -117,7 +130,7 @@ func UnpackLocalTarArchive(path, dest string) error {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return pkgutil.UnTar(file, dest, nil)
|
||||
return unTar(file, dest)
|
||||
}
|
||||
return errors.New("path does not lead to local tar archive")
|
||||
}
|
||||
@@ -181,5 +194,5 @@ func UnpackCompressedTar(path, dir string) error {
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
return pkgutil.UnTar(gzr, dir, nil)
|
||||
return unTar(gzr, dir)
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ func createTar(testdir string, writer io.Writer) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := AddToTar(filePath, fi, w); err != nil {
|
||||
if err := AddToTar(filePath, fi, map[uint64]string{}, w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user