465 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			465 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Go
		
	
	
	
/*
 | 
						|
Copyright 2018 Google LLC
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package executor
 | 
						|
 | 
						|
import (
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"reflect"
 | 
						|
	"sort"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/GoogleContainerTools/kaniko/pkg/config"
 | 
						|
	"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
 | 
						|
	"github.com/GoogleContainerTools/kaniko/testutil"
 | 
						|
	"github.com/google/go-cmp/cmp"
 | 
						|
	v1 "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/moby/buildkit/frontend/dockerfile/instructions"
 | 
						|
)
 | 
						|
 | 
						|
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],
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type MockCommand struct {
 | 
						|
	name string
 | 
						|
}
 | 
						|
 | 
						|
func (m *MockCommand) Name() string {
 | 
						|
	return m.name
 | 
						|
}
 | 
						|
 | 
						|
func Test_stageBuilder_shouldTakeSnapshot(t *testing.T) {
 | 
						|
	commands := []instructions.Command{
 | 
						|
		&MockCommand{name: "command1"},
 | 
						|
		&MockCommand{name: "command2"},
 | 
						|
		&MockCommand{name: "command3"},
 | 
						|
	}
 | 
						|
 | 
						|
	stage := instructions.Stage{
 | 
						|
		Commands: commands,
 | 
						|
	}
 | 
						|
 | 
						|
	type fields struct {
 | 
						|
		stage config.KanikoStage
 | 
						|
		opts  *config.KanikoOptions
 | 
						|
	}
 | 
						|
	type args struct {
 | 
						|
		index int
 | 
						|
		files []string
 | 
						|
	}
 | 
						|
	tests := []struct {
 | 
						|
		name   string
 | 
						|
		fields fields
 | 
						|
		args   args
 | 
						|
		want   bool
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "final stage not last command",
 | 
						|
			fields: fields{
 | 
						|
				stage: config.KanikoStage{
 | 
						|
					Final: true,
 | 
						|
					Stage: stage,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			args: args{
 | 
						|
				index: 1,
 | 
						|
			},
 | 
						|
			want: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "not final stage last command",
 | 
						|
			fields: fields{
 | 
						|
				stage: config.KanikoStage{
 | 
						|
					Final: false,
 | 
						|
					Stage: stage,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			args: args{
 | 
						|
				index: len(commands) - 1,
 | 
						|
			},
 | 
						|
			want: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "not final stage not last command",
 | 
						|
			fields: fields{
 | 
						|
				stage: config.KanikoStage{
 | 
						|
					Final: false,
 | 
						|
					Stage: stage,
 | 
						|
				},
 | 
						|
			},
 | 
						|
			args: args{
 | 
						|
				index: 0,
 | 
						|
			},
 | 
						|
			want: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "caching enabled intermediate container",
 | 
						|
			fields: fields{
 | 
						|
				stage: config.KanikoStage{
 | 
						|
					Final: false,
 | 
						|
					Stage: stage,
 | 
						|
				},
 | 
						|
				opts: &config.KanikoOptions{Cache: true},
 | 
						|
			},
 | 
						|
			args: args{
 | 
						|
				index: 0,
 | 
						|
			},
 | 
						|
			want: true,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
 | 
						|
			if tt.fields.opts == nil {
 | 
						|
				tt.fields.opts = &config.KanikoOptions{}
 | 
						|
			}
 | 
						|
			s := &stageBuilder{
 | 
						|
				stage: tt.fields.stage,
 | 
						|
				opts:  tt.fields.opts,
 | 
						|
			}
 | 
						|
			if got := s.shouldTakeSnapshot(tt.args.index, tt.args.files); got != tt.want {
 | 
						|
				t.Errorf("stageBuilder.shouldTakeSnapshot() = %v, want %v", got, tt.want)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestCalculateDependencies(t *testing.T) {
 | 
						|
	type args struct {
 | 
						|
		dockerfile string
 | 
						|
	}
 | 
						|
	tests := []struct {
 | 
						|
		name string
 | 
						|
		args args
 | 
						|
		want map[int][]string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "no deps",
 | 
						|
			args: args{
 | 
						|
				dockerfile: `
 | 
						|
FROM debian as stage1
 | 
						|
RUN foo
 | 
						|
FROM stage1
 | 
						|
RUN bar
 | 
						|
`,
 | 
						|
			},
 | 
						|
			want: map[int][]string{},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "args",
 | 
						|
			args: args{
 | 
						|
				dockerfile: `
 | 
						|
ARG myFile=foo
 | 
						|
FROM debian as stage1
 | 
						|
RUN foo
 | 
						|
FROM stage1
 | 
						|
ARG myFile
 | 
						|
COPY --from=stage1 /tmp/$myFile.txt .
 | 
						|
RUN bar
 | 
						|
`,
 | 
						|
			},
 | 
						|
			want: map[int][]string{
 | 
						|
				0: {"/tmp/foo.txt"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "simple deps",
 | 
						|
			args: args{
 | 
						|
				dockerfile: `
 | 
						|
FROM debian as stage1
 | 
						|
FROM alpine
 | 
						|
COPY --from=stage1 /foo /bar
 | 
						|
`,
 | 
						|
			},
 | 
						|
			want: map[int][]string{
 | 
						|
				0: {"/foo"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "two sets deps",
 | 
						|
			args: args{
 | 
						|
				dockerfile: `
 | 
						|
FROM debian as stage1
 | 
						|
FROM ubuntu as stage2
 | 
						|
RUN foo
 | 
						|
COPY --from=stage1 /foo /bar
 | 
						|
FROM alpine
 | 
						|
COPY --from=stage2 /bar /bat
 | 
						|
`,
 | 
						|
			},
 | 
						|
			want: map[int][]string{
 | 
						|
				0: {"/foo"},
 | 
						|
				1: {"/bar"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "double deps",
 | 
						|
			args: args{
 | 
						|
				dockerfile: `
 | 
						|
FROM debian as stage1
 | 
						|
FROM ubuntu as stage2
 | 
						|
RUN foo
 | 
						|
COPY --from=stage1 /foo /bar
 | 
						|
FROM alpine
 | 
						|
COPY --from=stage1 /baz /bat
 | 
						|
`,
 | 
						|
			},
 | 
						|
			want: map[int][]string{
 | 
						|
				0: {"/foo", "/baz"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "envs in deps",
 | 
						|
			args: args{
 | 
						|
				dockerfile: `
 | 
						|
FROM debian as stage1
 | 
						|
FROM ubuntu as stage2
 | 
						|
RUN foo
 | 
						|
ENV key1 val1
 | 
						|
ENV key2 val2
 | 
						|
COPY --from=stage1 /foo/$key1 /foo/$key2 /bar
 | 
						|
FROM alpine
 | 
						|
COPY --from=stage2 /bar /bat
 | 
						|
`,
 | 
						|
			},
 | 
						|
			want: map[int][]string{
 | 
						|
				0: {"/foo/val1", "/foo/val2"},
 | 
						|
				1: {"/bar"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "envs from base image in deps",
 | 
						|
			args: args{
 | 
						|
				dockerfile: `
 | 
						|
FROM debian as stage1
 | 
						|
ENV key1 baseval1
 | 
						|
FROM stage1 as stage2
 | 
						|
RUN foo
 | 
						|
ENV key2 val2
 | 
						|
COPY --from=stage1 /foo/$key1 /foo/$key2 /bar
 | 
						|
FROM alpine
 | 
						|
COPY --from=stage2 /bar /bat
 | 
						|
`,
 | 
						|
			},
 | 
						|
			want: map[int][]string{
 | 
						|
				0: {"/foo/baseval1", "/foo/val2"},
 | 
						|
				1: {"/bar"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			f, _ := ioutil.TempFile("", "")
 | 
						|
			ioutil.WriteFile(f.Name(), []byte(tt.args.dockerfile), 0755)
 | 
						|
			opts := &config.KanikoOptions{
 | 
						|
				DockerfilePath: f.Name(),
 | 
						|
			}
 | 
						|
 | 
						|
			got, err := CalculateDependencies(opts)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("got error: %s,", err)
 | 
						|
			}
 | 
						|
 | 
						|
			if !reflect.DeepEqual(got, tt.want) {
 | 
						|
				diff := cmp.Diff(got, tt.want)
 | 
						|
				t.Errorf("CalculateDependencies() = %v, want %v, diff %v", got, tt.want, diff)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func Test_filesToSave(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		name  string
 | 
						|
		args  []string
 | 
						|
		want  []string
 | 
						|
		files []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:  "simple",
 | 
						|
			args:  []string{"foo"},
 | 
						|
			files: []string{"foo"},
 | 
						|
			want:  []string{"foo"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "glob",
 | 
						|
			args:  []string{"foo*"},
 | 
						|
			files: []string{"foo", "foo2", "fooooo", "bar"},
 | 
						|
			want:  []string{"foo", "foo2", "fooooo"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "complex glob",
 | 
						|
			args:  []string{"foo*", "bar?"},
 | 
						|
			files: []string{"foo", "foo2", "fooooo", "bar", "bar1", "bar2", "bar33"},
 | 
						|
			want:  []string{"foo", "foo2", "fooooo", "bar1", "bar2"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "dir",
 | 
						|
			args:  []string{"foo"},
 | 
						|
			files: []string{"foo/bar", "foo/baz", "foo/bat/baz"},
 | 
						|
			want:  []string{"foo"},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			tmpDir, err := ioutil.TempDir("", "")
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("error creating tmpdir: %s", err)
 | 
						|
			}
 | 
						|
			defer os.RemoveAll(tmpDir)
 | 
						|
 | 
						|
			for _, f := range tt.files {
 | 
						|
				p := filepath.Join(tmpDir, f)
 | 
						|
				dir := filepath.Dir(p)
 | 
						|
				if dir != "." {
 | 
						|
					if err := os.MkdirAll(dir, 0755); err != nil {
 | 
						|
						t.Errorf("error making dir: %s", err)
 | 
						|
					}
 | 
						|
				}
 | 
						|
				fp, err := os.Create(p)
 | 
						|
				if err != nil {
 | 
						|
					t.Errorf("error making file: %s", err)
 | 
						|
				}
 | 
						|
				fp.Close()
 | 
						|
			}
 | 
						|
 | 
						|
			args := []string{}
 | 
						|
			for _, arg := range tt.args {
 | 
						|
				args = append(args, filepath.Join(tmpDir, arg))
 | 
						|
			}
 | 
						|
			got, err := filesToSave(args)
 | 
						|
			if err != nil {
 | 
						|
				t.Errorf("got err: %s", err)
 | 
						|
			}
 | 
						|
			want := []string{}
 | 
						|
			for _, w := range tt.want {
 | 
						|
				want = append(want, filepath.Join(tmpDir, w))
 | 
						|
			}
 | 
						|
			sort.Strings(want)
 | 
						|
			sort.Strings(got)
 | 
						|
			if !reflect.DeepEqual(got, want) {
 | 
						|
				t.Errorf("filesToSave() = %v, want %v", got, want)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestInitializeConfig(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		description string
 | 
						|
		cfg         v1.ConfigFile
 | 
						|
		expected    v1.Config
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			description: "env is not set in the image",
 | 
						|
			cfg: v1.ConfigFile{
 | 
						|
				Config: v1.Config{
 | 
						|
					Image: "test",
 | 
						|
				},
 | 
						|
			},
 | 
						|
			expected: v1.Config{
 | 
						|
				Image: "test",
 | 
						|
				Env: []string{
 | 
						|
					"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			description: "env is set in the image",
 | 
						|
			cfg: v1.ConfigFile{
 | 
						|
				Config: v1.Config{
 | 
						|
					Env: []string{
 | 
						|
						"PATH=/usr/local/something",
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
			expected: v1.Config{
 | 
						|
				Env: []string{
 | 
						|
					"PATH=/usr/local/something",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			description: "image is empty",
 | 
						|
			expected: v1.Config{
 | 
						|
				Env: []string{
 | 
						|
					"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tt := range tests {
 | 
						|
		img, err := mutate.ConfigFile(empty.Image, &tt.cfg)
 | 
						|
		if err != nil {
 | 
						|
			t.Errorf("error seen when running test %s", err)
 | 
						|
			t.Fail()
 | 
						|
		}
 | 
						|
		actual, _ := initializeConfig(img)
 | 
						|
		testutil.CheckDeepEqual(t, tt.expected, actual.Config)
 | 
						|
	}
 | 
						|
}
 |