diff --git a/deploy/skaffold/Dockerfile b/deploy/skaffold/Dockerfile index f9921e50fdb..bcf4e691eb4 100644 --- a/deploy/skaffold/Dockerfile +++ b/deploy/skaffold/Dockerfile @@ -47,6 +47,11 @@ RUN echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8 RUN apt-get update \ && apt-get install -y bazel +ENV CONTAINER_STRUCTURE_TEST_VERSION=1.5.0 +RUN curl -LO https://storage.googleapis.com/container-structure-test/v${CONTAINER_STRUCTURE_TEST_VERSION}/container-structure-test-linux-amd64 \ + && chmod +x container-structure-test-linux-amd64 \ + && mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test + ENV PATH /usr/local/go/bin:/go/bin:/google-cloud-sdk/bin:$PATH FROM runtime_deps as builder @@ -94,4 +99,3 @@ CMD ["make", "integration"] FROM runtime_deps as distribution COPY --from=integration /usr/bin/skaffold /usr/bin/skaffold - diff --git a/integration/examples/getting-started/skaffold.yaml b/integration/examples/getting-started/skaffold.yaml index 785d94052de..7ace8c5b6f8 100644 --- a/integration/examples/getting-started/skaffold.yaml +++ b/integration/examples/getting-started/skaffold.yaml @@ -3,6 +3,10 @@ kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-example +test: + - image: gcr.io/k8s-skaffold/skaffold-example + structureTests: + - ./test/* deploy: kubectl: manifests: @@ -12,3 +16,8 @@ profiles: build: googleCloudBuild: projectId: k8s-skaffold + - name: test + test: + - image: gcr.io/k8s-skaffold/skaffold-example + structureTests: + - ./test/profile_structure_test.yaml diff --git a/integration/examples/getting-started/test/profile_structure_test.yaml b/integration/examples/getting-started/test/profile_structure_test.yaml new file mode 100644 index 00000000000..6966d26930a --- /dev/null +++ b/integration/examples/getting-started/test/profile_structure_test.yaml @@ -0,0 +1,6 @@ +schemaVersion: 2.0.0 + +fileExistenceTests: + - name: 'no go binary' + path: '/usr/bin/go' + shouldExist: false diff --git a/integration/examples/getting-started/test/structure_test.yaml b/integration/examples/getting-started/test/structure_test.yaml new file mode 100644 index 00000000000..6b1223eae90 --- /dev/null +++ b/integration/examples/getting-started/test/structure_test.yaml @@ -0,0 +1,6 @@ +schemaVersion: 2.0.0 + +fileExistenceTests: + - name: 'no local go binary' + path: /usr/local/bin/go' + shouldExist: false diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index cf50520f9eb..5814567a157 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -38,6 +38,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -51,6 +52,7 @@ var ErrorConfigurationChanged = errors.New("configuration changed") type SkaffoldRunner struct { build.Builder deploy.Deployer + test.Tester tag.Tagger opts *config.SkaffoldOptions @@ -76,19 +78,25 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldConfig) (*Sk return nil, errors.Wrap(err, "parsing skaffold build config") } + tester, err := getTester(&cfg.Test) + if err != nil { + return nil, errors.Wrap(err, "parsing skaffold test config") + } + deployer, err := getDeployer(&cfg.Deploy, kubeContext, opts.Namespace) if err != nil { return nil, errors.Wrap(err, "parsing skaffold deploy config") } deployer = deploy.WithLabels(deployer, opts, builder, deployer, tagger) - builder, deployer = WithTimings(builder, deployer) + builder, tester, deployer = WithTimings(builder, tester, deployer) if opts.Notification { deployer = WithNotification(deployer) } return &SkaffoldRunner{ Builder: builder, + Tester: tester, Deployer: deployer, Tagger: tagger, opts: opts, @@ -115,6 +123,10 @@ func getBuilder(cfg *latest.BuildConfig, kubeContext string) (build.Builder, err } } +func getTester(cfg *[]latest.TestCase) (test.Tester, error) { + return test.NewTester(cfg) +} + func getDeployer(cfg *latest.DeployConfig, kubeContext string, namespace string) (deploy.Deployer, error) { deployers := []deploy.Deployer{} @@ -171,13 +183,17 @@ func getTagger(t latest.TagPolicy, customTag string) (tag.Tagger, error) { } } -// Run builds artifacts and then deploys them. +// Run builds artifacts, runs tests on built artifacts, and then deploys them. func (r *SkaffoldRunner) Run(ctx context.Context, out io.Writer, artifacts []*latest.Artifact) error { bRes, err := r.Build(ctx, out, r.Tagger, artifacts) if err != nil { return errors.Wrap(err, "build step") } + if err = r.Test(out, bRes); err != nil { + return errors.Wrap(err, "test step") + } + _, err = r.Deploy(ctx, out, bRes) if err != nil { return errors.Wrap(err, "deploy step") @@ -241,12 +257,20 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } r.updateBuiltImages(imageList, bRes) + if err := r.Test(out, bRes); err != nil { + logrus.Warnln("Skipping Deploy due to failed tests:", err) + return nil + } if _, err = r.Deploy(ctx, out, r.builds); err != nil { logrus.Warnln("Skipping Deploy due to error:", err) return nil } case changed.needsRedeploy: + if err := r.Test(out, r.builds); err != nil { + logrus.Warnln("Skipping Deploy due to failed tests:", err) + return nil + } if _, err := r.Deploy(ctx, out, r.builds); err != nil { logrus.Warnln("Skipping Deploy due to error:", err) return nil @@ -275,6 +299,14 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } } + // Watch test configuration + if err := watcher.Register( + func() ([]string, error) { return r.TestDependencies(), nil }, + func(watch.Events) { changed.needsRedeploy = true }, + ); err != nil { + return nil, errors.Wrap(err, "watching test files") + } + // Watch deployment configuration if err := watcher.Register( func() ([]string, error) { return r.Dependencies() }, @@ -298,6 +330,9 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } r.updateBuiltImages(imageList, bRes) + if err := r.Test(out, bRes); err != nil { + return nil, errors.Wrap(err, "exiting dev mode because the first test run failed") + } _, err = r.Deploy(ctx, out, r.builds) if err != nil { diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 9276893851c..2260d97e414 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -31,6 +31,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" clientgo "k8s.io/client-go/kubernetes" @@ -65,6 +66,23 @@ func (t *TestBuilder) Build(ctx context.Context, w io.Writer, tagger tag.Tagger, return builds, nil } +type TestTester struct { + errors []error +} + +func (t *TestTester) Test(out io.Writer, builds []build.Artifact) error { + if len(t.errors) > 0 { + err := t.errors[0] + t.errors = t.errors[1:] + return err + } + return nil +} + +func (t *TestTester) TestDependencies() []string { + return []string{} +} + type TestDeployer struct { deployed []build.Artifact errors []error @@ -140,6 +158,7 @@ func TestNewForConfig(t *testing.T) { config *latest.SkaffoldConfig shouldErr bool expectedBuilder build.Builder + expectedTester test.Tester expectedDeployer deploy.Deployer }{ { @@ -158,6 +177,7 @@ func TestNewForConfig(t *testing.T) { }, }, expectedBuilder: &local.Builder{}, + expectedTester: &test.FullTester{}, expectedDeployer: &deploy.KubectlDeployer{}, }, { @@ -184,6 +204,7 @@ func TestNewForConfig(t *testing.T) { }, shouldErr: true, expectedBuilder: &local.Builder{}, + expectedTester: &test.FullTester{}, expectedDeployer: &deploy.KubectlDeployer{}, }, { @@ -197,6 +218,7 @@ func TestNewForConfig(t *testing.T) { }}, shouldErr: true, expectedBuilder: &local.Builder{}, + expectedTester: &test.FullTester{}, expectedDeployer: &deploy.KubectlDeployer{}, }, { @@ -218,9 +240,10 @@ func TestNewForConfig(t *testing.T) { testutil.CheckError(t, test.shouldErr, err) if cfg != nil { - b, d := WithTimings(test.expectedBuilder, test.expectedDeployer) + b, _t, d := WithTimings(test.expectedBuilder, test.expectedTester, test.expectedDeployer) testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, b, cfg.Builder) + testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, _t, cfg.Tester) testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, d, cfg.Deployer) } }) @@ -232,6 +255,7 @@ func TestRun(t *testing.T) { description string config *latest.SkaffoldConfig builder build.Builder + tester test.Tester deployer deploy.Deployer shouldErr bool }{ @@ -239,6 +263,7 @@ func TestRun(t *testing.T) { description: "run no error", config: &latest.SkaffoldConfig{}, builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{}, }, { @@ -247,6 +272,7 @@ func TestRun(t *testing.T) { builder: &TestBuilder{ errors: []error{fmt.Errorf("")}, }, + tester: &TestTester{}, shouldErr: true, }, { @@ -261,17 +287,42 @@ func TestRun(t *testing.T) { }, }, builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{ errors: []error{fmt.Errorf("")}, }, shouldErr: true, }, + { + description: "run test error", + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ + { + ImageName: "test", + }, + }, + }, + Test: []latest.TestCase{ + { + ImageName: "test", + StructureTests: []string{"fake_file.yaml"}, + }, + }, + }, + builder: &TestBuilder{}, + tester: &TestTester{ + errors: []error{fmt.Errorf("")}, + }, + shouldErr: true, + }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { runner := &SkaffoldRunner{ Builder: test.builder, + Tester: test.tester, Deployer: test.deployer, Tagger: &tag.ChecksumTagger{}, opts: &config.SkaffoldOptions{}, @@ -290,6 +341,7 @@ func TestDev(t *testing.T) { var tests = []struct { description string builder build.Builder + tester test.Tester deployer deploy.Deployer watcherFactory watch.Factory shouldErr bool @@ -306,23 +358,35 @@ func TestDev(t *testing.T) { { description: "fails to deploy the first time", builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{ errors: []error{fmt.Errorf("")}, }, watcherFactory: NewWatcherFactory(nil, nil), shouldErr: true, }, + { + description: "fails to deploy due to failed tests", + builder: &TestBuilder{}, + tester: &TestTester{ + errors: []error{fmt.Errorf("")}, + }, + watcherFactory: NewWatcherFactory(nil, nil), + shouldErr: true, + }, { description: "ignore subsequent build errors", builder: &TestBuilder{ errors: []error{nil, fmt.Errorf("")}, }, + tester: &TestTester{}, deployer: &TestDeployer{}, watcherFactory: NewWatcherFactory(nil, nil, nil), }, { description: "ignore subsequent deploy errors", builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{ errors: []error{nil, fmt.Errorf("")}, }, @@ -331,6 +395,7 @@ func TestDev(t *testing.T) { { description: "fail to watch files", builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{}, watcherFactory: NewWatcherFactory(fmt.Errorf(""), nil), shouldErr: true, @@ -341,6 +406,7 @@ func TestDev(t *testing.T) { t.Run(test.description, func(t *testing.T) { runner := &SkaffoldRunner{ Builder: test.builder, + Tester: test.tester, Deployer: test.deployer, Tagger: &tag.ChecksumTagger{}, watchFactory: test.watcherFactory, @@ -360,6 +426,7 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { defer resetClient() builder := &TestBuilder{} + tester := &TestTester{} deployer := &TestDeployer{} artifacts := []*latest.Artifact{ {ImageName: "image1"}, @@ -368,6 +435,7 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { runner := &SkaffoldRunner{ Builder: builder, + Tester: tester, Deployer: deployer, opts: &config.SkaffoldOptions{}, } diff --git a/pkg/skaffold/runner/timings.go b/pkg/skaffold/runner/timings.go index 53713cbc982..0bf13b0b000 100644 --- a/pkg/skaffold/runner/timings.go +++ b/pkg/skaffold/runner/timings.go @@ -26,20 +26,23 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" ) // WithTimings creates a deployer that logs the duration of each phase. -func WithTimings(b build.Builder, d deploy.Deployer) (build.Builder, deploy.Deployer) { +func WithTimings(b build.Builder, t test.Tester, d deploy.Deployer) (build.Builder, test.Tester, deploy.Deployer) { w := withTimings{ Builder: b, + Tester: t, Deployer: d, } - return w, w + return w, w, w } type withTimings struct { build.Builder + test.Tester deploy.Deployer } @@ -54,6 +57,17 @@ func (w withTimings) Build(ctx context.Context, out io.Writer, tagger tag.Tagger return bRes, err } +func (w withTimings) Test(out io.Writer, builds []build.Artifact) error { + start := time.Now() + color.Default.Fprintln(out, "Starting test...") + + err := w.Tester.Test(out, builds) + if err == nil { + color.Default.Fprintln(out, "Test complete in", time.Since(start)) + } + return err +} + func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact) ([]deploy.Artifact, error) { start := time.Now() color.Default.Fprintln(out, "Starting deploy...") diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index b1bd8b1011d..c9f7efbc1a8 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -34,6 +34,7 @@ type SkaffoldConfig struct { Kind string `yaml:"kind"` Build BuildConfig `yaml:"build,omitempty"` + Test []TestCase `yaml:"test,omitempty"` Deploy DeployConfig `yaml:"deploy,omitempty"` Profiles []Profile `yaml:"profiles,omitempty"` } @@ -116,6 +117,13 @@ type KanikoBuild struct { Timeout string `yaml:"timeout,omitempty"` } +// TestCase is a struct containing all the specified test +// configuration for an image. +type TestCase struct { + ImageName string `yaml:"image"` + StructureTests []string `yaml:"structureTests,omitempty"` +} + // DeployConfig contains all the configuration needed by the deploy steps type DeployConfig struct { DeployType `yaml:",inline"` @@ -212,6 +220,7 @@ type Artifact struct { type Profile struct { Name string `yaml:"name"` Build BuildConfig `yaml:"build,omitempty"` + Test []TestCase `yaml:"test,omitempty"` Deploy DeployConfig `yaml:"deploy,omitempty"` } diff --git a/pkg/skaffold/schema/profiles.go b/pkg/skaffold/schema/profiles.go index 58823355c42..fd191393151 100644 --- a/pkg/skaffold/schema/profiles.go +++ b/pkg/skaffold/schema/profiles.go @@ -55,6 +55,7 @@ func applyProfile(config *latest.SkaffoldConfig, profile latest.Profile) { Kind: config.Kind, Build: overlayProfileField(config.Build, profile.Build).(latest.BuildConfig), Deploy: overlayProfileField(config.Deploy, profile.Deploy).(latest.DeployConfig), + Test: overlayProfileField(config.Test, profile.Test).([]latest.TestCase), } } diff --git a/pkg/skaffold/schema/v1alpha3/config.go b/pkg/skaffold/schema/v1alpha3/config.go index cb35735b426..35cf08cff54 100644 --- a/pkg/skaffold/schema/v1alpha3/config.go +++ b/pkg/skaffold/schema/v1alpha3/config.go @@ -201,9 +201,10 @@ type HelmConventionConfig struct { // Artifact represents items that need to be built, along with the context in which // they should be built. type Artifact struct { - ImageName string `yaml:"imageName"` - Workspace string `yaml:"workspace,omitempty"` - ArtifactType `yaml:",inline"` + ImageName string `yaml:"imageName"` + Workspace string `yaml:"workspace,omitempty"` + ArtifactType `yaml:",inline"` + StructureTestFiles []string `yaml:"structureTestFiles"` } // Profile is additional configuration that overrides default diff --git a/pkg/skaffold/test/structure/structure.go b/pkg/skaffold/test/structure/structure.go new file mode 100644 index 00000000000..fb1e49aa401 --- /dev/null +++ b/pkg/skaffold/test/structure/structure.go @@ -0,0 +1,39 @@ +/* +Copyright 2018 The Skaffold Authors + +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 structure + +import ( + "os/exec" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + + "github.com/sirupsen/logrus" +) + +// Test is the entrypoint for running structure tests +func (tr *Runner) Test(image string) error { + logrus.Infof("running structure tests for files %v", tr.testFiles) + args := []string{"test", "--image", image} + for _, f := range tr.testFiles { + args = append(args, "--config", f) + } + args = append(args, tr.testFiles...) + cmd := exec.Command("container-structure-test", args...) + + _, err := util.RunCmdOut(cmd) + return err +} diff --git a/pkg/skaffold/test/structure/types.go b/pkg/skaffold/test/structure/types.go new file mode 100644 index 00000000000..dddf0618b5a --- /dev/null +++ b/pkg/skaffold/test/structure/types.go @@ -0,0 +1,27 @@ +/* +Copyright 2018 The Skaffold Authors + +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 structure + +type Runner struct { + testFiles []string +} + +func NewStructureTestRunner(files []string) (*Runner, error) { + return &Runner{ + testFiles: files, + }, nil +} diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go new file mode 100644 index 00000000000..e75861411e0 --- /dev/null +++ b/pkg/skaffold/test/test.go @@ -0,0 +1,104 @@ +/* +Copyright 2018 The Skaffold Authors + +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 test + +import ( + "io" + "os" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test/structure" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + + "github.com/pkg/errors" +) + +// NewTester parses the provided test cases from the Skaffold config, +// and returns a Tester instance with all the necessary test runners +// to run all specified tests. +func NewTester(testCases *[]latest.TestCase) (Tester, error) { + testers := []*ArtifactTester{} + deps := []string{} + // TODO(nkubala): copied this from runner.getDeployer(), this should be moved somewhere else + cwd, err := os.Getwd() + if err != nil { + return nil, errors.Wrap(err, "finding current directory") + } + for _, testCase := range *testCases { + testRunner := &ArtifactTester{ + ImageName: testCase.ImageName, + } + if testCase.StructureTests != nil { + stFiles, err := util.ExpandPathsGlob(cwd, testCase.StructureTests) + if err != nil { + return FullTester{}, errors.Wrap(err, "expanding test file paths") + } + stRunner, err := structure.NewStructureTestRunner(stFiles) + if err != nil { + return FullTester{}, errors.Wrap(err, "retrieving structure test runner") + } + testRunner.TestRunners = append(testRunner.TestRunners, stRunner) + + deps = append(deps, stFiles...) + } + testers = append(testers, testRunner) + } + return FullTester{ + ArtifactTesters: testers, + Dependencies: deps, + }, nil +} + +// TestDependencies returns the watch dependencies to the runner. +func (t FullTester) TestDependencies() []string { + return t.Dependencies +} + +// Test is the top level testing execution call. It serves as the +// entrypoint to all individual tests. +func (t FullTester) Test(out io.Writer, bRes []build.Artifact) error { + t.resolveArtifactImageTags(bRes) + for _, aTester := range t.ArtifactTesters { + if err := aTester.RunTests(); err != nil { + return err + } + } + return nil +} + +// RunTests serves as the entrypoint to each group of +// artifact-specific tests. +func (a *ArtifactTester) RunTests() error { + for _, t := range a.TestRunners { + if err := t.Test(a.ImageName); err != nil { + return err + } + } + return nil +} + +// replace original test artifact images with tagged build artifact images +func (t *FullTester) resolveArtifactImageTags(bRes []build.Artifact) { + for _, aTest := range t.ArtifactTesters { + for _, res := range bRes { + if aTest.ImageName == res.ImageName { + aTest.ImageName = res.Tag + } + } + } +} diff --git a/pkg/skaffold/test/types.go b/pkg/skaffold/test/types.go new file mode 100644 index 00000000000..524cdf0d1e2 --- /dev/null +++ b/pkg/skaffold/test/types.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 The Skaffold Authors + +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 test + +import ( + "io" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" +) + +// Tester is the top level test executor in Skaffold. +// A tester is really a collection of artifact-specific testers, +// each of which contains one or more TestRunners which implements +// a single test run. +type Tester interface { + Test(io.Writer, []build.Artifact) error + + TestDependencies() []string +} + +// FullTester serves as a holder for the individual artifact-specific +// testers. It exists so that the Tester interface can mimic the Builder/Deployer +// interface, so it can be called in a similar fashion from the Runner, while +// the FullTester actually handles the work. + +// FullTester should always be the ONLY implementation of the Tester interface; +// newly added testing implementations should implement the TestRunner interface. +type FullTester struct { + ArtifactTesters []*ArtifactTester + Dependencies []string +} + +// ArtifactTester is an artifact-specific test holder, which contains +// tests runners to run all specified tests on an individual artifact. +type ArtifactTester struct { + ImageName string + TestRunners []Runner +} + +// TestRunner is the lowest-level test executor in Skaffold, responsible for +// running a single test on a single artifact image and returning its result. +// Any new test type should implement this interface. +type Runner interface { + Test(image string) error +}