From 3674813c5aeb2e5c01736b93bd87808cb611b15c Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Tue, 18 Sep 2018 14:57:23 -0700 Subject: [PATCH 01/61] Add test runner This PR adds a test phase to the runner. If any tests are specified, they will always be executed after the build phase but before the deploy phase, and if any tests fail the deploy phase will be skipped. In the dev loop, only newly built images will be tested; any artifacts that were not modified by new changes will be skipped. A 'test' section is added to the config, where users can add all of their test configuration. Each test is specified at the artifact level, matched up by image names specified in the build artifacts. The test executor is designed to be pluggable, similar to the builders and deployers. The main exception is that any number of testers can be executed in a single run; a failure in any of them counts as a failure of the entire test run. This PR implements running structure tests, but adds the infrastructure to easily add more test types in the future. --- deploy/skaffold/Dockerfile | 5 +- .../profile_structure_test.yaml | 6 ++ examples/getting-started/skaffold.yaml | 9 +++ examples/getting-started/structure_test.yaml | 6 ++ pkg/skaffold/runner/runner.go | 39 +++++++++- pkg/skaffold/runner/timings.go | 18 ++++- pkg/skaffold/schema/latest/config.go | 9 +++ pkg/skaffold/schema/profiles.go | 1 + pkg/skaffold/schema/v1alpha3/config.go | 16 +++- pkg/skaffold/test/structure/structure.go | 39 ++++++++++ pkg/skaffold/test/structure/types.go | 27 +++++++ pkg/skaffold/test/test.go | 78 +++++++++++++++++++ pkg/skaffold/test/types.go | 63 +++++++++++++++ 13 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 examples/getting-started/profile_structure_test.yaml create mode 100644 examples/getting-started/structure_test.yaml create mode 100644 pkg/skaffold/test/structure/structure.go create mode 100644 pkg/skaffold/test/structure/types.go create mode 100644 pkg/skaffold/test/test.go create mode 100644 pkg/skaffold/test/types.go diff --git a/deploy/skaffold/Dockerfile b/deploy/skaffold/Dockerfile index f9921e50fdb..16370c60ded 100644 --- a/deploy/skaffold/Dockerfile +++ b/deploy/skaffold/Dockerfile @@ -47,6 +47,10 @@ RUN echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8 RUN apt-get update \ && apt-get install -y bazel +RUN curl -LO https://storage.googleapis.com/container-structure-test/latest/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 +98,3 @@ CMD ["make", "integration"] FROM runtime_deps as distribution COPY --from=integration /usr/bin/skaffold /usr/bin/skaffold - diff --git a/examples/getting-started/profile_structure_test.yaml b/examples/getting-started/profile_structure_test.yaml new file mode 100644 index 00000000000..6966d26930a --- /dev/null +++ b/examples/getting-started/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/examples/getting-started/skaffold.yaml b/examples/getting-started/skaffold.yaml index fb4d4adbc4c..48361be1a3e 100644 --- a/examples/getting-started/skaffold.yaml +++ b/examples/getting-started/skaffold.yaml @@ -3,6 +3,10 @@ kind: Config build: artifacts: - imageName: gcr.io/k8s-skaffold/skaffold-example +test: + - imageName: gcr.io/k8s-skaffold/skaffold-example + structureTests: + - structure_test.yaml deploy: kubectl: manifests: @@ -12,3 +16,8 @@ profiles: build: googleCloudBuild: projectId: k8s-skaffold + - name: test + test: + - imageName: gcr.io/k8s-skaffold/skaffold-example + structureTests: + - profile_structure_test.yaml diff --git a/examples/getting-started/structure_test.yaml b/examples/getting-started/structure_test.yaml new file mode 100644 index 00000000000..6b1223eae90 --- /dev/null +++ b/examples/getting-started/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/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..2c650e754d1 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..b02fb52f42a 100644 --- a/pkg/skaffold/schema/v1alpha3/config.go +++ b/pkg/skaffold/schema/v1alpha3/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:"imageName"` + StructureTests []string `yaml:"structureTests,omitempty"` +} + // DeployConfig contains all the configuration needed by the deploy steps type DeployConfig struct { DeployType `yaml:",inline"` @@ -201,9 +209,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 @@ -211,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/test/structure/structure.go b/pkg/skaffold/test/structure/structure.go new file mode 100644 index 00000000000..6179dfdf5a8 --- /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 *TestRunner) 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..af1fb804432 --- /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 TestRunner struct { + testFiles []string +} + +func NewStructureTestRunner(files []string) (*TestRunner, error) { + return &TestRunner{ + testFiles: files, + }, nil +} diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go new file mode 100644 index 00000000000..1fa7b1a768c --- /dev/null +++ b/pkg/skaffold/test/test.go @@ -0,0 +1,78 @@ +/* +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" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test/structure" + + "github.com/pkg/errors" +) + +// Test is the top level testing execution call. +// it should return back the list of artifacts that passed the tests +// in some form, so they can be deployed. It should also be responsible for +// logging which artifacts were not deployed because their tests failed. +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 +} + +// 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 *[]v1alpha3.TestCase) (Tester, error) { + testers := []*ArtifactTester{} + deps := []string{} + for _, testCase := range *testCases { + testRunner := &ArtifactTester{ + ImageName: testCase.ImageName, + } + if testCase.StructureTests != nil { + stRunner, err := structure.NewStructureTestRunner(testCase.StructureTests) + if err != nil { + return FullTester{}, errors.Wrap(err, "retrieving structure test runner") + } + testRunner.TestRunners = append(testRunner.TestRunners, stRunner) + deps = append(deps, testCase.StructureTests...) + } + testers = append(testers, testRunner) + } + return FullTester{ + ArtifactTesters: testers, + Dependencies: deps, + }, 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..6cb9f3a0aae --- /dev/null +++ b/pkg/skaffold/test/types.go @@ -0,0 +1,63 @@ +/* +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 ( + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" + "io" +) + +// 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 +} + +type FullTester struct { + ArtifactTesters []*ArtifactTester + Dependencies []string +} + +func (t FullTester) TestDependencies() []string { + return t.Dependencies +} + +// 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 []TestRunner +} + +func (a *ArtifactTester) RunTests() error { + for _, t := range a.TestRunners { + if err := t.Test(a.ImageName); err != nil { + return err + } + } + return nil +} + +// TestRunner is the lowest-level test executor in Skaffold, responsible for +// running a single test on a single artifact image and returning its result. +type TestRunner interface { + Test(image string) error +} From d888488e5b0318338570e1f933d0d5cdf0b37fdb Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Fri, 21 Sep 2018 11:57:31 -0700 Subject: [PATCH 02/61] cleanup and organization --- pkg/skaffold/test/test.go | 42 +++++++++++++++++++++++++------------- pkg/skaffold/test/types.go | 24 +++++++++------------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go index 1fa7b1a768c..bd37056106d 100644 --- a/pkg/skaffold/test/test.go +++ b/pkg/skaffold/test/test.go @@ -26,20 +26,6 @@ import ( "github.com/pkg/errors" ) -// Test is the top level testing execution call. -// it should return back the list of artifacts that passed the tests -// in some form, so they can be deployed. It should also be responsible for -// logging which artifacts were not deployed because their tests failed. -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 -} - // 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. @@ -66,6 +52,34 @@ func NewTester(testCases *[]v1alpha3.TestCase) (Tester, error) { }, 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 { diff --git a/pkg/skaffold/test/types.go b/pkg/skaffold/test/types.go index 6cb9f3a0aae..a4938e22f88 100644 --- a/pkg/skaffold/test/types.go +++ b/pkg/skaffold/test/types.go @@ -17,8 +17,9 @@ limitations under the License. package test import ( - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "io" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" ) // Tester is the top level test executor in Skaffold. @@ -31,15 +32,18 @@ type Tester interface { 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 } -func (t FullTester) TestDependencies() []string { - return t.Dependencies -} - // ArtifactTester is an artifact-specific test holder, which contains // tests runners to run all specified tests on an individual artifact. type ArtifactTester struct { @@ -47,17 +51,9 @@ type ArtifactTester struct { TestRunners []TestRunner } -func (a *ArtifactTester) RunTests() error { - for _, t := range a.TestRunners { - if err := t.Test(a.ImageName); err != nil { - return err - } - } - return nil -} - // 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 TestRunner interface { Test(image string) error } From b0a83d6ae8ff000e7cc33fb27afe02bb693981f6 Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Fri, 21 Sep 2018 12:43:21 -0700 Subject: [PATCH 03/61] add tests --- pkg/skaffold/runner/runner_test.go | 69 +++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 9276893851c..02c6fa6a617 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,7 +240,7 @@ func TestNewForConfig(t *testing.T) { testutil.CheckError(t, test.shouldErr, err) if cfg != nil { - b, d := WithTimings(test.expectedBuilder, test.expectedDeployer) + b, _, d := WithTimings(test.expectedBuilder, test.expectedTester, test.expectedDeployer) testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, b, cfg.Builder) testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, d, cfg.Deployer) @@ -232,6 +254,7 @@ func TestRun(t *testing.T) { description string config *latest.SkaffoldConfig builder build.Builder + tester test.Tester deployer deploy.Deployer shouldErr bool }{ @@ -239,6 +262,7 @@ func TestRun(t *testing.T) { description: "run no error", config: &latest.SkaffoldConfig{}, builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{}, }, { @@ -247,6 +271,7 @@ func TestRun(t *testing.T) { builder: &TestBuilder{ errors: []error{fmt.Errorf("")}, }, + tester: &TestTester{}, shouldErr: true, }, { @@ -261,17 +286,42 @@ func TestRun(t *testing.T) { }, }, builder: &TestBuilder{}, + tester: &TestTester{}, deployer: &TestDeployer{ errors: []error{fmt.Errorf("")}, }, shouldErr: true, }, + { + description: "run test error", + config: &v1alpha3.SkaffoldConfig{ + Build: v1alpha3.BuildConfig{ + Artifacts: []*v1alpha3.Artifact{ + { + ImageName: "test", + }, + }, + }, + Test: []v1alpha3.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 +340,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 +357,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), + 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 +394,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 +405,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 +425,7 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { defer resetClient() builder := &TestBuilder{} + tester := &TestTester{} deployer := &TestDeployer{} artifacts := []*latest.Artifact{ {ImageName: "image1"}, @@ -368,6 +434,7 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { runner := &SkaffoldRunner{ Builder: builder, + Tester: tester, Deployer: deployer, opts: &config.SkaffoldOptions{}, } From a96cbfbf3931c043edd848fced3f33a77fcd2624 Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Fri, 21 Sep 2018 12:53:09 -0700 Subject: [PATCH 04/61] lint --- pkg/skaffold/test/structure/structure.go | 2 +- pkg/skaffold/test/structure/types.go | 6 +++--- pkg/skaffold/test/types.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/skaffold/test/structure/structure.go b/pkg/skaffold/test/structure/structure.go index 6179dfdf5a8..fb1e49aa401 100644 --- a/pkg/skaffold/test/structure/structure.go +++ b/pkg/skaffold/test/structure/structure.go @@ -25,7 +25,7 @@ import ( ) // Test is the entrypoint for running structure tests -func (tr *TestRunner) Test(image string) error { +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 { diff --git a/pkg/skaffold/test/structure/types.go b/pkg/skaffold/test/structure/types.go index af1fb804432..dddf0618b5a 100644 --- a/pkg/skaffold/test/structure/types.go +++ b/pkg/skaffold/test/structure/types.go @@ -16,12 +16,12 @@ limitations under the License. package structure -type TestRunner struct { +type Runner struct { testFiles []string } -func NewStructureTestRunner(files []string) (*TestRunner, error) { - return &TestRunner{ +func NewStructureTestRunner(files []string) (*Runner, error) { + return &Runner{ testFiles: files, }, nil } diff --git a/pkg/skaffold/test/types.go b/pkg/skaffold/test/types.go index a4938e22f88..524cdf0d1e2 100644 --- a/pkg/skaffold/test/types.go +++ b/pkg/skaffold/test/types.go @@ -48,12 +48,12 @@ type FullTester struct { // tests runners to run all specified tests on an individual artifact. type ArtifactTester struct { ImageName string - TestRunners []TestRunner + 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 TestRunner interface { +type Runner interface { Test(image string) error } From 9c5902914c3d692f41ee9c7298c392b5d40a03b7 Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Mon, 24 Sep 2018 11:30:28 -0700 Subject: [PATCH 05/61] check runner.Tester in unit tests. pin cst binary to version in Dockerfile --- deploy/skaffold/Dockerfile | 3 ++- pkg/skaffold/runner/runner_test.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/deploy/skaffold/Dockerfile b/deploy/skaffold/Dockerfile index 16370c60ded..bcf4e691eb4 100644 --- a/deploy/skaffold/Dockerfile +++ b/deploy/skaffold/Dockerfile @@ -47,7 +47,8 @@ RUN echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8 RUN apt-get update \ && apt-get install -y bazel -RUN curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 \ +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 diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 02c6fa6a617..0db39ae2590 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -240,9 +240,10 @@ func TestNewForConfig(t *testing.T) { testutil.CheckError(t, test.shouldErr, err) if cfg != nil { - b, _, d := WithTimings(test.expectedBuilder, test.expectedTester, 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) } }) From f703c8dd806b5ea8a6301a0b12223b6987ddf75a Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Tue, 2 Oct 2018 10:30:57 -0700 Subject: [PATCH 06/61] move config changes to v1alpha4 and examples to integration/examples --- examples/getting-started/skaffold.yaml | 9 --------- .../getting-started/profile_structure_test.yaml | 0 integration/examples/getting-started/skaffold.yaml | 9 +++++++++ .../examples}/getting-started/structure_test.yaml | 0 pkg/skaffold/schema/latest/config.go | 2 +- pkg/skaffold/schema/v1alpha3/config.go | 9 --------- pkg/skaffold/test/test.go | 4 ++-- 7 files changed, 12 insertions(+), 21 deletions(-) rename {examples => integration/examples}/getting-started/profile_structure_test.yaml (100%) rename {examples => integration/examples}/getting-started/structure_test.yaml (100%) diff --git a/examples/getting-started/skaffold.yaml b/examples/getting-started/skaffold.yaml index 48361be1a3e..fb4d4adbc4c 100644 --- a/examples/getting-started/skaffold.yaml +++ b/examples/getting-started/skaffold.yaml @@ -3,10 +3,6 @@ kind: Config build: artifacts: - imageName: gcr.io/k8s-skaffold/skaffold-example -test: - - imageName: gcr.io/k8s-skaffold/skaffold-example - structureTests: - - structure_test.yaml deploy: kubectl: manifests: @@ -16,8 +12,3 @@ profiles: build: googleCloudBuild: projectId: k8s-skaffold - - name: test - test: - - imageName: gcr.io/k8s-skaffold/skaffold-example - structureTests: - - profile_structure_test.yaml diff --git a/examples/getting-started/profile_structure_test.yaml b/integration/examples/getting-started/profile_structure_test.yaml similarity index 100% rename from examples/getting-started/profile_structure_test.yaml rename to integration/examples/getting-started/profile_structure_test.yaml diff --git a/integration/examples/getting-started/skaffold.yaml b/integration/examples/getting-started/skaffold.yaml index 785d94052de..77ade13d99b 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: + - structure_test.yaml deploy: kubectl: manifests: @@ -12,3 +16,8 @@ profiles: build: googleCloudBuild: projectId: k8s-skaffold + - name: test + test: + - image: gcr.io/k8s-skaffold/skaffold-example + structureTests: + - profile_structure_test.yaml diff --git a/examples/getting-started/structure_test.yaml b/integration/examples/getting-started/structure_test.yaml similarity index 100% rename from examples/getting-started/structure_test.yaml rename to integration/examples/getting-started/structure_test.yaml diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 2c650e754d1..c9f7efbc1a8 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -118,7 +118,7 @@ type KanikoBuild struct { } // TestCase is a struct containing all the specified test -// configuration for an image +// configuration for an image. type TestCase struct { ImageName string `yaml:"image"` StructureTests []string `yaml:"structureTests,omitempty"` diff --git a/pkg/skaffold/schema/v1alpha3/config.go b/pkg/skaffold/schema/v1alpha3/config.go index b02fb52f42a..35cf08cff54 100644 --- a/pkg/skaffold/schema/v1alpha3/config.go +++ b/pkg/skaffold/schema/v1alpha3/config.go @@ -34,7 +34,6 @@ 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"` } @@ -117,13 +116,6 @@ 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:"imageName"` - StructureTests []string `yaml:"structureTests,omitempty"` -} - // DeployConfig contains all the configuration needed by the deploy steps type DeployConfig struct { DeployType `yaml:",inline"` @@ -220,7 +212,6 @@ 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/test/test.go b/pkg/skaffold/test/test.go index bd37056106d..eb3c4a3d55f 100644 --- a/pkg/skaffold/test/test.go +++ b/pkg/skaffold/test/test.go @@ -20,7 +20,7 @@ import ( "io" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + latest "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha4" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test/structure" "github.com/pkg/errors" @@ -29,7 +29,7 @@ import ( // 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 *[]v1alpha3.TestCase) (Tester, error) { +func NewTester(testCases *[]latest.TestCase) (Tester, error) { testers := []*ArtifactTester{} deps := []string{} for _, testCase := range *testCases { From 50e565af40d711a01490335a94c38d96341bc4c3 Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Tue, 2 Oct 2018 11:52:02 -0700 Subject: [PATCH 07/61] Fix tests --- pkg/skaffold/runner/runner_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 0db39ae2590..7c1ae070a5d 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -295,15 +295,15 @@ func TestRun(t *testing.T) { }, { description: "run test error", - config: &v1alpha3.SkaffoldConfig{ - Build: v1alpha3.BuildConfig{ - Artifacts: []*v1alpha3.Artifact{ + config: &latest.SkaffoldConfig{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ { ImageName: "test", }, }, }, - Test: []v1alpha3.TestCase{ + Test: []latest.TestCase{ { ImageName: "test", StructureTests: []string{"fake_file.yaml"}, From 7907f24f7eeb38f4298ebba98713dfa828ff6b61 Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Tue, 2 Oct 2018 14:11:50 -0700 Subject: [PATCH 08/61] fix rebase --- pkg/skaffold/runner/runner_test.go | 2 +- pkg/skaffold/test/test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 7c1ae070a5d..2260d97e414 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -371,7 +371,7 @@ func TestDev(t *testing.T) { tester: &TestTester{ errors: []error{fmt.Errorf("")}, }, - watcherFactory: NewWatcherFactory(nil), + watcherFactory: NewWatcherFactory(nil, nil), shouldErr: true, }, { diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go index eb3c4a3d55f..4c434c25137 100644 --- a/pkg/skaffold/test/test.go +++ b/pkg/skaffold/test/test.go @@ -20,7 +20,7 @@ import ( "io" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" - latest "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha4" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test/structure" "github.com/pkg/errors" From a7b9412b830bc82ab2b006a4fe4b8e5938e745c0 Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Tue, 2 Oct 2018 15:40:22 -0700 Subject: [PATCH 09/61] add support for wildcards when specifying test files --- .../examples/getting-started/skaffold.yaml | 4 ++-- .../{ => test}/profile_structure_test.yaml | 0 .../{ => test}/structure_test.yaml | 0 pkg/skaffold/test/test.go | 16 ++++++++++++++-- 4 files changed, 16 insertions(+), 4 deletions(-) rename integration/examples/getting-started/{ => test}/profile_structure_test.yaml (100%) rename integration/examples/getting-started/{ => test}/structure_test.yaml (100%) diff --git a/integration/examples/getting-started/skaffold.yaml b/integration/examples/getting-started/skaffold.yaml index 77ade13d99b..7ace8c5b6f8 100644 --- a/integration/examples/getting-started/skaffold.yaml +++ b/integration/examples/getting-started/skaffold.yaml @@ -6,7 +6,7 @@ build: test: - image: gcr.io/k8s-skaffold/skaffold-example structureTests: - - structure_test.yaml + - ./test/* deploy: kubectl: manifests: @@ -20,4 +20,4 @@ profiles: test: - image: gcr.io/k8s-skaffold/skaffold-example structureTests: - - profile_structure_test.yaml + - ./test/profile_structure_test.yaml diff --git a/integration/examples/getting-started/profile_structure_test.yaml b/integration/examples/getting-started/test/profile_structure_test.yaml similarity index 100% rename from integration/examples/getting-started/profile_structure_test.yaml rename to integration/examples/getting-started/test/profile_structure_test.yaml diff --git a/integration/examples/getting-started/structure_test.yaml b/integration/examples/getting-started/test/structure_test.yaml similarity index 100% rename from integration/examples/getting-started/structure_test.yaml rename to integration/examples/getting-started/test/structure_test.yaml diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go index 4c434c25137..e75861411e0 100644 --- a/pkg/skaffold/test/test.go +++ b/pkg/skaffold/test/test.go @@ -18,10 +18,12 @@ 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" ) @@ -32,17 +34,27 @@ import ( 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 { - stRunner, err := structure.NewStructureTestRunner(testCase.StructureTests) + 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, testCase.StructureTests...) + + deps = append(deps, stFiles...) } testers = append(testers, testRunner) } From bf4bc7d132acb220297267c1496e10db35337613 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Wed, 3 Oct 2018 09:50:20 +0200 Subject: [PATCH 10/61] Print test logs and pass a context Also fix a few comments Signed-off-by: David Gageot --- pkg/skaffold/runner/runner.go | 8 ++-- pkg/skaffold/runner/runner_test.go | 2 +- pkg/skaffold/runner/timings.go | 51 +++++++++++++----------- pkg/skaffold/test/structure/structure.go | 25 +++++++----- pkg/skaffold/test/test.go | 23 +++++++---- pkg/skaffold/test/types.go | 9 +++-- 6 files changed, 69 insertions(+), 49 deletions(-) diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 5814567a157..a02cccb994f 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -190,7 +190,7 @@ func (r *SkaffoldRunner) Run(ctx context.Context, out io.Writer, artifacts []*la return errors.Wrap(err, "build step") } - if err = r.Test(out, bRes); err != nil { + if err = r.Test(ctx, out, bRes); err != nil { return errors.Wrap(err, "test step") } @@ -257,7 +257,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } r.updateBuiltImages(imageList, bRes) - if err := r.Test(out, bRes); err != nil { + if err := r.Test(ctx, out, bRes); err != nil { logrus.Warnln("Skipping Deploy due to failed tests:", err) return nil } @@ -267,7 +267,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil } case changed.needsRedeploy: - if err := r.Test(out, r.builds); err != nil { + if err := r.Test(ctx, out, r.builds); err != nil { logrus.Warnln("Skipping Deploy due to failed tests:", err) return nil } @@ -330,7 +330,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } r.updateBuiltImages(imageList, bRes) - if err := r.Test(out, bRes); err != nil { + if err := r.Test(ctx, out, bRes); err != nil { return nil, errors.Wrap(err, "exiting dev mode because the first test run failed") } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 2260d97e414..515aa657362 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -70,7 +70,7 @@ type TestTester struct { errors []error } -func (t *TestTester) Test(out io.Writer, builds []build.Artifact) error { +func (t *TestTester) Test(ctx context.Context, out io.Writer, builds []build.Artifact) error { if len(t.errors) > 0 { err := t.errors[0] t.errors = t.errors[1:] diff --git a/pkg/skaffold/runner/timings.go b/pkg/skaffold/runner/timings.go index 0bf13b0b000..a4b880dee8c 100644 --- a/pkg/skaffold/runner/timings.go +++ b/pkg/skaffold/runner/timings.go @@ -21,6 +21,8 @@ import ( "io" "time" + "k8s.io/apimachinery/pkg/labels" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" @@ -46,26 +48,34 @@ type withTimings struct { deploy.Deployer } +func (w withTimings) Labels() map[string]string { + return labels.Merge(w.Builder.Labels(), w.Deployer.Labels()) +} + func (w withTimings) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) { start := time.Now() color.Default.Fprintln(out, "Starting build...") bRes, err := w.Builder.Build(ctx, out, tagger, artifacts) - if err == nil { - color.Default.Fprintln(out, "Build complete in", time.Since(start)) + if err != nil { + return nil, err } - return bRes, err + + color.Default.Fprintln(out, "Build complete in", time.Since(start)) + return bRes, nil } -func (w withTimings) Test(out io.Writer, builds []build.Artifact) error { +func (w withTimings) Test(ctx context.Context, 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)) + err := w.Tester.Test(ctx, out, builds) + if err != nil { + return err } - return err + + color.Default.Fprintln(out, "Test complete in", time.Since(start)) + return nil } func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact) ([]deploy.Artifact, error) { @@ -73,21 +83,12 @@ func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.A color.Default.Fprintln(out, "Starting deploy...") dRes, err := w.Deployer.Deploy(ctx, out, builds) - if err == nil { - color.Default.Fprintln(out, "Deploy complete in", time.Since(start)) + if err != nil { + return nil, err } - return dRes, err -} -func (w withTimings) Labels() map[string]string { - labels := map[string]string{} - for k, v := range w.Builder.Labels() { - labels[k] = v - } - for k, v := range w.Deployer.Labels() { - labels[k] = v - } - return labels + color.Default.Fprintln(out, "Deploy complete in", time.Since(start)) + return dRes, nil } func (w withTimings) Cleanup(ctx context.Context, out io.Writer) error { @@ -95,8 +96,10 @@ func (w withTimings) Cleanup(ctx context.Context, out io.Writer) error { color.Default.Fprintln(out, "Cleaning up...") err := w.Deployer.Cleanup(ctx, out) - if err == nil { - color.Default.Fprintln(out, "Cleanup complete in", time.Since(start)) + if err != nil { + return err } - return err + + color.Default.Fprintln(out, "Cleanup complete in", time.Since(start)) + return nil } diff --git a/pkg/skaffold/test/structure/structure.go b/pkg/skaffold/test/structure/structure.go index fb1e49aa401..63f226459c3 100644 --- a/pkg/skaffold/test/structure/structure.go +++ b/pkg/skaffold/test/structure/structure.go @@ -17,23 +17,30 @@ limitations under the License. package structure import ( + "context" + "io" "os/exec" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" - + "github.com/pkg/errors" "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} +func (tr *Runner) Test(ctx context.Context, out io.Writer, image string) error { + logrus.Infof("Running structure tests for files %v", tr.testFiles) + + args := []string{"test", "-v", "warn", "--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 + cmd := exec.CommandContext(ctx, "container-structure-test", args...) + cmd.Stdout = out + cmd.Stderr = out + + if err := cmd.Run(); err != nil { + return errors.Wrap(err, "running container-structure-test") + } + + return nil } diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go index e75861411e0..78a0af1379c 100644 --- a/pkg/skaffold/test/test.go +++ b/pkg/skaffold/test/test.go @@ -17,6 +17,7 @@ limitations under the License. package test import ( + "context" "io" "os" @@ -32,32 +33,38 @@ import ( // 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") } + + testers := []*ArtifactTester{} + deps := []string{} + 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) + testRunner.TestRunners = append(testRunner.TestRunners, stRunner) deps = append(deps, stFiles...) } + testers = append(testers, testRunner) } + return FullTester{ ArtifactTesters: testers, Dependencies: deps, @@ -71,10 +78,11 @@ func (t FullTester) TestDependencies() []string { // 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 { +func (t FullTester) Test(ctx context.Context, out io.Writer, bRes []build.Artifact) error { t.resolveArtifactImageTags(bRes) + for _, aTester := range t.ArtifactTesters { - if err := aTester.RunTests(); err != nil { + if err := aTester.RunTests(ctx, out); err != nil { return err } } @@ -83,12 +91,13 @@ func (t FullTester) Test(out io.Writer, bRes []build.Artifact) error { // RunTests serves as the entrypoint to each group of // artifact-specific tests. -func (a *ArtifactTester) RunTests() error { +func (a *ArtifactTester) RunTests(ctx context.Context, out io.Writer) error { for _, t := range a.TestRunners { - if err := t.Test(a.ImageName); err != nil { + if err := t.Test(ctx, out, a.ImageName); err != nil { return err } } + return nil } diff --git a/pkg/skaffold/test/types.go b/pkg/skaffold/test/types.go index 524cdf0d1e2..9ef087cf84f 100644 --- a/pkg/skaffold/test/types.go +++ b/pkg/skaffold/test/types.go @@ -17,6 +17,7 @@ limitations under the License. package test import ( + "context" "io" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" @@ -27,7 +28,7 @@ import ( // each of which contains one or more TestRunners which implements // a single test run. type Tester interface { - Test(io.Writer, []build.Artifact) error + Test(context.Context, io.Writer, []build.Artifact) error TestDependencies() []string } @@ -38,7 +39,7 @@ type Tester interface { // 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. +// newly added testing implementations should implement the Runner interface. type FullTester struct { ArtifactTesters []*ArtifactTester Dependencies []string @@ -51,9 +52,9 @@ type ArtifactTester struct { TestRunners []Runner } -// TestRunner is the lowest-level test executor in Skaffold, responsible for +// Runner 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 + Test(ctx context.Context, out io.Writer, image string) error } From f2465c5c23cb057fa2bdaea3773290130b641f9d Mon Sep 17 00:00:00 2001 From: David Gageot Date: Wed, 3 Oct 2018 10:23:26 +0200 Subject: [PATCH 11/61] skaffold dev now detects new test cases Fix #1079 Signed-off-by: David Gageot --- pkg/skaffold/runner/runner.go | 2 +- pkg/skaffold/runner/runner_test.go | 4 +- pkg/skaffold/test/structure/types.go | 5 +- pkg/skaffold/test/test.go | 87 +++++++++++++--------------- pkg/skaffold/test/types.go | 14 ++--- 5 files changed, 49 insertions(+), 63 deletions(-) diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index a02cccb994f..542dee9acf1 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -301,7 +301,7 @@ 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() ([]string, error) { return r.TestDependencies() }, func(watch.Events) { changed.needsRedeploy = true }, ); err != nil { return nil, errors.Wrap(err, "watching test files") diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 515aa657362..e0bcf6430f6 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -79,8 +79,8 @@ func (t *TestTester) Test(ctx context.Context, out io.Writer, builds []build.Art return nil } -func (t *TestTester) TestDependencies() []string { - return []string{} +func (t *TestTester) TestDependencies() ([]string, error) { + return nil, nil } type TestDeployer struct { diff --git a/pkg/skaffold/test/structure/types.go b/pkg/skaffold/test/structure/types.go index dddf0618b5a..11727061213 100644 --- a/pkg/skaffold/test/structure/types.go +++ b/pkg/skaffold/test/structure/types.go @@ -20,8 +20,9 @@ type Runner struct { testFiles []string } -func NewStructureTestRunner(files []string) (*Runner, error) { +// NewRunner creates a new structure.Runner. +func NewRunner(files []string) *Runner { return &Runner{ testFiles: files, - }, nil + } } diff --git a/pkg/skaffold/test/test.go b/pkg/skaffold/test/test.go index 78a0af1379c..a46b7bf499a 100644 --- a/pkg/skaffold/test/test.go +++ b/pkg/skaffold/test/test.go @@ -39,75 +39,66 @@ func NewTester(testCases *[]latest.TestCase) (Tester, error) { return nil, errors.Wrap(err, "finding current directory") } - testers := []*ArtifactTester{} - deps := []string{} - - for _, testCase := range *testCases { - testRunner := &ArtifactTester{ - ImageName: testCase.ImageName, - } + return FullTester{ + testCases: testCases, + workingDir: cwd, + }, nil +} - if testCase.StructureTests != nil { - stFiles, err := util.ExpandPathsGlob(cwd, testCase.StructureTests) - if err != nil { - return FullTester{}, errors.Wrap(err, "expanding test file paths") - } +// TestDependencies returns the watch dependencies to the runner. +func (t FullTester) TestDependencies() ([]string, error) { + var deps []string - stRunner, err := structure.NewStructureTestRunner(stFiles) - if err != nil { - return FullTester{}, errors.Wrap(err, "retrieving structure test runner") - } + for _, test := range *t.testCases { + if test.StructureTests == nil { + continue + } - testRunner.TestRunners = append(testRunner.TestRunners, stRunner) - deps = append(deps, stFiles...) + files, err := util.ExpandPathsGlob(t.workingDir, test.StructureTests) + if err != nil { + return nil, errors.Wrap(err, "expanding test file paths") } - testers = append(testers, testRunner) + deps = append(deps, files...) } - return FullTester{ - ArtifactTesters: testers, - Dependencies: deps, - }, nil -} - -// TestDependencies returns the watch dependencies to the runner. -func (t FullTester) TestDependencies() []string { - return t.Dependencies + return deps, nil } // Test is the top level testing execution call. It serves as the // entrypoint to all individual tests. func (t FullTester) Test(ctx context.Context, out io.Writer, bRes []build.Artifact) error { - t.resolveArtifactImageTags(bRes) - - for _, aTester := range t.ArtifactTesters { - if err := aTester.RunTests(ctx, out); err != nil { - return err + for _, test := range *t.testCases { + if err := t.runStructureTests(ctx, out, bRes, test); err != nil { + return errors.Wrap(err, "running structure tests") } } + return nil } -// RunTests serves as the entrypoint to each group of -// artifact-specific tests. -func (a *ArtifactTester) RunTests(ctx context.Context, out io.Writer) error { - for _, t := range a.TestRunners { - if err := t.Test(ctx, out, a.ImageName); err != nil { - return err - } +func (t FullTester) runStructureTests(ctx context.Context, out io.Writer, bRes []build.Artifact, testCase latest.TestCase) error { + if len(testCase.StructureTests) == 0 { + return nil } - return nil + files, err := util.ExpandPathsGlob(t.workingDir, testCase.StructureTests) + if err != nil { + return errors.Wrap(err, "expanding test file paths") + } + + runner := structure.NewRunner(files) + fqn := resolveArtifactImageTag(testCase.ImageName, bRes) + + return runner.Test(ctx, out, fqn) } -// 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 - } +func resolveArtifactImageTag(imageName string, bRes []build.Artifact) string { + for _, res := range bRes { + if imageName == res.ImageName { + return res.Tag } } + + return imageName } diff --git a/pkg/skaffold/test/types.go b/pkg/skaffold/test/types.go index 9ef087cf84f..e23d46a6324 100644 --- a/pkg/skaffold/test/types.go +++ b/pkg/skaffold/test/types.go @@ -21,6 +21,7 @@ import ( "io" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" ) // Tester is the top level test executor in Skaffold. @@ -30,7 +31,7 @@ import ( type Tester interface { Test(context.Context, io.Writer, []build.Artifact) error - TestDependencies() []string + TestDependencies() ([]string, error) } // FullTester serves as a holder for the individual artifact-specific @@ -41,15 +42,8 @@ type Tester interface { // FullTester should always be the ONLY implementation of the Tester interface; // newly added testing implementations should implement the Runner 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 + testCases *[]latest.TestCase + workingDir string } // Runner is the lowest-level test executor in Skaffold, responsible for From 62b5ff6d326decdfe8e21a96163796ee8fe56f37 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Wed, 3 Oct 2018 17:20:20 +0200 Subject: [PATCH 12/61] Implement a manual trigger for watch mode Signed-off-by: David Gageot --- cmd/skaffold/app/cmd/dev.go | 1 + pkg/skaffold/config/options.go | 1 + pkg/skaffold/runner/runner.go | 15 ++-- pkg/skaffold/runner/runner_test.go | 26 +++++-- pkg/skaffold/watch/triggers.go | 111 +++++++++++++++++++++++++++++ pkg/skaffold/watch/watch.go | 14 ++-- pkg/skaffold/watch/watch_test.go | 6 +- 7 files changed, 154 insertions(+), 20 deletions(-) create mode 100644 pkg/skaffold/watch/triggers.go diff --git a/cmd/skaffold/app/cmd/dev.go b/cmd/skaffold/app/cmd/dev.go index aed9a86299b..3e3642e0460 100644 --- a/cmd/skaffold/app/cmd/dev.go +++ b/cmd/skaffold/app/cmd/dev.go @@ -39,6 +39,7 @@ func NewCmdDev(out io.Writer) *cobra.Command { AddRunDevFlags(cmd) AddDevFlags(cmd) cmd.Flags().BoolVar(&opts.TailDev, "tail", true, "Stream logs from deployed objects") + cmd.Flags().StringVar(&opts.Trigger, "trigger", "polling", "How are changes detected? (polling or manual)") return cmd } diff --git a/pkg/skaffold/config/options.go b/pkg/skaffold/config/options.go index a067e279bfb..40b5c2bd482 100644 --- a/pkg/skaffold/config/options.go +++ b/pkg/skaffold/config/options.go @@ -33,6 +33,7 @@ type SkaffoldOptions struct { CustomTag string Namespace string Watch []string + Trigger string WatchPollInterval int } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 5814567a157..87f017b85ef 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -23,7 +23,6 @@ import ( "os" "path/filepath" "strings" - "time" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/bazel" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" @@ -31,7 +30,6 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" @@ -54,6 +52,7 @@ type SkaffoldRunner struct { deploy.Deployer test.Tester tag.Tagger + watch.Trigger opts *config.SkaffoldOptions watchFactory watch.Factory @@ -94,11 +93,17 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldConfig) (*Sk deployer = WithNotification(deployer) } + trigger, err := watch.NewTrigger(opts) + if err != nil { + return nil, errors.Wrap(err, "creating watch trigger") + } + return &SkaffoldRunner{ Builder: builder, Tester: tester, Deployer: deployer, Tagger: tagger, + Trigger: trigger, opts: opts, watchFactory: watch.NewWatcher, }, nil @@ -239,7 +244,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la logger.Mute() defer func() { changed.reset() - color.Default.Fprintln(out, "Watching for changes...") + r.Trigger.WatchForChanges(out) if !hasError { logger.Unmute() } @@ -352,8 +357,8 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } } - pollInterval := time.Duration(r.opts.WatchPollInterval) * time.Millisecond - return nil, watcher.Run(ctx, pollInterval, onChange) + r.Trigger.WatchForChanges(out) + return nil, watcher.Run(ctx, r.Trigger, onChange) } func (r *SkaffoldRunner) shouldWatch(artifact *latest.Artifact) bool { diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 2260d97e414..350b27e5544 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -22,7 +22,6 @@ import ( "io" "io/ioutil" "testing" - "time" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" @@ -136,7 +135,7 @@ func (t *TestWatcher) Register(deps func() ([]string, error), onChange func(watc return nil } -func (t *TestWatcher) Run(ctx context.Context, pollInterval time.Duration, onChange func() error) error { +func (t *TestWatcher) Run(ctx context.Context, trigger watch.Trigger, onChange func() error) error { evts := watch.Events{} if t.events != nil { evts = t.events[0] @@ -236,7 +235,9 @@ func TestNewForConfig(t *testing.T) { } for _, test := range tests { t.Run(test.description, func(t *testing.T) { - cfg, err := NewForConfig(&config.SkaffoldOptions{}, test.config) + cfg, err := NewForConfig(&config.SkaffoldOptions{ + Trigger: "polling", + }, test.config) testutil.CheckError(t, test.shouldErr, err) if cfg != nil { @@ -404,15 +405,21 @@ func TestDev(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { + opts := &config.SkaffoldOptions{ + WatchPollInterval: 100, + Trigger: "polling", + } + + trigger, _ := watch.NewTrigger(opts) + runner := &SkaffoldRunner{ Builder: test.builder, Tester: test.tester, Deployer: test.deployer, Tagger: &tag.ChecksumTagger{}, + Trigger: trigger, watchFactory: test.watcherFactory, - opts: &config.SkaffoldOptions{ - WatchPollInterval: 100, - }, + opts: opts, } _, err := runner.Dev(context.Background(), ioutil.Discard, nil) @@ -425,9 +432,13 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { kubernetes.Client = fakeGetClient defer resetClient() + opts := &config.SkaffoldOptions{ + Trigger: "polling", + } builder := &TestBuilder{} tester := &TestTester{} deployer := &TestDeployer{} + trigger, _ := watch.NewTrigger(opts) artifacts := []*latest.Artifact{ {ImageName: "image1"}, {ImageName: "image2"}, @@ -437,7 +448,8 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { Builder: builder, Tester: tester, Deployer: deployer, - opts: &config.SkaffoldOptions{}, + Trigger: trigger, + opts: opts, } ctx := context.Background() diff --git a/pkg/skaffold/watch/triggers.go b/pkg/skaffold/watch/triggers.go new file mode 100644 index 00000000000..bce847064da --- /dev/null +++ b/pkg/skaffold/watch/triggers.go @@ -0,0 +1,111 @@ +/* +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 watch + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" + "github.com/sirupsen/logrus" +) + +// Trigger describes a mechanism that triggers the watch. +type Trigger interface { + Start() (<-chan bool, func()) + WatchForChanges(io.Writer) + Debounce() bool +} + +// NewTrigger creates a new trigger. +func NewTrigger(opts *config.SkaffoldOptions) (Trigger, error) { + switch strings.ToLower(opts.Trigger) { + case "polling": + return &pollTrigger{ + Interval: time.Duration(opts.WatchPollInterval) * time.Millisecond, + }, nil + case "manual": + return &manualTrigger{}, nil + default: + return nil, fmt.Errorf("unsupported type of trigger: %s", opts.Trigger) + } +} + +// pollTrigger watches for changes on a given interval of time. +type pollTrigger struct { + Interval time.Duration +} + +// Debounce tells the watcher to debounce rapid sequence of changes. +func (t *pollTrigger) Debounce() bool { + return true +} + +func (t *pollTrigger) WatchForChanges(out io.Writer) { + color.Yellow.Fprintf(out, "Watching for changes every %v...\n", t.Interval) +} + +// Start starts a timer. +func (t *pollTrigger) Start() (<-chan bool, func()) { + trigger := make(chan bool) + + ticker := time.NewTicker(t.Interval) + go func() { + for { + <-ticker.C + trigger <- true + } + }() + + return trigger, ticker.Stop +} + +// manualTrigger watches for changes when the user presses a key. +type manualTrigger struct { +} + +// Debounce tells the watcher to not debounce rapid sequence of changes. +func (t *manualTrigger) Debounce() bool { + return false +} + +func (t *manualTrigger) WatchForChanges(out io.Writer) { + color.Yellow.Fprintln(out, "Press any key to rebuild/redeploy the changes") +} + +// Start starts listening to pressed keys. +func (t *manualTrigger) Start() (<-chan bool, func()) { + trigger := make(chan bool) + + reader := bufio.NewReader(os.Stdin) + go func() { + for { + _, _, err := reader.ReadRune() + if err != nil { + logrus.Debugf("manual trigger error: %s", err) + } + trigger <- true + } + }() + + return trigger, func() {} +} diff --git a/pkg/skaffold/watch/watch.go b/pkg/skaffold/watch/watch.go index 6b2933c8f79..a6c750f683b 100644 --- a/pkg/skaffold/watch/watch.go +++ b/pkg/skaffold/watch/watch.go @@ -18,7 +18,6 @@ package watch import ( "context" - "time" "github.com/pkg/errors" ) @@ -29,7 +28,7 @@ type Factory func() Watcher // Watcher monitors files changes for multiples components. type Watcher interface { Register(deps func() ([]string, error), onChange func(Events)) error - Run(ctx context.Context, pollInterval time.Duration, onChange func() error) error + Run(ctx context.Context, trigger Trigger, onChange func() error) error } type watchList []*component @@ -62,9 +61,9 @@ func (w *watchList) Register(deps func() ([]string, error), onChange func(Events } // Run watches files until the context is cancelled or an error occurs. -func (w *watchList) Run(ctx context.Context, pollInterval time.Duration, onChange func() error) error { - ticker := time.NewTicker(pollInterval) - defer ticker.Stop() +func (w *watchList) Run(ctx context.Context, trigger Trigger, onChange func() error) error { + t, cleanup := trigger.Start() + defer cleanup() changedComponents := map[int]bool{} @@ -72,7 +71,7 @@ func (w *watchList) Run(ctx context.Context, pollInterval time.Duration, onChang select { case <-ctx.Done(): return nil - case <-ticker.C: + case <-t: changed := 0 for i, component := range *w { state, err := stat(component.deps) @@ -94,7 +93,8 @@ func (w *watchList) Run(ctx context.Context, pollInterval time.Duration, onChang // To prevent that, we debounce changes that happen too quickly // by waiting for a full turn where nothing happens and trigger a rebuild for // the accumulated changes. - if changed == 0 && len(changedComponents) > 0 { + debounce := trigger.Debounce() + if (!debounce && changed > 0) || (debounce && changed == 0 && len(changedComponents) > 0) { for i, component := range *w { if changedComponents[i] { component.onChange(component.events) diff --git a/pkg/skaffold/watch/watch_test.go b/pkg/skaffold/watch/watch_test.go index 64a44230508..bf47a32f1ab 100644 --- a/pkg/skaffold/watch/watch_test.go +++ b/pkg/skaffold/watch/watch_test.go @@ -79,7 +79,11 @@ func TestWatch(t *testing.T) { var stopped sync.WaitGroup stopped.Add(1) go func() { - err = watcher.Run(ctx, 10*time.Millisecond, somethingChanged.callNoErr) + trigger := &pollTrigger{ + Interval: 10 * time.Millisecond, + } + + err = watcher.Run(ctx, trigger, somethingChanged.callNoErr) stopped.Done() testutil.CheckError(t, false, err) }() From 778d5da5bf6f733ad11f849007e23dc60a103cf3 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 4 Oct 2018 09:16:32 +0200 Subject: [PATCH 13/61] Fix missing parenthesis Signed-off-by: David Gageot --- cmd/skaffold/app/cmd/cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/skaffold/app/cmd/cmd.go b/cmd/skaffold/app/cmd/cmd.go index 68c9e200681..91837a5d105 100644 --- a/cmd/skaffold/app/cmd/cmd.go +++ b/cmd/skaffold/app/cmd/cmd.go @@ -81,7 +81,7 @@ func NewSkaffoldCommand(out, err io.Writer) *cobra.Command { rootCmd.AddCommand(NewCmdConfig(out)) rootCmd.AddCommand(NewCmdInit(out)) - rootCmd.PersistentFlags().StringVarP(&v, "verbosity", "v", constants.DefaultLogLevel.String(), "Log level (debug, info, warn, error, fatal, panic") + rootCmd.PersistentFlags().StringVarP(&v, "verbosity", "v", constants.DefaultLogLevel.String(), "Log level (debug, info, warn, error, fatal, panic)") setFlagsFromEnvVariables(rootCmd.Commands()) From 37b225521d71aa7e61f8acdaca0061de35287872 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 4 Oct 2018 09:41:15 +0200 Subject: [PATCH 14/61] Skaffold init asks user to write skaffold.yaml Fix #1090 Signed-off-by: David Gageot --- cmd/skaffold/app/cmd/init.go | 66 ++++++++++++++++++++++++++---------- integration/run_test.go | 2 +- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/cmd/skaffold/app/cmd/init.go b/cmd/skaffold/app/cmd/init.go index 9aa35b6657d..bf5e7d048a0 100644 --- a/cmd/skaffold/app/cmd/init.go +++ b/cmd/skaffold/app/cmd/init.go @@ -44,10 +44,13 @@ import ( // an image we parse out from a kubernetes manifest const NoDockerfile = "None (image not built from these sources)" -var outfile string -var skipBuild bool -var cliArtifacts []string +var ( + cliArtifacts []string + skipBuild bool + force bool +) +// NewCmdInit describes the CLI command to generate a skaffold configuration. func NewCmdInit(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "init", @@ -57,18 +60,16 @@ func NewCmdInit(out io.Writer) *cobra.Command { return doInit(out) }, } - AddInitFlags(cmd) - return cmd -} - -func AddInitFlags(cmd *cobra.Command) { - cmd.Flags().StringVarP(&outfile, "file", "f", "", "File to write generated skaffold config") + cmd.Flags().StringVarP(&opts.ConfigurationFile, "filename", "f", "skaffold.yaml", "Filename or URL to the pipeline file") cmd.Flags().BoolVar(&skipBuild, "skip-build", false, "Skip generating build artifacts in skaffold config") + cmd.Flags().BoolVar(&force, "force", false, "Force the generation of the skaffold config") cmd.Flags().StringArrayVarP(&cliArtifacts, "artifact", "a", nil, "'='-delimited dockerfile/image pair to generate build artifact\n(example: --artifact=/web/Dockerfile.web=gcr.io/web-project/image)") + return cmd } func doInit(out io.Writer) error { rootDir := "." + var potentialConfigs, k8sConfigs, dockerfiles, images []string err := filepath.Walk(rootDir, func(path string, f os.FileInfo, e error) error { if f.IsDir() { @@ -90,11 +91,13 @@ func doInit(out io.Writer) error { if err != nil { return err } + for _, file := range potentialConfigs { - config, err := schema.ParseConfig(file, true) - if err == nil && config != nil { - out.Write([]byte(fmt.Sprintf("pre-existing skaffold yaml %s found: exiting\n", file))) - return nil + if !force { + config, err := schema.ParseConfig(file, true) + if err == nil && config != nil { + return fmt.Errorf("pre-existing %s found", file) + } } logrus.Debugf("%s is not a valid skaffold configuration: continuing", file) @@ -133,14 +136,41 @@ func doInit(out io.Writer) error { if err != nil { return err } - if outfile != "" { - if err := ioutil.WriteFile(outfile, cfg, 0644); err != nil { - return errors.Wrap(err, "writing config to file") - } - } else { + + if opts.ConfigurationFile == "-" { out.Write(cfg) + return nil } + if !force { + fmt.Fprintln(out, string(cfg)) + + reader := bufio.NewReader(os.Stdin) + confirmLoop: + for { + fmt.Fprintf(out, "Do you want to write this configuration to %s? [y/n]: ", opts.ConfigurationFile) + + response, err := reader.ReadString('\n') + if err != nil { + return errors.Wrap(err, "reading user confirmation") + } + + response = strings.ToLower(strings.TrimSpace(response)) + switch response { + case "y", "yes": + break confirmLoop + case "n", "no": + return nil + } + } + } + + if err := ioutil.WriteFile(opts.ConfigurationFile, cfg, 0644); err != nil { + return errors.Wrap(err, "writing config to file") + } + + fmt.Fprintf(out, "Configuration %s was written\n", opts.ConfigurationFile) + return nil } diff --git a/integration/run_test.go b/integration/run_test.go index e380005180b..31fb4cb677a 100644 --- a/integration/run_test.go +++ b/integration/run_test.go @@ -342,7 +342,7 @@ func TestInit(t *testing.T) { t.Errorf("error removing generated skaffold yaml: %v", err) } }() - initArgs := []string{"init", "-f", generatedYaml} + initArgs := []string{"init", "--force", "-f", generatedYaml} initArgs = append(initArgs, test.args...) initCmd := exec.Command("skaffold", initArgs...) initCmd.Dir = test.dir From acdebafdf6134e35961aac222541d995915b2a21 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 4 Oct 2018 17:37:19 +0200 Subject: [PATCH 15/61] Merge error? Signed-off-by: David Gageot --- pkg/skaffold/schema/v1alpha3/config.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/skaffold/schema/v1alpha3/config.go b/pkg/skaffold/schema/v1alpha3/config.go index 35cf08cff54..cb35735b426 100644 --- a/pkg/skaffold/schema/v1alpha3/config.go +++ b/pkg/skaffold/schema/v1alpha3/config.go @@ -201,10 +201,9 @@ 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"` - StructureTestFiles []string `yaml:"structureTestFiles"` + ImageName string `yaml:"imageName"` + Workspace string `yaml:"workspace,omitempty"` + ArtifactType `yaml:",inline"` } // Profile is additional configuration that overrides default From b862d808886d62689eac87449cbb963bc6bfef5b Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Thu, 4 Oct 2018 11:04:07 -0700 Subject: [PATCH 16/61] Add --label flag to specify custom labels for deployments This should work for skaffold run, dev, and deploy. This is one of the features requested for integrating skaffold with IntelliJ. e.g. skaffold run --label firstlabel=one --label secondlabel=two --- cmd/skaffold/app/cmd/cmd.go | 2 ++ pkg/skaffold/config/options.go | 9 +++++++++ pkg/skaffold/config/options_test.go | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/cmd/skaffold/app/cmd/cmd.go b/cmd/skaffold/app/cmd/cmd.go index 91837a5d105..83b33a6ec9e 100644 --- a/cmd/skaffold/app/cmd/cmd.go +++ b/cmd/skaffold/app/cmd/cmd.go @@ -136,10 +136,12 @@ func AddDevFlags(cmd *cobra.Command) { cmd.Flags().StringArrayVarP(&opts.Watch, "watch-image", "w", nil, "Choose which artifacts to watch. Artifacts with image names that contain the expression will be watched only. Default is to watch sources for all artifacts.") cmd.Flags().IntVarP(&opts.WatchPollInterval, "watch-poll-interval", "i", 1000, "Interval (in ms) between two checks for file changes.") cmd.Flags().BoolVar(&opts.PortForward, "port-forward", true, "Port-forward exposed container ports within pods") + cmd.Flags().StringArrayVarP(&opts.CustomLabels, "label", "l", nil, "Add custom labels to deployed objects. Set multiple times for multiple labels.") } func AddRunDeployFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&opts.Tail, "tail", false, "Stream logs from deployed objects") + cmd.Flags().StringArrayVarP(&opts.CustomLabels, "label", "l", nil, "Add custom labels to deployed objects. Set multiple times for multiple labels.") } func AddRunDevFlags(cmd *cobra.Command) { diff --git a/pkg/skaffold/config/options.go b/pkg/skaffold/config/options.go index 40b5c2bd482..19e834eade2 100644 --- a/pkg/skaffold/config/options.go +++ b/pkg/skaffold/config/options.go @@ -34,6 +34,7 @@ type SkaffoldOptions struct { Namespace string Watch []string Trigger string + CustomLabels []string WatchPollInterval int } @@ -54,5 +55,13 @@ func (opts *SkaffoldOptions) Labels() map[string]string { if len(opts.Profiles) > 0 { labels["profiles"] = strings.Join(opts.Profiles, ",") } + for _, cl := range opts.CustomLabels { + l := strings.SplitN(cl, "=", 2) + if len(l) == 1 { + labels[l[0]] = "" + continue + } + labels[l[0]] = l[1] + } return labels } diff --git a/pkg/skaffold/config/options_test.go b/pkg/skaffold/config/options_test.go index 6f9e98d6c83..7212c50b107 100644 --- a/pkg/skaffold/config/options_test.go +++ b/pkg/skaffold/config/options_test.go @@ -65,6 +65,25 @@ func TestLabels(t *testing.T) { "profiles": "p1,p2", }, }, + { + description: "custom labels", + options: SkaffoldOptions{ + Cleanup: true, + CustomLabels: []string{ + "one=first", + "two=second", + "three=", + "four", + }, + }, + expectedLabels: map[string]string{ + "cleanup": "true", + "one": "first", + "two": "second", + "three": "", + "four": "", + }, + }, } for _, test := range tests { From 1d2281a1fc3e51386eba8df1669326f2225a7074 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:09:30 -0700 Subject: [PATCH 17/61] [examples] add webserver file sync example --- examples/webserver/Dockerfile | 8 ++++++++ examples/webserver/index.html | 1 + examples/webserver/k8s-pod.yaml | 10 ++++++++++ examples/webserver/main.go | 10 ++++++++++ examples/webserver/skaffold.yaml | 11 +++++++++++ 5 files changed, 40 insertions(+) create mode 100644 examples/webserver/Dockerfile create mode 100644 examples/webserver/index.html create mode 100644 examples/webserver/k8s-pod.yaml create mode 100644 examples/webserver/main.go create mode 100644 examples/webserver/skaffold.yaml diff --git a/examples/webserver/Dockerfile b/examples/webserver/Dockerfile new file mode 100644 index 00000000000..6b9262b7f24 --- /dev/null +++ b/examples/webserver/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:1.10.1-alpine3.7 as builder +COPY main.go . +RUN go build -o /app main.go + +FROM alpine:3.7 +CMD ["./app"] +COPY --from=builder /app . +COPY *.html /static/ diff --git a/examples/webserver/index.html b/examples/webserver/index.html new file mode 100644 index 00000000000..b297683f36d --- /dev/null +++ b/examples/webserver/index.html @@ -0,0 +1 @@ +Hello world!! diff --git a/examples/webserver/k8s-pod.yaml b/examples/webserver/k8s-pod.yaml new file mode 100644 index 00000000000..00a9adc38c9 --- /dev/null +++ b/examples/webserver/k8s-pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: webserver +spec: + containers: + - name: webserver + image: gcr.io/k8s-skaffold/webserver-example + ports: + - containerPort: 8080 diff --git a/examples/webserver/main.go b/examples/webserver/main.go new file mode 100644 index 00000000000..036690cdc4b --- /dev/null +++ b/examples/webserver/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "net/http" +) + +func main() { + http.Handle("/", http.FileServer(http.Dir("/static"))) + http.ListenAndServe(":8080", nil) +} diff --git a/examples/webserver/skaffold.yaml b/examples/webserver/skaffold.yaml new file mode 100644 index 00000000000..218bb5f4d62 --- /dev/null +++ b/examples/webserver/skaffold.yaml @@ -0,0 +1,11 @@ +apiVersion: skaffold/v1alpha3 +kind: Config +build: + artifacts: + - imageName: gcr.io/k8s-skaffold/webserver-example + sync: + '*.html': '/static' +deploy: + kubectl: + manifests: + - k8s-* From 350f0b0857649bd4592ae2433fae6b10ca508d4c Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:09:57 -0700 Subject: [PATCH 18/61] [kubernetes] add kubectl syncer --- pkg/skaffold/kubernetes/sync.go | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 pkg/skaffold/kubernetes/sync.go diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go new file mode 100644 index 00000000000..4508e325cae --- /dev/null +++ b/pkg/skaffold/kubernetes/sync.go @@ -0,0 +1,78 @@ +/* +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 kubernetes + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + "github.com/sirupsen/logrus" + + "github.com/pkg/errors" + "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type Syncer interface { + CopyFilesForImage(image string, syncMap map[string]string) error + DeleteFilesForImage(image string, syncMap map[string]string) error +} + +type KubectlSyncer struct{} + +func (*KubectlSyncer) CopyFilesForImage(image string, syncMap map[string]string) error { + return perform(image, syncMap, copyFileFn) +} + +func (*KubectlSyncer) DeleteFilesForImage(image string, syncMap map[string]string) error { + return perform(image, syncMap, deleteFileFn) +} + +func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { + return exec.Command("kubectl", "exec", fmt.Sprintf("%s", pod.Name), "-c", container.Name, "--", "rm", "-rf", dst) +} + +func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { + return exec.Command("kubectl", "cp", src, fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, dst), "-c", container.Name) +} + +func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd) error { + logrus.Info("Syncing files:", files) + client, err := Client() + if err != nil { + return errors.Wrap(err, "getting k8s client") + } + pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{}) + if err != nil { + return errors.Wrap(err, "getting pods") + } + for _, p := range pods.Items { + for _, c := range p.Spec.Containers { + if strings.HasPrefix(c.Image, image) { + for src, dst := range files { + cmd := cmdFn(p, c, src, dst) + if err := util.RunCmd(cmd); err != nil { + return errors.Wrapf(err, "syncing with kubectl") + } + } + } + } + } + return nil +} From 9a1d4d0fe7dd73a6417d94899a83fc9d00118c69 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:10:12 -0700 Subject: [PATCH 19/61] [runner] add sync step to skaffold dev --- pkg/skaffold/runner/runner.go | 68 +++++++++++++- pkg/skaffold/runner/runner_test.go | 139 +++++++++++++++++++++++++++++ pkg/skaffold/util/util.go | 7 ++ 3 files changed, 213 insertions(+), 1 deletion(-) diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index ad461cc5cfa..8ba1a58f975 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -37,6 +37,7 @@ import ( 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/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -52,7 +53,11 @@ type SkaffoldRunner struct { deploy.Deployer test.Tester tag.Tagger +<<<<<<< HEAD watch.Trigger +======= + kubernetes.Syncer +>>>>>>> 84d22e8e... [runner] add sync step to skaffold dev opts *config.SkaffoldOptions watchFactory watch.Factory @@ -103,7 +108,11 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldConfig) (*Sk Tester: tester, Deployer: deployer, Tagger: tagger, +<<<<<<< HEAD Trigger: trigger, +======= + Syncer: &kubernetes.KubectlSyncer{}, +>>>>>>> 84d22e8e... [runner] add sync step to skaffold dev opts: opts, watchFactory: watch.NewWatcher, }, nil @@ -298,7 +307,18 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, - func(watch.Events) { changed.Add(artifact) }, + func(e watch.WatchEvents) { + sync, err := r.shouldSync(artifact.ImageName, artifact.Sync, e) + if err != nil { + return errors.Wrap(err, "checking sync files") + } + if !sync { + changed.Add(artifact) + } else { + color.Default.Fprintln(out, "Synced:", "copied", append(e.Added, e.Modified...), "deleted", e.Deleted) + } + return nil + }, ); err != nil { return nil, errors.Wrapf(err, "watching files for artifact %s", artifact.ImageName) } @@ -361,6 +381,52 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil, watcher.Run(ctx, r.Trigger, onChange) } +func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string, e watch.WatchEvents) (bool, error) { + // If there are no changes, there is nothing to sync + if !e.HasChanged() { + return false, nil + } + + toCopy, err := intersect(syncPatterns, append(e.Added, e.Modified...)) + if err != nil { + return false, errors.Wrap(err, "intersecting sync map and added, modified files") + } + // The only error that intersect can return is a bad pattern, which is checked above + toDelete, _ := intersect(syncPatterns, e.Deleted) + if toCopy == nil || toDelete == nil { + return false, nil + } + if err := r.Syncer.DeleteFilesForImage(image, toDelete); err != nil { + return false, errors.Wrap(err, "deleting files for image") + } + if err := r.Syncer.CopyFilesForImage(image, toCopy); err != nil { + return false, errors.Wrap(err, "copying files for image") + } + return true, nil +} + +func intersect(syncMap map[string]string, files []string) (map[string]string, error) { + ret := map[string]string{} + for _, f := range files { + for p, dst := range syncMap { + match, err := filepath.Match(p, f) + if err != nil { + return nil, errors.Wrap(err, "pattern error") + } + if !match { + return nil, nil + } + // If the source has special match characters, + // the destination must be a directory + if util.HasMeta(p) { + dst = filepath.Join(dst, f) + } + ret[f] = dst + } + } + return ret, nil +} + func (r *SkaffoldRunner) shouldWatch(artifact *latest.Artifact) bool { if len(r.opts.Watch) == 0 { return true diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index f0634988d25..64b70d5fa07 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -110,6 +110,39 @@ func (t *TestDeployer) Cleanup(ctx context.Context, out io.Writer) error { return nil } +type TestSyncer struct { + copyErr, deleteErr error + copies map[string]string + deletes []string +} + +func NewTestSyncer() *TestSyncer { + return &TestSyncer{ + copies: map[string]string{}, + } +} + +func NewTestSyncerWithErrors(copyErr, deleteErr error) *TestSyncer { + return &TestSyncer{ + copies: map[string]string{}, + copyErr: copyErr, + deleteErr: deleteErr, + } +} + +func (t *TestSyncer) CopyFilesForImage(image string, f map[string]string) error { + for src, dst := range f { + t.copies[src] = dst + } + return t.copyErr +} +func (t *TestSyncer) DeleteFilesForImage(image string, f map[string]string) error { + for _, dst := range f { + t.deletes = append(t.deletes, dst) + } + return t.deleteErr +} + func resetClient() { kubernetes.Client = kubernetes.GetClientset } func fakeGetClient() (clientgo.Interface, error) { return fake.NewSimpleClientset(), nil } @@ -420,6 +453,10 @@ func TestDev(t *testing.T) { Trigger: trigger, watchFactory: test.watcherFactory, opts: opts, + opts: &config.SkaffoldOptions{ + WatchPollInterval: 100, + }, + Syncer: NewTestSyncer(), } _, err := runner.Dev(context.Background(), ioutil.Discard, nil) @@ -450,6 +487,7 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { Deployer: deployer, Trigger: trigger, opts: opts, + Syncer: NewTestSyncer(), } ctx := context.Background() @@ -532,3 +570,104 @@ func TestShouldWatch(t *testing.T) { }) } } + +func TestShouldSync(t *testing.T) { + var tests = []struct { + description string + syncPatterns map[string]string + evt watch.WatchEvents + copies map[string]string + deletes []string + shouldErr bool + expected bool + syncer *TestSyncer + }{ + { + description: "match copy", + syncPatterns: map[string]string{ + "*.html": ".", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + }, + copies: map[string]string{ + "index.html": "index.html", + }, + syncer: NewTestSyncer(), + expected: true, + }, + { + description: "not copy syncable", + syncPatterns: map[string]string{ + "*.html": ".", + }, + evt: watch.WatchEvents{ + Added: []string{"main.go"}, + Deleted: []string{"index.html"}, + }, + syncer: NewTestSyncer(), + expected: false, + }, + { + description: "not delete syncable", + syncPatterns: map[string]string{ + "*.html": "/static", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + Deleted: []string{"some/other/file"}, + }, + syncer: NewTestSyncer(), + expected: false, + }, + { + description: "err bad pattern", + syncPatterns: map[string]string{ + "[*.html": "*", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + Deleted: []string{"some/other/file"}, + }, + syncer: NewTestSyncer(), + shouldErr: true, + }, + { + description: "err copy", + syncPatterns: map[string]string{ + "*.html": "*", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + }, + syncer: NewTestSyncerWithErrors(fmt.Errorf(""), nil), + shouldErr: true, + }, + { + description: "err copy", + syncPatterns: map[string]string{ + "*.html": "*", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + }, + syncer: NewTestSyncerWithErrors(nil, fmt.Errorf("")), + shouldErr: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + r := &SkaffoldRunner{ + Syncer: test.syncer, + } + actual, err := r.shouldSync("", test.syncPatterns, test.evt) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) + if test.expected { + testutil.CheckDeepEqual(t, test.copies, test.syncer.copies) + testutil.CheckDeepEqual(t, test.deletes, test.syncer.deletes) + } + }) + } + +} diff --git a/pkg/skaffold/util/util.go b/pkg/skaffold/util/util.go index 7eade1ac029..a8bcd5a0b2c 100644 --- a/pkg/skaffold/util/util.go +++ b/pkg/skaffold/util/util.go @@ -107,6 +107,13 @@ func ExpandPathsGlob(workingDir string, paths []string) ([]string, error) { return ret, nil } +// HasMeta reports whether path contains any of the magic characters +// recognized by filepath.Match. +// This is a copy of filepath/match.go's hasMeta +func HasMeta(path string) bool { + return strings.ContainsAny(path, "*?[") +} + // BoolPtr returns a pointer to a bool func BoolPtr(b bool) *bool { o := b From 64af3ab5a44dd3555a150686cccc5afdc1d36c21 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:28:21 -0700 Subject: [PATCH 20/61] [examples] add readme to webserver example --- examples/webserver/README.adoc | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 examples/webserver/README.adoc diff --git a/examples/webserver/README.adoc b/examples/webserver/README.adoc new file mode 100644 index 00000000000..b45e41779de --- /dev/null +++ b/examples/webserver/README.adoc @@ -0,0 +1,77 @@ +=== Example: File Sync with Skaffold +:icons: font + +ifndef::env-github[] +link:{github-repo-tree}/examples/webserver[see on Github icon:github[]] +endif::[] + +In this example: + +* Deploy a basic golang webserver that serves static files +* In development, sync static HTML files without triggering a rebuild or redeploy of the artifacts + +Not all file changes should require a complete rebuild and redeploy of the skaffold artifacts. +This tutorial shows how you can use the file sync feature of skaffold for very quick iterative development. + +==== Running the example on minikube + +From this directory, run + +```bash +skaffold dev +``` + +Now in a different terminal, hit the webserver's endpoint to see index.html + +```bash +$ curl localhost:8080 +Hello World!! +``` + +Now, edit the index.html file to contain something else. You'll see that skaffold syncs the file to the already running container, without rebuilding or redeploying. +```bash +echo "Hello skaffold" > index.html +``` + +You should see that skaffold has synced those changes in the other terminal +```bash +... +Synced: copied [index.html] deleted [] +Watching for changes... +``` + +Now, lets add a new file that matches the glob pattern but wasn't in the original container. +```bash +echo "Dogs are great" > cats.html +``` + +Now you can see that change immediately. +```bash +$ curl localhost:8080/cats.html +"Dogs are great" +``` + +Delete the file +```bash +rm cats.html +``` + +Now you can see that change immediately +```bash +$ curl localhost:8080/cats.html +404 page not found +``` + +==== Configuration walkthrough + +Let's walk through the first part of the skaffold.yaml + +```yaml + artifacts: + - imageName: gcr.io/k8s-skaffold/webserver-example + sync: + '*.html': '/static' +``` + +This will sync all files that match the *.html pattern to the /static/ folder in the container. +Sync can be multiple keys and the only restriction is that the destination must be a folder if the source is a pattern. \ No newline at end of file From 884e6b309a2dbf293553b89efcf00621377ace67 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:48:39 -0700 Subject: [PATCH 21/61] [linter] fixes --- pkg/skaffold/kubernetes/sync.go | 3 ++- pkg/skaffold/runner/runner.go | 11 +++-------- pkg/skaffold/runner/runner_test.go | 14 +++++++------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 4508e325cae..fba62d5a143 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -44,8 +44,9 @@ func (*KubectlSyncer) DeleteFilesForImage(image string, syncMap map[string]strin return perform(image, syncMap, deleteFileFn) } +// TODO(r2d4): kubectl exec doesn't seem to take a namespace flag? func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { - return exec.Command("kubectl", "exec", fmt.Sprintf("%s", pod.Name), "-c", container.Name, "--", "rm", "-rf", dst) + return exec.Command("kubectl", "exec", pod.Name, "-c", container.Name, "--", "rm", "-rf", dst) } func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 8ba1a58f975..6a740b8d639 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,6 +30,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" @@ -53,11 +54,8 @@ type SkaffoldRunner struct { deploy.Deployer test.Tester tag.Tagger -<<<<<<< HEAD watch.Trigger -======= kubernetes.Syncer ->>>>>>> 84d22e8e... [runner] add sync step to skaffold dev opts *config.SkaffoldOptions watchFactory watch.Factory @@ -108,11 +106,8 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldConfig) (*Sk Tester: tester, Deployer: deployer, Tagger: tagger, -<<<<<<< HEAD Trigger: trigger, -======= Syncer: &kubernetes.KubectlSyncer{}, ->>>>>>> 84d22e8e... [runner] add sync step to skaffold dev opts: opts, watchFactory: watch.NewWatcher, }, nil @@ -307,7 +302,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, - func(e watch.WatchEvents) { + func(e watch.Events) { sync, err := r.shouldSync(artifact.ImageName, artifact.Sync, e) if err != nil { return errors.Wrap(err, "checking sync files") @@ -381,7 +376,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil, watcher.Run(ctx, r.Trigger, onChange) } -func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string, e watch.WatchEvents) (bool, error) { +func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string, e watch.Events) (bool, error) { // If there are no changes, there is nothing to sync if !e.HasChanged() { return false, nil diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 64b70d5fa07..1ca49c3ba24 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -575,7 +575,7 @@ func TestShouldSync(t *testing.T) { var tests = []struct { description string syncPatterns map[string]string - evt watch.WatchEvents + evt watch.Events copies map[string]string deletes []string shouldErr bool @@ -587,7 +587,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": ".", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, }, copies: map[string]string{ @@ -601,7 +601,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": ".", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"main.go"}, Deleted: []string{"index.html"}, }, @@ -613,7 +613,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": "/static", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, Deleted: []string{"some/other/file"}, }, @@ -625,7 +625,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "[*.html": "*", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, Deleted: []string{"some/other/file"}, }, @@ -637,7 +637,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": "*", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, }, syncer: NewTestSyncerWithErrors(fmt.Errorf(""), nil), @@ -648,7 +648,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": "*", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, }, syncer: NewTestSyncerWithErrors(nil, fmt.Errorf("")), From 16415be69ab886dd1569aca936ef2f4b1cd516eb Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 14:10:16 -0700 Subject: [PATCH 22/61] review feedback --- hack/linter.sh | 8 +- pkg/skaffold/kubernetes/sync.go | 17 ++-- pkg/skaffold/kubernetes/sync_test.go | 114 +++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 pkg/skaffold/kubernetes/sync_test.go diff --git a/hack/linter.sh b/hack/linter.sh index c3a55355b7b..20e657c181e 100755 --- a/hack/linter.sh +++ b/hack/linter.sh @@ -20,7 +20,12 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if ! [ -x "$(command -v golangci-lint)" ]; then echo "Installing GolangCI-Lint" - ${DIR}/install_golint.sh -b $GOPATH/bin v1.9.3 + ${DIR}/install_golint.sh -b $GOPATH/bin v1.10.2 +fi + +if ! [ "$(golangci-lint --)" == ]; then + echo "Upgrading GolangCI-Lint" + ${DIR}/install_golint.sh -b $GOPATH/bin v1.10.2 fi golangci-lint run \ @@ -33,4 +38,5 @@ golangci-lint run \ -E misspell \ -E unconvert \ -E unparam \ + -D typecheck \ -D errcheck diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index fba62d5a143..d5e5556fca6 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -23,6 +23,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" "github.com/pkg/errors" "k8s.io/api/core/v1" @@ -44,9 +45,8 @@ func (*KubectlSyncer) DeleteFilesForImage(image string, syncMap map[string]strin return perform(image, syncMap, deleteFileFn) } -// TODO(r2d4): kubectl exec doesn't seem to take a namespace flag? func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { - return exec.Command("kubectl", "exec", pod.Name, "-c", container.Name, "--", "rm", "-rf", dst) + return exec.Command("kubectl", "exec", pod.Name, "--namespace", pod.Namespace, "-c", container.Name, "--", "rm", "-rf", dst) } func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { @@ -66,11 +66,16 @@ func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Contai for _, p := range pods.Items { for _, c := range p.Spec.Containers { if strings.HasPrefix(c.Image, image) { + var e errgroup.Group for src, dst := range files { - cmd := cmdFn(p, c, src, dst) - if err := util.RunCmd(cmd); err != nil { - return errors.Wrapf(err, "syncing with kubectl") - } + src, dst := src, dst + e.Go(func() error { + cmd := cmdFn(p, c, src, dst) + return util.RunCmd(cmd) + }) + } + if err := e.Wait(); err != nil { + return errors.Wrap(err, "syncing files:") } } } diff --git a/pkg/skaffold/kubernetes/sync_test.go b/pkg/skaffold/kubernetes/sync_test.go new file mode 100644 index 00000000000..4c0fb85ec20 --- /dev/null +++ b/pkg/skaffold/kubernetes/sync_test.go @@ -0,0 +1,114 @@ +package kubernetes + +import ( + "fmt" + "os/exec" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + "github.com/GoogleContainerTools/skaffold/testutil" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" +) + +type TestCmdRecorder struct { + cmds []string + err error +} + +func (t *TestCmdRecorder) RunCmd(cmd *exec.Cmd) error { + if t.err != nil { + return t.err + } + t.cmds = append(t.cmds, strings.Join(cmd.Args, " ")) + return nil +} + +func (t *TestCmdRecorder) RunCmdOut(cmd *exec.Cmd) ([]byte, error) { + return nil, t.RunCmd(cmd) +} + +func fakeCmd(p v1.Pod, c v1.Container, src, dst string) *exec.Cmd { + return exec.Command("copy", src, dst) +} + +var pod = &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podname", + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container_name", + Image: "gcr.io/k8s-skaffold:123", + }, + }, + }, +} + +func TestPerform(t *testing.T) { + var tests = []struct { + description string + image string + files map[string]string + cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd + cmdErr error + clientErr error + expected []string + shouldErr bool + }{ + { + description: "no error", + image: "gcr.io/k8s-skaffold:123", + files: map[string]string{"test.go": "/test.go"}, + cmdFn: fakeCmd, + expected: []string{"copy test.go /test.go"}, + }, + { + description: "cmd error", + image: "gcr.io/k8s-skaffold:123", + files: map[string]string{"test.go": "/test.go"}, + cmdFn: fakeCmd, + cmdErr: fmt.Errorf(""), + shouldErr: true, + }, + { + description: "client error", + image: "gcr.io/k8s-skaffold:123", + files: map[string]string{"test.go": "/test.go"}, + cmdFn: fakeCmd, + clientErr: fmt.Errorf(""), + shouldErr: true, + }, + { + description: "no copy", + image: "gcr.io/different-pod:123", + files: map[string]string{"test.go": "/test.go"}, + cmdFn: fakeCmd, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + cmdRecord := &TestCmdRecorder{err: test.cmdErr} + defer func(c util.Command) { util.DefaultExecCommand = c }(util.DefaultExecCommand) + util.DefaultExecCommand = cmdRecord + + defer func(c func() (kubernetes.Interface, error)) { Client = c }(GetClientset) + Client = func() (kubernetes.Interface, error) { + return fake.NewSimpleClientset(pod), test.clientErr + } + + util.DefaultExecCommand = cmdRecord + err := perform(test.image, test.files, test.cmdFn) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, cmdRecord.cmds) + }) + } +} From 98b2219c0a56b8391ae7b69f9429f65aff2c4b9f Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 14:19:43 -0700 Subject: [PATCH 23/61] [vendor] add x/sync/errgroup --- Gopkg.lock | 9 +++ hack/linter.sh | 8 +-- vendor/golang.org/x/sync/AUTHORS | 3 + vendor/golang.org/x/sync/CONTRIBUTORS | 3 + vendor/golang.org/x/sync/LICENSE | 27 ++++++++ vendor/golang.org/x/sync/PATENTS | 22 ++++++ vendor/golang.org/x/sync/errgroup/errgroup.go | 67 +++++++++++++++++++ 7 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 vendor/golang.org/x/sync/AUTHORS create mode 100644 vendor/golang.org/x/sync/CONTRIBUTORS create mode 100644 vendor/golang.org/x/sync/LICENSE create mode 100644 vendor/golang.org/x/sync/PATENTS create mode 100644 vendor/golang.org/x/sync/errgroup/errgroup.go diff --git a/Gopkg.lock b/Gopkg.lock index 4d34a23d829..33e9738b525 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -722,6 +722,14 @@ pruneopts = "NUT" revision = "d2e6202438beef2727060aa7cabdd924d92ebfd9" +[[projects]] + branch = "master" + digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239" + name = "golang.org/x/sync" + packages = ["errgroup"] + pruneopts = "NUT" + revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca" + [[projects]] branch = "master" digest = "1:581828cee9f0d31195c1ae52ea8935dfaec395cee6c23adc02109b892b391c2e" @@ -1180,6 +1188,7 @@ "golang.org/x/crypto/ssh/terminal", "golang.org/x/oauth2", "golang.org/x/oauth2/google", + "golang.org/x/sync/errgroup", "google.golang.org/api/cloudbuild/v1", "google.golang.org/api/googleapi", "google.golang.org/api/iterator", diff --git a/hack/linter.sh b/hack/linter.sh index 20e657c181e..c3a55355b7b 100755 --- a/hack/linter.sh +++ b/hack/linter.sh @@ -20,12 +20,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if ! [ -x "$(command -v golangci-lint)" ]; then echo "Installing GolangCI-Lint" - ${DIR}/install_golint.sh -b $GOPATH/bin v1.10.2 -fi - -if ! [ "$(golangci-lint --)" == ]; then - echo "Upgrading GolangCI-Lint" - ${DIR}/install_golint.sh -b $GOPATH/bin v1.10.2 + ${DIR}/install_golint.sh -b $GOPATH/bin v1.9.3 fi golangci-lint run \ @@ -38,5 +33,4 @@ golangci-lint run \ -E misspell \ -E unconvert \ -E unparam \ - -D typecheck \ -D errcheck diff --git a/vendor/golang.org/x/sync/AUTHORS b/vendor/golang.org/x/sync/AUTHORS new file mode 100644 index 00000000000..15167cd746c --- /dev/null +++ b/vendor/golang.org/x/sync/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/sync/CONTRIBUTORS b/vendor/golang.org/x/sync/CONTRIBUTORS new file mode 100644 index 00000000000..1c4577e9680 --- /dev/null +++ b/vendor/golang.org/x/sync/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/vendor/golang.org/x/sync/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/sync/PATENTS b/vendor/golang.org/x/sync/PATENTS new file mode 100644 index 00000000000..733099041f8 --- /dev/null +++ b/vendor/golang.org/x/sync/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go new file mode 100644 index 00000000000..533438d91c1 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -0,0 +1,67 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package errgroup provides synchronization, error propagation, and Context +// cancelation for groups of goroutines working on subtasks of a common task. +package errgroup + +import ( + "sync" + + "golang.org/x/net/context" +) + +// A Group is a collection of goroutines working on subtasks that are part of +// the same overall task. +// +// A zero Group is valid and does not cancel on error. +type Group struct { + cancel func() + + wg sync.WaitGroup + + errOnce sync.Once + err error +} + +// WithContext returns a new Group and an associated Context derived from ctx. +// +// The derived Context is canceled the first time a function passed to Go +// returns a non-nil error or the first time Wait returns, whichever occurs +// first. +func WithContext(ctx context.Context) (*Group, context.Context) { + ctx, cancel := context.WithCancel(ctx) + return &Group{cancel: cancel}, ctx +} + +// Wait blocks until all function calls from the Go method have returned, then +// returns the first non-nil error (if any) from them. +func (g *Group) Wait() error { + g.wg.Wait() + if g.cancel != nil { + g.cancel() + } + return g.err +} + +// Go calls the given function in a new goroutine. +// +// The first call to return a non-nil error cancels the group; its error will be +// returned by Wait. +func (g *Group) Go(f func() error) { + g.wg.Add(1) + + go func() { + defer g.wg.Done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel() + } + }) + } + }() +} From f0ed4cda0d0a1f8d2758898bb972fa81fdcb178f Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 16:14:03 -0700 Subject: [PATCH 24/61] [linter] add boilerplate to sync_test.go --- pkg/skaffold/kubernetes/sync_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/skaffold/kubernetes/sync_test.go b/pkg/skaffold/kubernetes/sync_test.go index 4c0fb85ec20..9e071cacc27 100644 --- a/pkg/skaffold/kubernetes/sync_test.go +++ b/pkg/skaffold/kubernetes/sync_test.go @@ -1,3 +1,19 @@ +/* +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 kubernetes import ( From c511693f0b9a028cf504e08916104410fba2100f Mon Sep 17 00:00:00 2001 From: r2d4 Date: Thu, 27 Sep 2018 16:40:23 -0700 Subject: [PATCH 25/61] File changes should be relative to artifact context --- pkg/skaffold/docker/parse.go | 2 +- pkg/skaffold/runner/runner.go | 25 ++++++++++++++-------- pkg/skaffold/runner/runner_test.go | 33 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/pkg/skaffold/docker/parse.go b/pkg/skaffold/docker/parse.go index 80d9b7a320b..52abf8993ed 100644 --- a/pkg/skaffold/docker/parse.go +++ b/pkg/skaffold/docker/parse.go @@ -249,7 +249,7 @@ func expandPaths(workspace string, copied [][]string) ([]string, error) { for dep := range expandedPaths { deps = append(deps, dep) } - logrus.Infof("Found dependencies for dockerfile %s", deps) + // logrus.Infof("Found dependencies for dockerfile %s", deps) return deps, nil } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 6a740b8d639..97e12c6a113 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -303,7 +303,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, func(e watch.Events) { - sync, err := r.shouldSync(artifact.ImageName, artifact.Sync, e) + sync, err := r.shouldSync(artifact.ImageName, artifact.Workspace, artifact.Sync, e) if err != nil { return errors.Wrap(err, "checking sync files") } @@ -376,18 +376,18 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil, watcher.Run(ctx, r.Trigger, onChange) } -func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string, e watch.Events) (bool, error) { - // If there are no changes, there is nothing to sync - if !e.HasChanged() { +func (r *SkaffoldRunner) shouldSync(image string, context string, syncPatterns map[string]string, e watch.Events) (bool, error) { + // If there are no changes, short circuit and don't sync anything + if !e.HasChanged() || syncPatterns == nil || len(syncPatterns) == 0 { return false, nil } - toCopy, err := intersect(syncPatterns, append(e.Added, e.Modified...)) + toCopy, err := intersect(context, syncPatterns, append(e.Added, e.Modified...)) if err != nil { return false, errors.Wrap(err, "intersecting sync map and added, modified files") } // The only error that intersect can return is a bad pattern, which is checked above - toDelete, _ := intersect(syncPatterns, e.Deleted) + toDelete, _ := intersect(context, syncPatterns, e.Deleted) if toCopy == nil || toDelete == nil { return false, nil } @@ -400,21 +400,28 @@ func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string return true, nil } -func intersect(syncMap map[string]string, files []string) (map[string]string, error) { +func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { ret := map[string]string{} + fmt.Println("context", context) for _, f := range files { + relPath, err := filepath.Rel(context, f) + if err != nil { + return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) + } + fmt.Println("relPath", relPath) for p, dst := range syncMap { - match, err := filepath.Match(p, f) + match, err := filepath.Match(p, relPath) if err != nil { return nil, errors.Wrap(err, "pattern error") } + fmt.Println("match", match, "pattern", p, "f", f, "dst", dst) if !match { return nil, nil } // If the source has special match characters, // the destination must be a directory if util.HasMeta(p) { - dst = filepath.Join(dst, f) + dst = filepath.Join(dst, filepath.Base(relPath)) } ret[f] = dst } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 1ca49c3ba24..ccbe1646fe9 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -284,6 +284,39 @@ func TestNewForConfig(t *testing.T) { } } +func TestIntersect(t *testing.T) { + var tests = []struct { + description string + syncPatterns map[string]string + files []string + expected map[string]string + shouldErr bool + }{ + { + description: "nil sync patterns doesn't sync", + expected: map[string]string{}, + }, + { + description: "copy nested file to correct destination", + files: []string{"static/index.html", "static/test.html"}, + syncPatterns: map[string]string{ + "static/*.html": "/html", + }, + expected: map[string]string{ + "static/index.html": "/html/index.html", + "static/test.html": "/html/test.html", + }, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + actual, err := intersect(test.syncPatterns, test.files) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) + }) + } +} + func TestRun(t *testing.T) { var tests = []struct { description string From 88f3d02c07e9b32bb48679da9d18ed75b9dfef63 Mon Sep 17 00:00:00 2001 From: r2d4 Date: Fri, 28 Sep 2018 10:37:15 -0500 Subject: [PATCH 26/61] rename webserver example to hot reload --- examples/webserver/Dockerfile | 8 - examples/webserver/index.html | 1 - examples/webserver/k8s-pod.yaml | 10 - examples/webserver/main.go | 10 - examples/webserver/skaffold.yaml | 11 - .../examples/hot-reload}/README.adoc | 0 .../examples/hot-reload/node/Dockerfile | 4 + .../examples/hot-reload/node/k8s/pod.yaml | 10 + .../hot-reload/node/package-lock.json | 2316 +++++++++++++++++ .../examples/hot-reload/node/server.js | 14 + integration/examples/hot-reload/skaffold.yaml | 16 + pkg/skaffold/runner/runner.go | 3 - pkg/skaffold/runner/runner_test.go | 26 +- 13 files changed, 2384 insertions(+), 45 deletions(-) delete mode 100644 examples/webserver/Dockerfile delete mode 100644 examples/webserver/index.html delete mode 100644 examples/webserver/k8s-pod.yaml delete mode 100644 examples/webserver/main.go delete mode 100644 examples/webserver/skaffold.yaml rename {examples/webserver => integration/examples/hot-reload}/README.adoc (100%) create mode 100644 integration/examples/hot-reload/node/Dockerfile create mode 100644 integration/examples/hot-reload/node/k8s/pod.yaml create mode 100644 integration/examples/hot-reload/node/package-lock.json create mode 100644 integration/examples/hot-reload/node/server.js create mode 100644 integration/examples/hot-reload/skaffold.yaml diff --git a/examples/webserver/Dockerfile b/examples/webserver/Dockerfile deleted file mode 100644 index 6b9262b7f24..00000000000 --- a/examples/webserver/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM golang:1.10.1-alpine3.7 as builder -COPY main.go . -RUN go build -o /app main.go - -FROM alpine:3.7 -CMD ["./app"] -COPY --from=builder /app . -COPY *.html /static/ diff --git a/examples/webserver/index.html b/examples/webserver/index.html deleted file mode 100644 index b297683f36d..00000000000 --- a/examples/webserver/index.html +++ /dev/null @@ -1 +0,0 @@ -Hello world!! diff --git a/examples/webserver/k8s-pod.yaml b/examples/webserver/k8s-pod.yaml deleted file mode 100644 index 00a9adc38c9..00000000000 --- a/examples/webserver/k8s-pod.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: webserver -spec: - containers: - - name: webserver - image: gcr.io/k8s-skaffold/webserver-example - ports: - - containerPort: 8080 diff --git a/examples/webserver/main.go b/examples/webserver/main.go deleted file mode 100644 index 036690cdc4b..00000000000 --- a/examples/webserver/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "net/http" -) - -func main() { - http.Handle("/", http.FileServer(http.Dir("/static"))) - http.ListenAndServe(":8080", nil) -} diff --git a/examples/webserver/skaffold.yaml b/examples/webserver/skaffold.yaml deleted file mode 100644 index 218bb5f4d62..00000000000 --- a/examples/webserver/skaffold.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: skaffold/v1alpha3 -kind: Config -build: - artifacts: - - imageName: gcr.io/k8s-skaffold/webserver-example - sync: - '*.html': '/static' -deploy: - kubectl: - manifests: - - k8s-* diff --git a/examples/webserver/README.adoc b/integration/examples/hot-reload/README.adoc similarity index 100% rename from examples/webserver/README.adoc rename to integration/examples/hot-reload/README.adoc diff --git a/integration/examples/hot-reload/node/Dockerfile b/integration/examples/hot-reload/node/Dockerfile new file mode 100644 index 00000000000..abcafcbf67c --- /dev/null +++ b/integration/examples/hot-reload/node/Dockerfile @@ -0,0 +1,4 @@ +FROM node:6.10.3 +CMD ["nodemon", "server.js"] +RUN npm install -g nodemon +COPY *.js . diff --git a/integration/examples/hot-reload/node/k8s/pod.yaml b/integration/examples/hot-reload/node/k8s/pod.yaml new file mode 100644 index 00000000000..8fe53e3720e --- /dev/null +++ b/integration/examples/hot-reload/node/k8s/pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: node +spec: + containers: + - name: node + image: gcr.io/k8s-skaffold/node-example + ports: + - containerPort: 3000 diff --git a/integration/examples/hot-reload/node/package-lock.json b/integration/examples/hot-reload/node/package-lock.json new file mode 100644 index 00000000000..c9661cb9950 --- /dev/null +++ b/integration/examples/hot-reload/node/package-lock.json @@ -0,0 +1,2316 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "binary-extensions": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==" + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "event-stream": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", + "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", + "requires": { + "duplexer": "^0.1.1", + "flatmap-stream": "^0.1.0", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "flatmap-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.0.tgz", + "integrity": "sha512-Nlic4ZRYxikqnK5rj3YoxDVKGGtUjcNDUtvQ7XsdGLZmMwdUYnXf10o1zcXtzEZTBgc6GxeRpQxV/Wu3WPIIHA==" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "requires": { + "package-json": "^4.0.0" + } + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "nodemon": { + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.4.tgz", + "integrity": "sha512-hyK6vl65IPnky/ee+D3IWvVGgJa/m3No2/Xc/3wanS6Ce1MWjCzH6NnhPJ/vZM+6JFym16jtHx51lmCMB9HDtg==", + "requires": { + "chokidar": "^2.0.2", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.0", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.3.0" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "requires": { + "through": "~2.3" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "ps-tree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", + "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", + "requires": { + "event-stream": "~3.3.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "pstree.remy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz", + "integrity": "sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==", + "requires": { + "ps-tree": "^1.1.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "requires": { + "rc": "^1.0.1" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "requires": { + "semver": "^5.0.3" + } + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-combiner": { + "version": "0.2.2", + "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "requires": { + "execa": "^0.7.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + } + }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "requires": { + "debug": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==" + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", + "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "requires": { + "string-width": "^2.1.1" + } + }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } +} diff --git a/integration/examples/hot-reload/node/server.js b/integration/examples/hot-reload/node/server.js new file mode 100644 index 00000000000..e70e2778394 --- /dev/null +++ b/integration/examples/hot-reload/node/server.js @@ -0,0 +1,14 @@ +const http = require('http'); + +const hostname = '127.0.0.1'; +const port = 3000; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Hello World?\n'); +}); + +server.listen(port, hostname, () => { + console.log(`Node server running at http://${hostname}:${port}/`); +}); \ No newline at end of file diff --git a/integration/examples/hot-reload/skaffold.yaml b/integration/examples/hot-reload/skaffold.yaml new file mode 100644 index 00000000000..4f16076fef6 --- /dev/null +++ b/integration/examples/hot-reload/skaffold.yaml @@ -0,0 +1,16 @@ +apiVersion: skaffold/v1alpha3 +kind: Config +build: + artifacts: + - imageName: gcr.io/k8s-skaffold/node-example + workspace: node + sync: + '*.js': . + - imageName: gcr.io/k8s-skaffold/react-example + workspace: react + sync: + src/*: . +deploy: + kubectl: + manifests: + - node/k8s/** diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 97e12c6a113..4a364f38a9e 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -402,19 +402,16 @@ func (r *SkaffoldRunner) shouldSync(image string, context string, syncPatterns m func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { ret := map[string]string{} - fmt.Println("context", context) for _, f := range files { relPath, err := filepath.Rel(context, f) if err != nil { return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) } - fmt.Println("relPath", relPath) for p, dst := range syncMap { match, err := filepath.Match(p, relPath) if err != nil { return nil, errors.Wrap(err, "pattern error") } - fmt.Println("match", match, "pattern", p, "f", f, "dst", dst) if !match { return nil, nil } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index ccbe1646fe9..e99e468b4eb 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -289,6 +289,7 @@ func TestIntersect(t *testing.T) { description string syncPatterns map[string]string files []string + context string expected map[string]string shouldErr bool }{ @@ -307,11 +308,31 @@ func TestIntersect(t *testing.T) { "static/test.html": "/html/test.html", }, }, + { + description: "file not in . copies to correct destination", + files: []string{"node/server.js"}, + context: "node", + syncPatterns: map[string]string{ + "*.js": "/", + }, + expected: map[string]string{ + "node/server.js": "/server.js", + }, + }, + { + description: "file change not relative to context throws error", + files: []string{"node/server.js", "/something/test.js"}, + context: "node", + syncPatterns: map[string]string{ + "*.js": "/", + }, + shouldErr: true, + }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { - actual, err := intersect(test.syncPatterns, test.files) + actual, err := intersect(test.context, test.syncPatterns, test.files) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) }) } @@ -611,6 +632,7 @@ func TestShouldSync(t *testing.T) { evt watch.Events copies map[string]string deletes []string + context string shouldErr bool expected bool syncer *TestSyncer @@ -694,7 +716,7 @@ func TestShouldSync(t *testing.T) { r := &SkaffoldRunner{ Syncer: test.syncer, } - actual, err := r.shouldSync("", test.syncPatterns, test.evt) + actual, err := r.shouldSync(test.context, "", test.syncPatterns, test.evt) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) if test.expected { testutil.CheckDeepEqual(t, test.copies, test.syncer.copies) From 6edae2e1264b38652ecd64bb845025f97418bade Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Fri, 28 Sep 2018 14:55:40 -0500 Subject: [PATCH 27/61] [sync] move to sync package, move logic to watch main loop --- pkg/skaffold/kubernetes/sync.go | 20 ++-- pkg/skaffold/runner/changes.go | 26 +++- pkg/skaffold/runner/runner.go | 41 ++++--- pkg/skaffold/runner/runner_test.go | 184 +++-------------------------- pkg/skaffold/sync/sync.go | 73 ++++++++++++ pkg/skaffold/sync/sync_test.go | 176 +++++++++++++++++++++++++++ 6 files changed, 320 insertions(+), 200 deletions(-) create mode 100644 pkg/skaffold/sync/sync.go create mode 100644 pkg/skaffold/sync/sync_test.go diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index d5e5556fca6..1db5642f4f6 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -21,6 +21,7 @@ import ( "os/exec" "strings" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -30,19 +31,16 @@ import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type Syncer interface { - CopyFilesForImage(image string, syncMap map[string]string) error - DeleteFilesForImage(image string, syncMap map[string]string) error -} - type KubectlSyncer struct{} -func (*KubectlSyncer) CopyFilesForImage(image string, syncMap map[string]string) error { - return perform(image, syncMap, copyFileFn) -} - -func (*KubectlSyncer) DeleteFilesForImage(image string, syncMap map[string]string) error { - return perform(image, syncMap, deleteFileFn) +func (k *KubectlSyncer) Sync(s *sync.SyncItem) error { + if err := perform(s.Image, s.Copy, copyFileFn); err != nil { + return errors.Wrap(err, "copying files") + } + if err := perform(s.Image, s.Delete, deleteFileFn); err != nil { + return errors.Wrap(err, "deleting files") + } + return nil } func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { diff --git a/pkg/skaffold/runner/changes.go b/pkg/skaffold/runner/changes.go index e33137564fd..f5501aa0876 100644 --- a/pkg/skaffold/runner/changes.go +++ b/pkg/skaffold/runner/changes.go @@ -18,20 +18,40 @@ package runner import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" ) type changes struct { - dirtyArtifacts []*latest.Artifact + dirtyArtifacts []*artifactChange + needsRebuild []*latest.Artifact + needsResync []*sync.SyncItem needsRedeploy bool needsReload bool } -func (c *changes) Add(a *latest.Artifact) { - c.dirtyArtifacts = append(c.dirtyArtifacts, a) +type artifactChange struct { + artifact *latest.Artifact + events watch.Events +} + +func (c *changes) AddDirtyArtifact(a *latest.Artifact, e watch.Events) { + c.dirtyArtifacts = append(c.dirtyArtifacts, &artifactChange{artifact: a, events: e}) +} + +func (c *changes) AddRebuild(a *latest.Artifact) { + c.needsRebuild = append(c.needsRebuild, a) +} + +func (c *changes) AddResync(s *sync.SyncItem) { + c.needsResync = append(c.needsResync, s) } func (c *changes) reset() { c.dirtyArtifacts = nil + c.needsRebuild = nil + c.needsResync = nil + c.needsRedeploy = false c.needsReload = false } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 4a364f38a9e..ba72e20ded4 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,13 +30,13 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" "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/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" @@ -55,7 +55,7 @@ type SkaffoldRunner struct { test.Tester tag.Tagger watch.Trigger - kubernetes.Syncer + sync.Syncer opts *config.SkaffoldOptions watchFactory watch.Factory @@ -254,12 +254,32 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } }() + for _, a := range changed.dirtyArtifacts { + s, err := sync.NewSyncItem(a.artifact, a.events) + if err != nil { + return errors.Wrap(err, "sync") + } + if s != nil { + changed.AddResync(s) + } + if s == nil { + changed.AddRebuild(a.artifact) + } + } + switch { case changed.needsReload: logger.Stop() return ErrorConfigurationChanged - case len(changed.dirtyArtifacts) > 0: - bRes, err := r.Build(ctx, out, r.Tagger, changed.dirtyArtifacts) + case len(changed.needsResync) > 0: + for _, s := range changed.needsResync { + if err := r.Syncer.Sync(s); err != nil { + logrus.Warnln("Skipping build and deploy due to sync error:", err) + return nil + } + } + case len(changed.needsRebuild) > 0: + bRes, err := r.Build(ctx, out, r.Tagger, changed.needsRebuild) if err != nil { logrus.Warnln("Skipping Deploy due to build error:", err) return nil @@ -302,18 +322,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, - func(e watch.Events) { - sync, err := r.shouldSync(artifact.ImageName, artifact.Workspace, artifact.Sync, e) - if err != nil { - return errors.Wrap(err, "checking sync files") - } - if !sync { - changed.Add(artifact) - } else { - color.Default.Fprintln(out, "Synced:", "copied", append(e.Added, e.Modified...), "deleted", e.Deleted) - } - return nil - }, + func(e watch.Events) { changed.AddDirtyArtifact(artifact, e) }, ); err != nil { return nil, errors.Wrapf(err, "watching files for artifact %s", artifact.ImageName) } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index e99e468b4eb..c23f1516bca 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -30,6 +30,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/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" @@ -111,9 +112,9 @@ func (t *TestDeployer) Cleanup(ctx context.Context, out io.Writer) error { } type TestSyncer struct { - copyErr, deleteErr error - copies map[string]string - deletes []string + err error + copies map[string]string + deletes []string } func NewTestSyncer() *TestSyncer { @@ -122,25 +123,24 @@ func NewTestSyncer() *TestSyncer { } } -func NewTestSyncerWithErrors(copyErr, deleteErr error) *TestSyncer { +func NewTestSyncerWithError(err error) *TestSyncer { return &TestSyncer{ - copies: map[string]string{}, - copyErr: copyErr, - deleteErr: deleteErr, + copies: map[string]string{}, + err: err, } } -func (t *TestSyncer) CopyFilesForImage(image string, f map[string]string) error { - for src, dst := range f { +func (t *TestSyncer) Sync(s *sync.SyncItem) error { + if t.err != nil { + return t.err + } + for src, dst := range s.Copy { t.copies[src] = dst } - return t.copyErr -} -func (t *TestSyncer) DeleteFilesForImage(image string, f map[string]string) error { - for _, dst := range f { + for _, dst := range s.Delete { t.deletes = append(t.deletes, dst) } - return t.deleteErr + return nil } func resetClient() { kubernetes.Client = kubernetes.GetClientset } @@ -284,60 +284,6 @@ func TestNewForConfig(t *testing.T) { } } -func TestIntersect(t *testing.T) { - var tests = []struct { - description string - syncPatterns map[string]string - files []string - context string - expected map[string]string - shouldErr bool - }{ - { - description: "nil sync patterns doesn't sync", - expected: map[string]string{}, - }, - { - description: "copy nested file to correct destination", - files: []string{"static/index.html", "static/test.html"}, - syncPatterns: map[string]string{ - "static/*.html": "/html", - }, - expected: map[string]string{ - "static/index.html": "/html/index.html", - "static/test.html": "/html/test.html", - }, - }, - { - description: "file not in . copies to correct destination", - files: []string{"node/server.js"}, - context: "node", - syncPatterns: map[string]string{ - "*.js": "/", - }, - expected: map[string]string{ - "node/server.js": "/server.js", - }, - }, - { - description: "file change not relative to context throws error", - files: []string{"node/server.js", "/something/test.js"}, - context: "node", - syncPatterns: map[string]string{ - "*.js": "/", - }, - shouldErr: true, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - actual, err := intersect(test.context, test.syncPatterns, test.files) - testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) - }) - } -} - func TestRun(t *testing.T) { var tests = []struct { description string @@ -624,105 +570,3 @@ func TestShouldWatch(t *testing.T) { }) } } - -func TestShouldSync(t *testing.T) { - var tests = []struct { - description string - syncPatterns map[string]string - evt watch.Events - copies map[string]string - deletes []string - context string - shouldErr bool - expected bool - syncer *TestSyncer - }{ - { - description: "match copy", - syncPatterns: map[string]string{ - "*.html": ".", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - }, - copies: map[string]string{ - "index.html": "index.html", - }, - syncer: NewTestSyncer(), - expected: true, - }, - { - description: "not copy syncable", - syncPatterns: map[string]string{ - "*.html": ".", - }, - evt: watch.Events{ - Added: []string{"main.go"}, - Deleted: []string{"index.html"}, - }, - syncer: NewTestSyncer(), - expected: false, - }, - { - description: "not delete syncable", - syncPatterns: map[string]string{ - "*.html": "/static", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - Deleted: []string{"some/other/file"}, - }, - syncer: NewTestSyncer(), - expected: false, - }, - { - description: "err bad pattern", - syncPatterns: map[string]string{ - "[*.html": "*", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - Deleted: []string{"some/other/file"}, - }, - syncer: NewTestSyncer(), - shouldErr: true, - }, - { - description: "err copy", - syncPatterns: map[string]string{ - "*.html": "*", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - }, - syncer: NewTestSyncerWithErrors(fmt.Errorf(""), nil), - shouldErr: true, - }, - { - description: "err copy", - syncPatterns: map[string]string{ - "*.html": "*", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - }, - syncer: NewTestSyncerWithErrors(nil, fmt.Errorf("")), - shouldErr: true, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - r := &SkaffoldRunner{ - Syncer: test.syncer, - } - actual, err := r.shouldSync(test.context, "", test.syncPatterns, test.evt) - testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) - if test.expected { - testutil.CheckDeepEqual(t, test.copies, test.syncer.copies) - testutil.CheckDeepEqual(t, test.deletes, test.syncer.deletes) - } - }) - } - -} diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go new file mode 100644 index 00000000000..eac9278c910 --- /dev/null +++ b/pkg/skaffold/sync/sync.go @@ -0,0 +1,73 @@ +package sync + +import ( + "path/filepath" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" + "github.com/pkg/errors" +) + +type Syncer interface { + Sync(s *SyncItem) error +} + +type SyncItem struct { + Image string + Copy map[string]string + Delete map[string]string +} + +func NewSyncItem(a *v1alpha3.Artifact, e watch.Events) (*SyncItem, error) { + // If there are no changes, short circuit and don't sync anything + if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { + return nil, nil + } + + toCopy, err := intersect(a.Workspace, a.Sync, append(e.Added, e.Modified...)) + if err != nil { + return nil, errors.Wrap(err, "intersecting sync map and added, modified files") + } + + toDelete, err := intersect(a.Workspace, a.Sync, e.Deleted) + if err != nil { + return nil, errors.Wrap(err, "intersecting sync map and added, modified files") + } + + if toCopy == nil || toDelete == nil { + return nil, nil + } + + return &SyncItem{ + Image: a.ImageName, + Copy: toCopy, + Delete: toDelete, + }, nil +} + +func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { + ret := map[string]string{} + for _, f := range files { + relPath, err := filepath.Rel(context, f) + if err != nil { + return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) + } + for p, dst := range syncMap { + match, err := filepath.Match(p, relPath) + if err != nil { + return nil, errors.Wrap(err, "pattern error") + } + if !match { + return nil, nil + } + // If the source has special match characters, + // the destination must be a directory + if util.HasMeta(p) { + dst = filepath.Join(dst, filepath.Base(relPath)) + } + ret[f] = dst + } + } + return ret, nil +} diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go new file mode 100644 index 00000000000..19904c70d6f --- /dev/null +++ b/pkg/skaffold/sync/sync_test.go @@ -0,0 +1,176 @@ +package sync + +import ( + "testing" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" + "github.com/GoogleContainerTools/skaffold/testutil" +) + +func TestShouldSync(t *testing.T) { + var tests = []struct { + description string + artifact *v1alpha3.Artifact + evt watch.Events + shouldErr bool + expected *SyncItem + }{ + { + description: "match copy", + artifact: &v1alpha3.Artifact{ + ImageName: "test", + Sync: map[string]string{ + "*.html": ".", + }, + Workspace: ".", + }, + evt: watch.Events{ + Added: []string{"index.html"}, + }, + expected: &SyncItem{ + Image: "test", + Copy: map[string]string{ + "index.html": "index.html", + }, + Delete: map[string]string{}, + }, + }, + { + description: "sync all", + artifact: &v1alpha3.Artifact{ + ImageName: "test", + Sync: map[string]string{ + "*": ".", + }, + Workspace: "node", + }, + evt: watch.Events{ + Added: []string{"node/index.html"}, + Modified: []string{"node/server.js"}, + Deleted: []string{"node/package.json"}, + }, + expected: &SyncItem{ + Image: "test", + Copy: map[string]string{ + "node/server.js": "server.js", + "node/index.html": "index.html", + }, + Delete: map[string]string{ + "node/package.json": "package.json", + }, + }, + }, + { + description: "not copy syncable", + artifact: &v1alpha3.Artifact{ + Sync: map[string]string{ + "*.html": ".", + }, + Workspace: ".", + }, + evt: watch.Events{ + Added: []string{"main.go"}, + Deleted: []string{"index.html"}, + }, + }, + { + description: "not delete syncable", + artifact: &v1alpha3.Artifact{ + Sync: map[string]string{ + "*.html": "/static", + }, + Workspace: ".", + }, + evt: watch.Events{ + Added: []string{"index.html"}, + Deleted: []string{"some/other/file"}, + }, + }, + { + description: "err bad pattern", + artifact: &v1alpha3.Artifact{ + Sync: map[string]string{ + "[*.html": "*", + }, + Workspace: ".", + }, + evt: watch.Events{ + Added: []string{"index.html"}, + Deleted: []string{"some/other/file"}, + }, + shouldErr: true, + }, + { + description: "no change no sync", + artifact: &v1alpha3.Artifact{ + Sync: map[string]string{ + "[*.html": "*", + }, + Workspace: ".", + }, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + actual, err := NewSyncItem(test.artifact, test.evt) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) + }) + } + +} + +func TestIntersect(t *testing.T) { + var tests = []struct { + description string + syncPatterns map[string]string + files []string + context string + expected map[string]string + shouldErr bool + }{ + { + description: "nil sync patterns doesn't sync", + expected: map[string]string{}, + }, + { + description: "copy nested file to correct destination", + files: []string{"static/index.html", "static/test.html"}, + syncPatterns: map[string]string{ + "static/*.html": "/html", + }, + expected: map[string]string{ + "static/index.html": "/html/index.html", + "static/test.html": "/html/test.html", + }, + }, + { + description: "file not in . copies to correct destination", + files: []string{"node/server.js"}, + context: "node", + syncPatterns: map[string]string{ + "*.js": "/", + }, + expected: map[string]string{ + "node/server.js": "/server.js", + }, + }, + { + description: "file change not relative to context throws error", + files: []string{"node/server.js", "/something/test.js"}, + context: "node", + syncPatterns: map[string]string{ + "*.js": "/", + }, + shouldErr: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + actual, err := intersect(test.context, test.syncPatterns, test.files) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) + }) + } +} From 81bd94ca3f6cb67e719efc506ff68c7da9ac1302 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Fri, 28 Sep 2018 14:56:07 -0500 Subject: [PATCH 28/61] [examples] add flask hot-reload example --- integration/examples/hot-reload/node/Dockerfile | 3 +-- integration/examples/hot-reload/node/server.js | 2 +- integration/examples/hot-reload/python/Dockerfile | 9 +++++++++ integration/examples/hot-reload/python/app.py | 6 ++++++ integration/examples/hot-reload/python/k8s/pod.yaml | 10 ++++++++++ .../examples/hot-reload/python/requirements.txt | 1 + integration/examples/hot-reload/skaffold.yaml | 8 ++++---- 7 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 integration/examples/hot-reload/python/Dockerfile create mode 100644 integration/examples/hot-reload/python/app.py create mode 100644 integration/examples/hot-reload/python/k8s/pod.yaml create mode 100644 integration/examples/hot-reload/python/requirements.txt diff --git a/integration/examples/hot-reload/node/Dockerfile b/integration/examples/hot-reload/node/Dockerfile index abcafcbf67c..64839925af7 100644 --- a/integration/examples/hot-reload/node/Dockerfile +++ b/integration/examples/hot-reload/node/Dockerfile @@ -1,4 +1,3 @@ -FROM node:6.10.3 +FROM gcr.io/k8s-skaffold/nodemon CMD ["nodemon", "server.js"] -RUN npm install -g nodemon COPY *.js . diff --git a/integration/examples/hot-reload/node/server.js b/integration/examples/hot-reload/node/server.js index e70e2778394..5115ab9cd63 100644 --- a/integration/examples/hot-reload/node/server.js +++ b/integration/examples/hot-reload/node/server.js @@ -6,7 +6,7 @@ const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); - res.end('Hello World?\n'); + res.end('Hello World from Node!\n'); }); server.listen(port, hostname, () => { diff --git a/integration/examples/hot-reload/python/Dockerfile b/integration/examples/hot-reload/python/Dockerfile new file mode 100644 index 00000000000..84ba519f0bf --- /dev/null +++ b/integration/examples/hot-reload/python/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.7-slim +CMD ["python", "-m", "flask", "run"] +ENV FLASK_DEBUG=1 +ENV FLASK_APP=app.py + +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY *.py . + diff --git a/integration/examples/hot-reload/python/app.py b/integration/examples/hot-reload/python/app.py new file mode 100644 index 00000000000..be4bff4ac5b --- /dev/null +++ b/integration/examples/hot-reload/python/app.py @@ -0,0 +1,6 @@ +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello_world(): + return 'Hello, World from Flask!' \ No newline at end of file diff --git a/integration/examples/hot-reload/python/k8s/pod.yaml b/integration/examples/hot-reload/python/k8s/pod.yaml new file mode 100644 index 00000000000..54634a5c217 --- /dev/null +++ b/integration/examples/hot-reload/python/k8s/pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: python +spec: + containers: + - name: python + image: gcr.io/k8s-skaffold/python-reload + ports: + - containerPort: 5000 diff --git a/integration/examples/hot-reload/python/requirements.txt b/integration/examples/hot-reload/python/requirements.txt new file mode 100644 index 00000000000..2c34d09e113 --- /dev/null +++ b/integration/examples/hot-reload/python/requirements.txt @@ -0,0 +1 @@ +Flask==1.0 \ No newline at end of file diff --git a/integration/examples/hot-reload/skaffold.yaml b/integration/examples/hot-reload/skaffold.yaml index 4f16076fef6..fb4a293bb20 100644 --- a/integration/examples/hot-reload/skaffold.yaml +++ b/integration/examples/hot-reload/skaffold.yaml @@ -6,11 +6,11 @@ build: workspace: node sync: '*.js': . - - imageName: gcr.io/k8s-skaffold/react-example - workspace: react + - imageName: gcr.io/k8s-skaffold/python-reload + workspace: python sync: - src/*: . + '*.py': . deploy: kubectl: manifests: - - node/k8s/** + - "**/k8s" From f7b8a442ed2d3bafef46ec2caa33a5293ce0a179 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Fri, 28 Sep 2018 14:56:55 -0500 Subject: [PATCH 29/61] add boilerplate --- pkg/skaffold/sync/sync.go | 16 ++++++++++++++++ pkg/skaffold/sync/sync_test.go | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index eac9278c910..4a85c06079d 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -1,5 +1,21 @@ package sync +/* +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. +*/ + import ( "path/filepath" diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 19904c70d6f..01bd5e108c8 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -1,3 +1,18 @@ +/* +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 sync import ( From 799d630c830db577dc0ea8325f85465e288669c8 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sun, 30 Sep 2018 10:30:32 -0500 Subject: [PATCH 30/61] linter fixes --- pkg/skaffold/kubernetes/sync.go | 2 +- pkg/skaffold/runner/changes.go | 4 ++-- pkg/skaffold/runner/runner.go | 2 +- pkg/skaffold/runner/runner_test.go | 9 +-------- pkg/skaffold/sync/sync.go | 12 ++++++------ pkg/skaffold/sync/sync_test.go | 8 ++++---- 6 files changed, 15 insertions(+), 22 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 1db5642f4f6..0bf02065fbf 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -33,7 +33,7 @@ import ( type KubectlSyncer struct{} -func (k *KubectlSyncer) Sync(s *sync.SyncItem) error { +func (k *KubectlSyncer) Sync(s *sync.Item) error { if err := perform(s.Image, s.Copy, copyFileFn); err != nil { return errors.Wrap(err, "copying files") } diff --git a/pkg/skaffold/runner/changes.go b/pkg/skaffold/runner/changes.go index f5501aa0876..d62bf6f2347 100644 --- a/pkg/skaffold/runner/changes.go +++ b/pkg/skaffold/runner/changes.go @@ -25,7 +25,7 @@ import ( type changes struct { dirtyArtifacts []*artifactChange needsRebuild []*latest.Artifact - needsResync []*sync.SyncItem + needsResync []*sync.Item needsRedeploy bool needsReload bool } @@ -43,7 +43,7 @@ func (c *changes) AddRebuild(a *latest.Artifact) { c.needsRebuild = append(c.needsRebuild, a) } -func (c *changes) AddResync(s *sync.SyncItem) { +func (c *changes) AddResync(s *sync.Item) { c.needsResync = append(c.needsResync, s) } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index ba72e20ded4..97f66361373 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -255,7 +255,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la }() for _, a := range changed.dirtyArtifacts { - s, err := sync.NewSyncItem(a.artifact, a.events) + s, err := sync.NewItem(a.artifact, a.events) if err != nil { return errors.Wrap(err, "sync") } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index c23f1516bca..39784f780ba 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -123,14 +123,7 @@ func NewTestSyncer() *TestSyncer { } } -func NewTestSyncerWithError(err error) *TestSyncer { - return &TestSyncer{ - copies: map[string]string{}, - err: err, - } -} - -func (t *TestSyncer) Sync(s *sync.SyncItem) error { +func (t *TestSyncer) Sync(s *sync.Item) error { if t.err != nil { return t.err } diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 4a85c06079d..9e56ca0e333 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -1,5 +1,3 @@ -package sync - /* Copyright 2018 The Skaffold Authors @@ -16,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +package sync + import ( "path/filepath" @@ -26,16 +26,16 @@ import ( ) type Syncer interface { - Sync(s *SyncItem) error + Sync(s *Item) error } -type SyncItem struct { +type Item struct { Image string Copy map[string]string Delete map[string]string } -func NewSyncItem(a *v1alpha3.Artifact, e watch.Events) (*SyncItem, error) { +func NewItem(a *v1alpha3.Artifact, e watch.Events) (*Item, error) { // If there are no changes, short circuit and don't sync anything if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { return nil, nil @@ -55,7 +55,7 @@ func NewSyncItem(a *v1alpha3.Artifact, e watch.Events) (*SyncItem, error) { return nil, nil } - return &SyncItem{ + return &Item{ Image: a.ImageName, Copy: toCopy, Delete: toDelete, diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 01bd5e108c8..81de0f3ce7d 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -29,7 +29,7 @@ func TestShouldSync(t *testing.T) { artifact *v1alpha3.Artifact evt watch.Events shouldErr bool - expected *SyncItem + expected *Item }{ { description: "match copy", @@ -43,7 +43,7 @@ func TestShouldSync(t *testing.T) { evt: watch.Events{ Added: []string{"index.html"}, }, - expected: &SyncItem{ + expected: &Item{ Image: "test", Copy: map[string]string{ "index.html": "index.html", @@ -65,7 +65,7 @@ func TestShouldSync(t *testing.T) { Modified: []string{"node/server.js"}, Deleted: []string{"node/package.json"}, }, - expected: &SyncItem{ + expected: &Item{ Image: "test", Copy: map[string]string{ "node/server.js": "server.js", @@ -129,7 +129,7 @@ func TestShouldSync(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - actual, err := NewSyncItem(test.artifact, test.evt) + actual, err := NewItem(test.artifact, test.evt) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) }) } From d565e05d0068d6846c143bc33349fbf901ba5603 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sun, 30 Sep 2018 11:02:37 -0500 Subject: [PATCH 31/61] rebase on v1alpha4 --- integration/examples/hot-reload/skaffold.yaml | 2 +- pkg/skaffold/kubernetes/sync.go | 2 +- pkg/skaffold/runner/runner.go | 51 ------------------- pkg/skaffold/schema/latest/config.go | 5 +- pkg/skaffold/sync/sync.go | 4 +- pkg/skaffold/sync/sync_test.go | 16 +++--- 6 files changed, 15 insertions(+), 65 deletions(-) diff --git a/integration/examples/hot-reload/skaffold.yaml b/integration/examples/hot-reload/skaffold.yaml index fb4a293bb20..d888518b4e0 100644 --- a/integration/examples/hot-reload/skaffold.yaml +++ b/integration/examples/hot-reload/skaffold.yaml @@ -1,4 +1,4 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 0bf02065fbf..68570fe7189 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -73,7 +73,7 @@ func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Contai }) } if err := e.Wait(); err != nil { - return errors.Wrap(err, "syncing files:") + return errors.Wrap(err, "syncing files") } } } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 97f66361373..6a143474b6e 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -38,7 +38,6 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -385,56 +384,6 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil, watcher.Run(ctx, r.Trigger, onChange) } -func (r *SkaffoldRunner) shouldSync(image string, context string, syncPatterns map[string]string, e watch.Events) (bool, error) { - // If there are no changes, short circuit and don't sync anything - if !e.HasChanged() || syncPatterns == nil || len(syncPatterns) == 0 { - return false, nil - } - - toCopy, err := intersect(context, syncPatterns, append(e.Added, e.Modified...)) - if err != nil { - return false, errors.Wrap(err, "intersecting sync map and added, modified files") - } - // The only error that intersect can return is a bad pattern, which is checked above - toDelete, _ := intersect(context, syncPatterns, e.Deleted) - if toCopy == nil || toDelete == nil { - return false, nil - } - if err := r.Syncer.DeleteFilesForImage(image, toDelete); err != nil { - return false, errors.Wrap(err, "deleting files for image") - } - if err := r.Syncer.CopyFilesForImage(image, toCopy); err != nil { - return false, errors.Wrap(err, "copying files for image") - } - return true, nil -} - -func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { - ret := map[string]string{} - for _, f := range files { - relPath, err := filepath.Rel(context, f) - if err != nil { - return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) - } - for p, dst := range syncMap { - match, err := filepath.Match(p, relPath) - if err != nil { - return nil, errors.Wrap(err, "pattern error") - } - if !match { - return nil, nil - } - // If the source has special match characters, - // the destination must be a directory - if util.HasMeta(p) { - dst = filepath.Join(dst, filepath.Base(relPath)) - } - ret[f] = dst - } - } - return ret, nil -} - func (r *SkaffoldRunner) shouldWatch(artifact *latest.Artifact) bool { if len(r.opts.Watch) == 0 { return true diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index c9f7efbc1a8..02aa6c3faf4 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -210,8 +210,9 @@ 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:"image"` - Workspace string `yaml:"context,omitempty"` + ImageName string `yaml:"image"` + Workspace string `yaml:"context,omitempty"` + Sync map[string]string `yaml:"sync,omitempty"` ArtifactType `yaml:",inline"` } diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 9e56ca0e333..186db2e39a1 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -19,7 +19,7 @@ package sync import ( "path/filepath" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -35,7 +35,7 @@ type Item struct { Delete map[string]string } -func NewItem(a *v1alpha3.Artifact, e watch.Events) (*Item, error) { +func NewItem(a *latest.Artifact, e watch.Events) (*Item, error) { // If there are no changes, short circuit and don't sync anything if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { return nil, nil diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 81de0f3ce7d..75a792a2ac7 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -18,7 +18,7 @@ package sync import ( "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" ) @@ -26,14 +26,14 @@ import ( func TestShouldSync(t *testing.T) { var tests = []struct { description string - artifact *v1alpha3.Artifact + artifact *latest.Artifact evt watch.Events shouldErr bool expected *Item }{ { description: "match copy", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ ImageName: "test", Sync: map[string]string{ "*.html": ".", @@ -53,7 +53,7 @@ func TestShouldSync(t *testing.T) { }, { description: "sync all", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ ImageName: "test", Sync: map[string]string{ "*": ".", @@ -78,7 +78,7 @@ func TestShouldSync(t *testing.T) { }, { description: "not copy syncable", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ Sync: map[string]string{ "*.html": ".", }, @@ -91,7 +91,7 @@ func TestShouldSync(t *testing.T) { }, { description: "not delete syncable", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ Sync: map[string]string{ "*.html": "/static", }, @@ -104,7 +104,7 @@ func TestShouldSync(t *testing.T) { }, { description: "err bad pattern", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ Sync: map[string]string{ "[*.html": "*", }, @@ -118,7 +118,7 @@ func TestShouldSync(t *testing.T) { }, { description: "no change no sync", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ Sync: map[string]string{ "[*.html": "*", }, From 931b5d8c74f2d1d0a112f7da47ac41198c1baad7 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sun, 30 Sep 2018 11:23:09 -0500 Subject: [PATCH 32/61] fix windows test --- pkg/skaffold/sync/sync.go | 5 ++++- pkg/skaffold/sync/sync_test.go | 27 ++++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 186db2e39a1..1cea5cf1481 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -17,6 +17,7 @@ limitations under the License. package sync import ( + "path" "path/filepath" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" @@ -79,8 +80,10 @@ func intersect(context string, syncMap map[string]string, files []string) (map[s } // If the source has special match characters, // the destination must be a directory + // The path package must be used here, since the destination is always + // a linux filesystem. if util.HasMeta(p) { - dst = filepath.Join(dst, filepath.Base(relPath)) + dst = path.Join(dst, filepath.Base(relPath)) } ret[f] = dst } diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 75a792a2ac7..549648895a9 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -16,6 +16,7 @@ limitations under the License. package sync import ( + "path/filepath" "testing" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" @@ -61,18 +62,18 @@ func TestShouldSync(t *testing.T) { Workspace: "node", }, evt: watch.Events{ - Added: []string{"node/index.html"}, - Modified: []string{"node/server.js"}, - Deleted: []string{"node/package.json"}, + Added: []string{filepath.Join("node", "index.html")}, + Modified: []string{filepath.Join("node", "server.js")}, + Deleted: []string{filepath.Join("node", "package.json")}, }, expected: &Item{ Image: "test", Copy: map[string]string{ - "node/server.js": "server.js", - "node/index.html": "index.html", + filepath.Join("node", "server.js"): "server.js", + filepath.Join("node", "index.html"): "index.html", }, Delete: map[string]string{ - "node/package.json": "package.json", + filepath.Join("node", "package.json"): "package.json", }, }, }, @@ -151,29 +152,29 @@ func TestIntersect(t *testing.T) { }, { description: "copy nested file to correct destination", - files: []string{"static/index.html", "static/test.html"}, + files: []string{filepath.Join("static", "index.html"), filepath.Join("static", "test.html")}, syncPatterns: map[string]string{ - "static/*.html": "/html", + filepath.Join("static", "*.html"): "/html", }, expected: map[string]string{ - "static/index.html": "/html/index.html", - "static/test.html": "/html/test.html", + filepath.Join("static", "index.html"): "/html/index.html", + filepath.Join("static", "test.html"): "/html/test.html", }, }, { description: "file not in . copies to correct destination", - files: []string{"node/server.js"}, + files: []string{filepath.Join("node", "server.js")}, context: "node", syncPatterns: map[string]string{ "*.js": "/", }, expected: map[string]string{ - "node/server.js": "/server.js", + filepath.Join("node", "server.js"): "/server.js", }, }, { description: "file change not relative to context throws error", - files: []string{"node/server.js", "/something/test.js"}, + files: []string{filepath.Join("node", "server.js"), filepath.Join("/", "something", "test.js")}, context: "node", syncPatterns: map[string]string{ "*.js": "/", From 35ef51c7d662379ee001c90013cd80dd3145fa4e Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Mon, 1 Oct 2018 10:47:19 -0500 Subject: [PATCH 33/61] [parse] remove loud debug statements These run every second, so its difficult to read other debug statements --- pkg/skaffold/docker/parse.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/skaffold/docker/parse.go b/pkg/skaffold/docker/parse.go index 52abf8993ed..80d9b7a320b 100644 --- a/pkg/skaffold/docker/parse.go +++ b/pkg/skaffold/docker/parse.go @@ -249,7 +249,7 @@ func expandPaths(workspace string, copied [][]string) ([]string, error) { for dep := range expandedPaths { deps = append(deps, dep) } - // logrus.Infof("Found dependencies for dockerfile %s", deps) + logrus.Infof("Found dependencies for dockerfile %s", deps) return deps, nil } From 3f8ba1ee2431c50b478b7f616fb502bcfe03198e Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Mon, 1 Oct 2018 11:12:49 -0500 Subject: [PATCH 34/61] [sync] query only skaffold pods, use exact tag --- pkg/skaffold/kubernetes/sync.go | 15 ++++++-- pkg/skaffold/kubernetes/sync_test.go | 4 ++- pkg/skaffold/runner/runner.go | 4 +-- pkg/skaffold/sync/sync.go | 20 +++++++++-- pkg/skaffold/sync/sync_test.go | 51 +++++++++++++++++++++++++--- 5 files changed, 82 insertions(+), 12 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 68570fe7189..511f35563b2 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -21,6 +21,7 @@ import ( "os/exec" "strings" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/sirupsen/logrus" @@ -51,19 +52,29 @@ func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { return exec.Command("kubectl", "cp", src, fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, dst), "-c", container.Name) } +func labelSelector() string { + var reqs []string + for k, v := range constants.Labels.DefaultLabels { + reqs = append(reqs, fmt.Sprintf("%s=%s", k, v)) + } + return strings.Join(reqs, ",") +} + func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd) error { logrus.Info("Syncing files:", files) client, err := Client() if err != nil { return errors.Wrap(err, "getting k8s client") } - pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{}) + pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{ + LabelSelector: labelSelector(), + }) if err != nil { return errors.Wrap(err, "getting pods") } for _, p := range pods.Items { for _, c := range p.Spec.Containers { - if strings.HasPrefix(c.Image, image) { + if c.Image == image { var e errgroup.Group for src, dst := range files { src, dst := src, dst diff --git a/pkg/skaffold/kubernetes/sync_test.go b/pkg/skaffold/kubernetes/sync_test.go index 9e071cacc27..2cc70021dc0 100644 --- a/pkg/skaffold/kubernetes/sync_test.go +++ b/pkg/skaffold/kubernetes/sync_test.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" v1 "k8s.io/api/core/v1" @@ -54,7 +55,8 @@ func fakeCmd(p v1.Pod, c v1.Container, src, dst string) *exec.Cmd { var pod = &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "podname", + Name: "podname", + Labels: constants.Labels.DefaultLabels, }, Status: v1.PodStatus{ Phase: v1.PodRunning, diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 6a143474b6e..d515cd2a6b8 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -252,9 +252,8 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la logger.Unmute() } }() - for _, a := range changed.dirtyArtifacts { - s, err := sync.NewItem(a.artifact, a.events) + s, err := sync.NewItem(a.artifact, a.events, r.builds) if err != nil { return errors.Wrap(err, "sync") } @@ -276,6 +275,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la logrus.Warnln("Skipping build and deploy due to sync error:", err) return nil } + color.Default.Fprintf(out, "Synced files for %s...\nCopied: %s\nDeleted: %s\n", s.Image, s.Copy, s.Delete) } case len(changed.needsRebuild) > 0: bRes, err := r.Build(ctx, out, r.Tagger, changed.needsRebuild) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 1cea5cf1481..472a581b40b 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -17,10 +17,12 @@ limitations under the License. package sync import ( + "fmt" "path" "path/filepath" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -36,7 +38,7 @@ type Item struct { Delete map[string]string } -func NewItem(a *latest.Artifact, e watch.Events) (*Item, error) { +func NewItem(a *latest.Artifact, e watch.Events, builds []build.Artifact) (*Item, error) { // If there are no changes, short circuit and don't sync anything if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { return nil, nil @@ -56,13 +58,27 @@ func NewItem(a *latest.Artifact, e watch.Events) (*Item, error) { return nil, nil } + tag := latestTag(a.ImageName, builds) + if tag == "" { + return nil, fmt.Errorf("Could not find latest tag for image %s in builds: %s", a.ImageName, builds) + } + return &Item{ - Image: a.ImageName, + Image: tag, Copy: toCopy, Delete: toDelete, }, nil } +func latestTag(image string, builds []build.Artifact) string { + for _, build := range builds { + if build.ImageName == image { + return build.Tag + } + } + return "" +} + func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { ret := map[string]string{} for _, f := range files { diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 549648895a9..ea310375177 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -19,16 +19,19 @@ import ( "path/filepath" "testing" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" ) -func TestShouldSync(t *testing.T) { +func TestNewSyncItem(t *testing.T) { var tests = []struct { description string artifact *latest.Artifact evt watch.Events + builds []build.Artifact shouldErr bool expected *Item }{ @@ -41,17 +44,43 @@ func TestShouldSync(t *testing.T) { }, Workspace: ".", }, + builds: []build.Artifact{ + { + ImageName: "test", + Tag: "test:123", + }, + }, evt: watch.Events{ Added: []string{"index.html"}, }, expected: &Item{ - Image: "test", + Image: "test:123", Copy: map[string]string{ "index.html": "index.html", }, Delete: map[string]string{}, }, }, + { + description: "no tag for image", + artifact: &latest.Artifact{ + ImageName: "notbuildyet", + Sync: map[string]string{ + "*.html": ".", + }, + Workspace: ".", + }, + builds: []build.Artifact{ + { + ImageName: "test", + Tag: "test:123", + }, + }, + evt: watch.Events{ + Added: []string{"index.html"}, + }, + shouldErr: true, + }, { description: "sync all", artifact: &latest.Artifact{ @@ -61,13 +90,19 @@ func TestShouldSync(t *testing.T) { }, Workspace: "node", }, + builds: []build.Artifact{ + { + ImageName: "test", + Tag: "test:123", + }, + }, evt: watch.Events{ Added: []string{filepath.Join("node", "index.html")}, Modified: []string{filepath.Join("node", "server.js")}, Deleted: []string{filepath.Join("node", "package.json")}, }, expected: &Item{ - Image: "test", + Image: "test:123", Copy: map[string]string{ filepath.Join("node", "server.js"): "server.js", filepath.Join("node", "index.html"): "index.html", @@ -121,16 +156,22 @@ func TestShouldSync(t *testing.T) { description: "no change no sync", artifact: &latest.Artifact{ Sync: map[string]string{ - "[*.html": "*", + "*.html": "*", }, Workspace: ".", }, + builds: []build.Artifact{ + { + ImageName: "test", + Tag: "test:123", + }, + }, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { - actual, err := NewItem(test.artifact, test.evt) + actual, err := NewItem(test.artifact, test.evt, test.builds) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) }) } From c7b0fd5f2c362cdb3ba1d9554b4e2154866be874 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Tue, 2 Oct 2018 12:51:36 -0500 Subject: [PATCH 35/61] rebase on v1alpha4 config --- cmd/skaffold/app/cmd/fix.go | 3 ++- integration/examples/hot-reload/skaffold.yaml | 8 ++++---- pkg/skaffold/schema/latest/config.go | 2 +- pkg/skaffold/sync/sync.go | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/skaffold/app/cmd/fix.go b/cmd/skaffold/app/cmd/fix.go index 1a6d7f1335c..9831f3c3574 100644 --- a/cmd/skaffold/app/cmd/fix.go +++ b/cmd/skaffold/app/cmd/fix.go @@ -22,12 +22,13 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" schemautil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" yaml "gopkg.in/yaml.v2" - "k8s.io/client-go/tools/clientcmd/api/latest" ) func NewCmdFix(out io.Writer) *cobra.Command { diff --git a/integration/examples/hot-reload/skaffold.yaml b/integration/examples/hot-reload/skaffold.yaml index d888518b4e0..4daf1c6fba1 100644 --- a/integration/examples/hot-reload/skaffold.yaml +++ b/integration/examples/hot-reload/skaffold.yaml @@ -2,12 +2,12 @@ apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: - - imageName: gcr.io/k8s-skaffold/node-example - workspace: node + - image: gcr.io/k8s-skaffold/node-example + context: node sync: '*.js': . - - imageName: gcr.io/k8s-skaffold/python-reload - workspace: python + - image: gcr.io/k8s-skaffold/python-reload + context: python sync: '*.py': . deploy: diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 02aa6c3faf4..6fbdc34e296 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -210,7 +210,7 @@ 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:"image"` + Image string `yaml:"image"` Workspace string `yaml:"context,omitempty"` Sync map[string]string `yaml:"sync,omitempty"` ArtifactType `yaml:",inline"` diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 472a581b40b..d2130721ada 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -21,8 +21,8 @@ import ( "path" "path/filepath" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" From 10d5d31a390d5239f8acbdd006c9d8eb9296fad6 Mon Sep 17 00:00:00 2001 From: r2d4 Date: Wed, 3 Oct 2018 09:37:51 -0500 Subject: [PATCH 36/61] delete out of date readme --- integration/examples/hot-reload/README.adoc | 77 --------------------- pkg/skaffold/schema/latest/config.go | 2 +- 2 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 integration/examples/hot-reload/README.adoc diff --git a/integration/examples/hot-reload/README.adoc b/integration/examples/hot-reload/README.adoc deleted file mode 100644 index b45e41779de..00000000000 --- a/integration/examples/hot-reload/README.adoc +++ /dev/null @@ -1,77 +0,0 @@ -=== Example: File Sync with Skaffold -:icons: font - -ifndef::env-github[] -link:{github-repo-tree}/examples/webserver[see on Github icon:github[]] -endif::[] - -In this example: - -* Deploy a basic golang webserver that serves static files -* In development, sync static HTML files without triggering a rebuild or redeploy of the artifacts - -Not all file changes should require a complete rebuild and redeploy of the skaffold artifacts. -This tutorial shows how you can use the file sync feature of skaffold for very quick iterative development. - -==== Running the example on minikube - -From this directory, run - -```bash -skaffold dev -``` - -Now in a different terminal, hit the webserver's endpoint to see index.html - -```bash -$ curl localhost:8080 -Hello World!! -``` - -Now, edit the index.html file to contain something else. You'll see that skaffold syncs the file to the already running container, without rebuilding or redeploying. -```bash -echo "Hello skaffold" > index.html -``` - -You should see that skaffold has synced those changes in the other terminal -```bash -... -Synced: copied [index.html] deleted [] -Watching for changes... -``` - -Now, lets add a new file that matches the glob pattern but wasn't in the original container. -```bash -echo "Dogs are great" > cats.html -``` - -Now you can see that change immediately. -```bash -$ curl localhost:8080/cats.html -"Dogs are great" -``` - -Delete the file -```bash -rm cats.html -``` - -Now you can see that change immediately -```bash -$ curl localhost:8080/cats.html -404 page not found -``` - -==== Configuration walkthrough - -Let's walk through the first part of the skaffold.yaml - -```yaml - artifacts: - - imageName: gcr.io/k8s-skaffold/webserver-example - sync: - '*.html': '/static' -``` - -This will sync all files that match the *.html pattern to the /static/ folder in the container. -Sync can be multiple keys and the only restriction is that the destination must be a folder if the source is a pattern. \ No newline at end of file diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 6fbdc34e296..02aa6c3faf4 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -210,7 +210,7 @@ type HelmConventionConfig struct { // Artifact represents items that need to be built, along with the context in which // they should be built. type Artifact struct { - Image string `yaml:"image"` + ImageName string `yaml:"image"` Workspace string `yaml:"context,omitempty"` Sync map[string]string `yaml:"sync,omitempty"` ArtifactType `yaml:",inline"` From 8f9ac9a35f4b8da8b7c4fa8a5416594e7ed7b12e Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 3 Oct 2018 12:02:08 -0500 Subject: [PATCH 37/61] add legacy watch for nodemon example --- integration/examples/hot-reload/node/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/examples/hot-reload/node/Dockerfile b/integration/examples/hot-reload/node/Dockerfile index 64839925af7..c248d57ffe2 100644 --- a/integration/examples/hot-reload/node/Dockerfile +++ b/integration/examples/hot-reload/node/Dockerfile @@ -1,3 +1,3 @@ FROM gcr.io/k8s-skaffold/nodemon -CMD ["nodemon", "server.js"] +CMD ["nodemon","--legacy-watch", "server.js"] COPY *.js . From 8a22fd8f81df07e90e7eaa1db02590387f41170f Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Thu, 4 Oct 2018 09:45:48 -0500 Subject: [PATCH 38/61] review feedback --- pkg/skaffold/sync/sync.go | 4 ++-- pkg/skaffold/util/util.go | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index d2130721ada..d53ab842b0b 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -84,12 +84,12 @@ func intersect(context string, syncMap map[string]string, files []string) (map[s for _, f := range files { relPath, err := filepath.Rel(context, f) if err != nil { - return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) + return nil, errors.Wrapf(err, "changed file %s can't be found relative to context %s", f, context) } for p, dst := range syncMap { match, err := filepath.Match(p, relPath) if err != nil { - return nil, errors.Wrap(err, "pattern error") + return nil, errors.Wrapf(err, "pattern error for %s", relPath) } if !match { return nil, nil diff --git a/pkg/skaffold/util/util.go b/pkg/skaffold/util/util.go index a8bcd5a0b2c..28f9fde99bc 100644 --- a/pkg/skaffold/util/util.go +++ b/pkg/skaffold/util/util.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "sort" "strings" @@ -111,7 +112,11 @@ func ExpandPathsGlob(workingDir string, paths []string) ([]string, error) { // recognized by filepath.Match. // This is a copy of filepath/match.go's hasMeta func HasMeta(path string) bool { - return strings.ContainsAny(path, "*?[") + magicChars := `*?[` + if runtime.GOOS != "windows" { + magicChars = `*?[\` + } + return strings.ContainsAny(path, magicChars) } // BoolPtr returns a pointer to a bool From be4cc6c7a6fd0e00e34d1e50df0e53d3357086fb Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Thu, 4 Oct 2018 09:58:18 -0500 Subject: [PATCH 39/61] rebase changes --- Gopkg.lock | 1 + pkg/skaffold/runner/runner.go | 1 + pkg/skaffold/runner/runner_test.go | 5 +---- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 33e9738b525..4e4bfd9a869 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1203,6 +1203,7 @@ "k8s.io/apimachinery/pkg/api/meta", "k8s.io/apimachinery/pkg/apis/meta/v1", "k8s.io/apimachinery/pkg/fields", + "k8s.io/apimachinery/pkg/labels", "k8s.io/apimachinery/pkg/runtime", "k8s.io/apimachinery/pkg/runtime/schema", "k8s.io/apimachinery/pkg/types", diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index d515cd2a6b8..97c88467d34 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,6 +30,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 39784f780ba..8ab01d131a1 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -446,10 +446,7 @@ func TestDev(t *testing.T) { Trigger: trigger, watchFactory: test.watcherFactory, opts: opts, - opts: &config.SkaffoldOptions{ - WatchPollInterval: 100, - }, - Syncer: NewTestSyncer(), + Syncer: NewTestSyncer(), } _, err := runner.Dev(context.Background(), ioutil.Discard, nil) From 9b11bc4cbfae3b652b223b6dde4e88ffab3019d2 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Thu, 4 Oct 2018 12:38:22 -0500 Subject: [PATCH 40/61] debug and info messages only for sync --- pkg/skaffold/runner/runner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 97c88467d34..e9aac715715 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,7 +30,6 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" @@ -276,7 +275,8 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la logrus.Warnln("Skipping build and deploy due to sync error:", err) return nil } - color.Default.Fprintf(out, "Synced files for %s...\nCopied: %s\nDeleted: %s\n", s.Image, s.Copy, s.Delete) + logrus.Infof("Synced %d files for %s", len(s.Copy)+len(s.Delete), s.Image) + logrus.Debugf("Synced files for %s...\nCopied: %s\nDeleted: %s\n", s.Image, s.Copy, s.Delete) } case len(changed.needsRebuild) > 0: bRes, err := r.Build(ctx, out, r.Tagger, changed.needsRebuild) From 89bbf698ee52e4973de63cb60bf29638770bb49c Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 08:41:15 +0200 Subject: [PATCH 41/61] Add a few logs to Kaniko builder Signed-off-by: David Gageot --- pkg/skaffold/build/kaniko/run.go | 5 ++++- pkg/skaffold/build/kaniko/secret.go | 2 ++ pkg/skaffold/docker/context.go | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/skaffold/build/kaniko/run.go b/pkg/skaffold/build/kaniko/run.go index 200f4be63a9..5a86632c0f8 100644 --- a/pkg/skaffold/build/kaniko/run.go +++ b/pkg/skaffold/build/kaniko/run.go @@ -43,9 +43,11 @@ func runKaniko(ctx context.Context, out io.Writer, artifact *latest.Artifact, cf dockerfilePath := artifact.DockerArtifact.DockerfilePath initialTag := util.RandomID() + + logrus.Debug("Upload sources to GCS") tarName := fmt.Sprintf("context-%s.tar.gz", initialTag) if err := docker.UploadContextToGCS(ctx, artifact.Workspace, artifact.DockerArtifact, cfg.BuildContext.GCSBucket, tarName); err != nil { - return "", errors.Wrap(err, "uploading tar to gcs") + return "", errors.Wrap(err, "uploading sources to GCS") } defer gcsDelete(ctx, cfg.BuildContext.GCSBucket, tarName) @@ -64,6 +66,7 @@ func runKaniko(ctx context.Context, out io.Writer, artifact *latest.Artifact, cf } args = append(args, docker.GetBuildArgs(artifact.DockerArtifact)...) + logrus.Debug("Creating pod") p, err := pods.Create(&v1.Pod{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "kaniko", diff --git a/pkg/skaffold/build/kaniko/secret.go b/pkg/skaffold/build/kaniko/secret.go index 3224d9407b6..d748a8b1393 100644 --- a/pkg/skaffold/build/kaniko/secret.go +++ b/pkg/skaffold/build/kaniko/secret.go @@ -28,6 +28,8 @@ import ( ) func (b *Builder) setupSecret() (func(), error) { + logrus.Debug("Creating kaniko secret") + client, err := kubernetes.GetClientset() if err != nil { return nil, errors.Wrap(err, "getting kubernetes client") diff --git a/pkg/skaffold/docker/context.go b/pkg/skaffold/docker/context.go index 27e834fb472..013353b1d06 100644 --- a/pkg/skaffold/docker/context.go +++ b/pkg/skaffold/docker/context.go @@ -69,7 +69,7 @@ func CreateDockerTarGzContext(w io.Writer, workspace string, a *latest.DockerArt func UploadContextToGCS(ctx context.Context, workspace string, a *latest.DockerArtifact, bucket, objectName string) error { c, err := cstorage.NewClient(ctx) if err != nil { - return err + return errors.Wrap(err, "creating GCS client") } defer c.Close() From 18f4a64638a34c7d4172ffab59ed1fd30f29b39c Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 09:31:52 +0200 Subject: [PATCH 42/61] Auto-detect GCP project/bucket Signed-off-by: David Gageot --- pkg/skaffold/build/kaniko/run.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pkg/skaffold/build/kaniko/run.go b/pkg/skaffold/build/kaniko/run.go index 5a86632c0f8..c42290497f7 100644 --- a/pkg/skaffold/build/kaniko/run.go +++ b/pkg/skaffold/build/kaniko/run.go @@ -27,6 +27,7 @@ import ( cstorage "cloud.google.com/go/storage" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/gcp" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" @@ -40,16 +41,25 @@ import ( const kanikoContainerName = "kaniko" func runKaniko(ctx context.Context, out io.Writer, artifact *latest.Artifact, cfg *latest.KanikoBuild) (string, error) { + initialTag := util.RandomID() dockerfilePath := artifact.DockerArtifact.DockerfilePath - initialTag := util.RandomID() + bucket := cfg.BuildContext.GCSBucket + if bucket == "" { + guessedProjectID, err := gcp.ExtractProjectID(artifact.ImageName) + if err != nil { + return "", errors.Wrap(err, "extracting projectID from image name") + } + + bucket = guessedProjectID + } + logrus.Debugln("Upload sources to", bucket, "GCS bucket") - logrus.Debug("Upload sources to GCS") tarName := fmt.Sprintf("context-%s.tar.gz", initialTag) - if err := docker.UploadContextToGCS(ctx, artifact.Workspace, artifact.DockerArtifact, cfg.BuildContext.GCSBucket, tarName); err != nil { + if err := docker.UploadContextToGCS(ctx, artifact.Workspace, artifact.DockerArtifact, bucket, tarName); err != nil { return "", errors.Wrap(err, "uploading sources to GCS") } - defer gcsDelete(ctx, cfg.BuildContext.GCSBucket, tarName) + defer gcsDelete(ctx, bucket, tarName) client, err := kubernetes.GetClientset() if err != nil { @@ -60,7 +70,7 @@ func runKaniko(ctx context.Context, out io.Writer, artifact *latest.Artifact, cf imageDst := fmt.Sprintf("%s:%s", artifact.ImageName, initialTag) args := []string{ fmt.Sprintf("--dockerfile=%s", dockerfilePath), - fmt.Sprintf("--context=gs://%s/%s", cfg.BuildContext.GCSBucket, tarName), + fmt.Sprintf("--context=gs://%s/%s", bucket, tarName), fmt.Sprintf("--destination=%s", imageDst), fmt.Sprintf("-v=%s", logrus.GetLevel().String()), } From db35ea52b0f56239b1c4a666f93a8a3903c231ad Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 09:34:59 +0200 Subject: [PATCH 43/61] Override Kaniko image Signed-off-by: David Gageot --- pkg/skaffold/build/kaniko/run.go | 30 ++++++++++++-------------- pkg/skaffold/schema/latest/config.go | 1 + pkg/skaffold/schema/latest/defaults.go | 12 +++++++++++ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/pkg/skaffold/build/kaniko/run.go b/pkg/skaffold/build/kaniko/run.go index c42290497f7..13ec8b3cb9f 100644 --- a/pkg/skaffold/build/kaniko/run.go +++ b/pkg/skaffold/build/kaniko/run.go @@ -84,22 +84,20 @@ func runKaniko(ctx context.Context, out io.Writer, artifact *latest.Artifact, cf Namespace: cfg.Namespace, }, Spec: v1.PodSpec{ - Containers: []v1.Container{ - { - Name: kanikoContainerName, - Image: constants.DefaultKanikoImage, - ImagePullPolicy: v1.PullIfNotPresent, - Args: args, - VolumeMounts: []v1.VolumeMount{{ - Name: constants.DefaultKanikoSecretName, - MountPath: "/secret", - }}, - Env: []v1.EnvVar{{ - Name: "GOOGLE_APPLICATION_CREDENTIALS", - Value: "/secret/kaniko-secret", - }}, - }, - }, + Containers: []v1.Container{{ + Name: kanikoContainerName, + Image: cfg.Image, + ImagePullPolicy: v1.PullIfNotPresent, + Args: args, + VolumeMounts: []v1.VolumeMount{{ + Name: constants.DefaultKanikoSecretName, + MountPath: "/secret", + }}, + Env: []v1.EnvVar{{ + Name: "GOOGLE_APPLICATION_CREDENTIALS", + Value: "/secret/kaniko-secret", + }}, + }}, Volumes: []v1.Volume{{ Name: constants.DefaultKanikoSecretName, VolumeSource: v1.VolumeSource{ diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 02aa6c3faf4..6bc2d77a9e9 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -115,6 +115,7 @@ type KanikoBuild struct { PullSecretName string `yaml:"pullSecretName,omitempty"` Namespace string `yaml:"namespace,omitempty"` Timeout string `yaml:"timeout,omitempty"` + Image string `yaml:"image,omitempty"` } // TestCase is a struct containing all the specified test diff --git a/pkg/skaffold/schema/latest/defaults.go b/pkg/skaffold/schema/latest/defaults.go index 7c9f49f8c1a..c437ff3d9b1 100644 --- a/pkg/skaffold/schema/latest/defaults.go +++ b/pkg/skaffold/schema/latest/defaults.go @@ -36,6 +36,7 @@ func (c *SkaffoldConfig) SetDefaultValues() error { c.setDefaultKustomizePath() c.setDefaultKubectlManifests() c.setDefaultKanikoTimeout() + c.setDefaultKanikoImage() if err := c.setDefaultKanikoNamespace(); err != nil { return err } @@ -155,6 +156,17 @@ func (c *SkaffoldConfig) setDefaultKanikoTimeout() { } } +func (c *SkaffoldConfig) setDefaultKanikoImage() { + kaniko := c.Build.KanikoBuild + if kaniko == nil { + return + } + + if kaniko.Image == "" { + kaniko.Image = constants.DefaultKanikoImage + } +} + func (c *SkaffoldConfig) setDefaultKanikoSecret() error { kaniko := c.Build.KanikoBuild if kaniko == nil { From 650901828ed6006f757b475c7d7dfc07a53e5a7e Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 09:38:58 +0200 Subject: [PATCH 44/61] Upgrade to the Kaniko 0.4.0 Signed-off-by: David Gageot --- pkg/skaffold/constants/constants.go | 2 +- pkg/skaffold/schema/versions_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/skaffold/constants/constants.go b/pkg/skaffold/constants/constants.go index 30ad591c408..8592d980608 100644 --- a/pkg/skaffold/constants/constants.go +++ b/pkg/skaffold/constants/constants.go @@ -46,7 +46,7 @@ const ( DefaultKustomizationPath = "." - DefaultKanikoImage = "gcr.io/kaniko-project/executor:v0.2.0@sha256:bebe80bb97950d88b8d8eab315a58e0bc50307135cf25147d7e0b8f3db50a84a" + DefaultKanikoImage = "gcr.io/kaniko-project/executor:v0.4.0@sha256:0bbaa4859eec9796d32ab45e6c1627562dbc7796e40450295b9604cd3f4197af" DefaultKanikoSecretName = "kaniko-secret" DefaultKanikoTimeout = "20m" diff --git a/pkg/skaffold/schema/versions_test.go b/pkg/skaffold/schema/versions_test.go index 17bd25a6fcc..ad3b4a2b9b0 100644 --- a/pkg/skaffold/schema/versions_test.go +++ b/pkg/skaffold/schema/versions_test.go @@ -20,6 +20,7 @@ import ( "fmt" "testing" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1" @@ -223,6 +224,7 @@ func withKanikoBuild(bucket, secretName, namespace, secret string, timeout strin Namespace: namespace, PullSecret: secret, Timeout: timeout, + Image: constants.DefaultKanikoImage, }}} for _, op := range ops { op(&b) From b0df629159012034ed9ecc5359feba2f6db26df6 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 09:41:55 +0200 Subject: [PATCH 45/61] Simplify code that sets default config Signed-off-by: David Gageot --- pkg/skaffold/schema/latest/defaults.go | 92 ++++++++++++-------------- 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/pkg/skaffold/schema/latest/defaults.go b/pkg/skaffold/schema/latest/defaults.go index c437ff3d9b1..8850720d035 100644 --- a/pkg/skaffold/schema/latest/defaults.go +++ b/pkg/skaffold/schema/latest/defaults.go @@ -19,12 +19,12 @@ package latest import ( "fmt" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" + kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" homedir "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" "github.com/sirupsen/logrus" - - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" - kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" ) // SetDefaultValues makes sure default values are set. @@ -35,12 +35,13 @@ func (c *SkaffoldConfig) SetDefaultValues() error { c.setDefaultTagger() c.setDefaultKustomizePath() c.setDefaultKubectlManifests() - c.setDefaultKanikoTimeout() - c.setDefaultKanikoImage() - if err := c.setDefaultKanikoNamespace(); err != nil { - return err - } - if err := c.setDefaultKanikoSecret(); err != nil { + + if err := c.withKanikoConfig( + setDefaultKanikoTimeout, + setDefaultKanikoImage, + setDefaultKanikoNamespace, + setDefaultKanikoSecret, + ); err != nil { return err } @@ -77,9 +78,7 @@ func (c *SkaffoldConfig) setDefaultCloudBuildDockerImage() { return } - if cloudBuild.DockerImage == "" { - cloudBuild.DockerImage = constants.DefaultCloudBuildDockerImage - } + cloudBuild.DockerImage = valueOrDefault(cloudBuild.DockerImage, constants.DefaultCloudBuildDockerImage) } func (c *SkaffoldConfig) setDefaultTagger() { @@ -96,9 +95,7 @@ func (c *SkaffoldConfig) setDefaultKustomizePath() { return } - if kustomize.KustomizePath == "" { - kustomize.KustomizePath = constants.DefaultKustomizationPath - } + kustomize.KustomizePath = valueOrDefault(kustomize.KustomizePath, constants.DefaultKustomizationPath) } func (c *SkaffoldConfig) setDefaultKubectlManifests() { @@ -116,23 +113,28 @@ func (c *SkaffoldConfig) defaultToDockerArtifact(a *Artifact) { } func (c *SkaffoldConfig) setDefaultDockerfile(a *Artifact) { - if a.DockerArtifact != nil && a.DockerArtifact.DockerfilePath == "" { - a.DockerArtifact.DockerfilePath = constants.DefaultDockerfilePath + if a.DockerArtifact != nil { + a.DockerArtifact.DockerfilePath = valueOrDefault(a.DockerArtifact.DockerfilePath, constants.DefaultDockerfilePath) } } func (c *SkaffoldConfig) setDefaultWorkspace(a *Artifact) { - if a.Workspace == "" { - a.Workspace = "." - } + a.Workspace = valueOrDefault(a.Workspace, ".") } -func (c *SkaffoldConfig) setDefaultKanikoNamespace() error { - kaniko := c.Build.KanikoBuild - if kaniko == nil { - return nil +func (c *SkaffoldConfig) withKanikoConfig(operations ...func(kaniko *KanikoBuild) error) error { + if kaniko := c.Build.KanikoBuild; kaniko != nil { + for _, operation := range operations { + if err := operation(kaniko); err != nil { + return err + } + } } + return nil +} + +func setDefaultKanikoNamespace(kaniko *KanikoBuild) error { if kaniko.Namespace == "" { ns, err := currentNamespace() if err != nil { @@ -145,37 +147,18 @@ func (c *SkaffoldConfig) setDefaultKanikoNamespace() error { return nil } -func (c *SkaffoldConfig) setDefaultKanikoTimeout() { - kaniko := c.Build.KanikoBuild - if kaniko == nil { - return - } - - if kaniko.Timeout == "" { - kaniko.Timeout = constants.DefaultKanikoTimeout - } +func setDefaultKanikoTimeout(kaniko *KanikoBuild) error { + kaniko.Timeout = valueOrDefault(kaniko.Timeout, constants.DefaultKanikoTimeout) + return nil } -func (c *SkaffoldConfig) setDefaultKanikoImage() { - kaniko := c.Build.KanikoBuild - if kaniko == nil { - return - } - - if kaniko.Image == "" { - kaniko.Image = constants.DefaultKanikoImage - } +func setDefaultKanikoImage(kaniko *KanikoBuild) error { + kaniko.Image = valueOrDefault(kaniko.Image, constants.DefaultKanikoImage) + return nil } -func (c *SkaffoldConfig) setDefaultKanikoSecret() error { - kaniko := c.Build.KanikoBuild - if kaniko == nil { - return nil - } - - if kaniko.PullSecretName == "" { - kaniko.PullSecretName = constants.DefaultKanikoSecretName - } +func setDefaultKanikoSecret(kaniko *KanikoBuild) error { + kaniko.PullSecretName = valueOrDefault(kaniko.PullSecretName, constants.DefaultKanikoSecretName) if kaniko.PullSecret != "" { absPath, err := homedir.Expand(kaniko.PullSecret) @@ -190,6 +173,13 @@ func (c *SkaffoldConfig) setDefaultKanikoSecret() error { return nil } +func valueOrDefault(v, def string) string { + if v != "" { + return v + } + return def +} + func currentNamespace() (string, error) { cfg, err := kubectx.CurrentConfig() if err != nil { From b0091ea560123ac54ea78bcaaf8b17dd4a88736c Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 10:42:52 +0200 Subject: [PATCH 46/61] Fix errors Signed-off-by: David Gageot --- pkg/skaffold/sync/sync.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index d53ab842b0b..ed5a1fd0bff 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -51,7 +51,7 @@ func NewItem(a *latest.Artifact, e watch.Events, builds []build.Artifact) (*Item toDelete, err := intersect(a.Workspace, a.Sync, e.Deleted) if err != nil { - return nil, errors.Wrap(err, "intersecting sync map and added, modified files") + return nil, errors.Wrap(err, "intersecting sync map and deleted files") } if toCopy == nil || toDelete == nil { @@ -60,7 +60,7 @@ func NewItem(a *latest.Artifact, e watch.Events, builds []build.Artifact) (*Item tag := latestTag(a.ImageName, builds) if tag == "" { - return nil, fmt.Errorf("Could not find latest tag for image %s in builds: %s", a.ImageName, builds) + return nil, fmt.Errorf("could not find latest tag for image %s in builds: %v", a.ImageName, builds) } return &Item{ From faecc291fa235863b3e3574cf83df045676f504b Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 10:43:11 +0200 Subject: [PATCH 47/61] Not needed --- pkg/skaffold/sync/sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index ed5a1fd0bff..589e84c81c9 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -40,7 +40,7 @@ type Item struct { func NewItem(a *latest.Artifact, e watch.Events, builds []build.Artifact) (*Item, error) { // If there are no changes, short circuit and don't sync anything - if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { + if !e.HasChanged() || len(a.Sync) == 0 { return nil, nil } From fea9ad1872ecba84d9c145ce863fd82d80fd8c2b Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 10:46:32 +0200 Subject: [PATCH 48/61] Add a comment Signed-off-by: David Gageot --- pkg/skaffold/sync/sync.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 589e84c81c9..f94bcb9c0ac 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -54,6 +54,7 @@ func NewItem(a *latest.Artifact, e watch.Events, builds []build.Artifact) (*Item return nil, errors.Wrap(err, "intersecting sync map and deleted files") } + // Something went wrong, don't sync, rebuild. if toCopy == nil || toDelete == nil { return nil, nil } From 7f42c6354461edd7b8fdb5b8efd0241a54e7b597 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 10:48:35 +0200 Subject: [PATCH 49/61] Simplify code Signed-off-by: David Gageot --- pkg/skaffold/runner/runner.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index e9aac715715..8cfe7f71e51 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -259,8 +259,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } if s != nil { changed.AddResync(s) - } - if s == nil { + } else { changed.AddRebuild(a.artifact) } } From d860c54f27b439642a8e57d8d6a5ebc78fa5d120 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 10:49:58 +0200 Subject: [PATCH 50/61] Improve logs Signed-off-by: David Gageot --- pkg/skaffold/kubernetes/sync.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 511f35563b2..b8d587a356d 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -35,12 +35,22 @@ import ( type KubectlSyncer struct{} func (k *KubectlSyncer) Sync(s *sync.Item) error { - if err := perform(s.Image, s.Copy, copyFileFn); err != nil { - return errors.Wrap(err, "copying files") + if len(s.Copy) > 0 { + logrus.Infoln("Copying files:", s.Copy) + + if err := perform(s.Image, s.Copy, copyFileFn); err != nil { + return errors.Wrap(err, "copying files") + } } - if err := perform(s.Image, s.Delete, deleteFileFn); err != nil { - return errors.Wrap(err, "deleting files") + + if len(s.Delete) > 0 { + logrus.Infoln("Deleting files:", s.Delete) + + if err := perform(s.Image, s.Delete, deleteFileFn); err != nil { + return errors.Wrap(err, "deleting files") + } } + return nil } @@ -61,7 +71,6 @@ func labelSelector() string { } func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd) error { - logrus.Info("Syncing files:", files) client, err := Client() if err != nil { return errors.Wrap(err, "getting k8s client") From 97b0ea1dbadc42405adda58e5e0b177d03330861 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 11:16:11 +0200 Subject: [PATCH 51/61] Pass context Signed-off-by: David Gageot --- pkg/skaffold/kubernetes/sync.go | 21 ++++++++++++--------- pkg/skaffold/kubernetes/sync_test.go | 11 +++++++---- pkg/skaffold/runner/runner.go | 7 ++++--- pkg/skaffold/runner/runner_test.go | 2 +- pkg/skaffold/sync/sync.go | 3 ++- pkg/skaffold/sync/sync_test.go | 1 - 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index b8d587a356d..a98f58453a6 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -17,6 +17,7 @@ limitations under the License. package kubernetes import ( + "context" "fmt" "os/exec" "strings" @@ -34,11 +35,11 @@ import ( type KubectlSyncer struct{} -func (k *KubectlSyncer) Sync(s *sync.Item) error { +func (k *KubectlSyncer) Sync(ctx context.Context, s *sync.Item) error { if len(s.Copy) > 0 { logrus.Infoln("Copying files:", s.Copy) - if err := perform(s.Image, s.Copy, copyFileFn); err != nil { + if err := perform(ctx, s.Image, s.Copy, copyFileFn); err != nil { return errors.Wrap(err, "copying files") } } @@ -46,7 +47,7 @@ func (k *KubectlSyncer) Sync(s *sync.Item) error { if len(s.Delete) > 0 { logrus.Infoln("Deleting files:", s.Delete) - if err := perform(s.Image, s.Delete, deleteFileFn); err != nil { + if err := perform(ctx, s.Image, s.Delete, deleteFileFn); err != nil { return errors.Wrap(err, "deleting files") } } @@ -54,12 +55,12 @@ func (k *KubectlSyncer) Sync(s *sync.Item) error { return nil } -func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { - return exec.Command("kubectl", "exec", pod.Name, "--namespace", pod.Namespace, "-c", container.Name, "--", "rm", "-rf", dst) +func deleteFileFn(ctx context.Context, pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { + return exec.CommandContext(ctx, "kubectl", "exec", pod.Name, "--namespace", pod.Namespace, "-c", container.Name, "--", "rm", "-rf", dst) } -func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { - return exec.Command("kubectl", "cp", src, fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, dst), "-c", container.Name) +func copyFileFn(ctx context.Context, pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { + return exec.CommandContext(ctx, "kubectl", "cp", src, fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, dst), "-c", container.Name) } func labelSelector() string { @@ -70,17 +71,19 @@ func labelSelector() string { return strings.Join(reqs, ",") } -func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd) error { +func perform(ctx context.Context, image string, files map[string]string, cmdFn func(context.Context, v1.Pod, v1.Container, string, string) *exec.Cmd) error { client, err := Client() if err != nil { return errors.Wrap(err, "getting k8s client") } + pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{ LabelSelector: labelSelector(), }) if err != nil { return errors.Wrap(err, "getting pods") } + for _, p := range pods.Items { for _, c := range p.Spec.Containers { if c.Image == image { @@ -88,7 +91,7 @@ func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Contai for src, dst := range files { src, dst := src, dst e.Go(func() error { - cmd := cmdFn(p, c, src, dst) + cmd := cmdFn(ctx, p, c, src, dst) return util.RunCmd(cmd) }) } diff --git a/pkg/skaffold/kubernetes/sync_test.go b/pkg/skaffold/kubernetes/sync_test.go index 2cc70021dc0..84bcd8e0436 100644 --- a/pkg/skaffold/kubernetes/sync_test.go +++ b/pkg/skaffold/kubernetes/sync_test.go @@ -17,6 +17,7 @@ limitations under the License. package kubernetes import ( + "context" "fmt" "os/exec" "strings" @@ -49,8 +50,8 @@ func (t *TestCmdRecorder) RunCmdOut(cmd *exec.Cmd) ([]byte, error) { return nil, t.RunCmd(cmd) } -func fakeCmd(p v1.Pod, c v1.Container, src, dst string) *exec.Cmd { - return exec.Command("copy", src, dst) +func fakeCmd(ctx context.Context, p v1.Pod, c v1.Container, src, dst string) *exec.Cmd { + return exec.CommandContext(ctx, "copy", src, dst) } var pod = &v1.Pod{ @@ -76,7 +77,7 @@ func TestPerform(t *testing.T) { description string image string files map[string]string - cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd + cmdFn func(context.Context, v1.Pod, v1.Container, string, string) *exec.Cmd cmdErr error clientErr error expected []string @@ -125,7 +126,9 @@ func TestPerform(t *testing.T) { } util.DefaultExecCommand = cmdRecord - err := perform(test.image, test.files, test.cmdFn) + + err := perform(context.Background(), test.image, test.files, test.cmdFn) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, cmdRecord.cmds) }) } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 8cfe7f71e51..e313d6fc45f 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,6 +30,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" @@ -270,12 +271,12 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return ErrorConfigurationChanged case len(changed.needsResync) > 0: for _, s := range changed.needsResync { - if err := r.Syncer.Sync(s); err != nil { + color.Default.Fprintf(out, "Syncing %d files for %s\n", len(s.Copy)+len(s.Delete), s.Image) + + if err := r.Syncer.Sync(ctx, s); err != nil { logrus.Warnln("Skipping build and deploy due to sync error:", err) return nil } - logrus.Infof("Synced %d files for %s", len(s.Copy)+len(s.Delete), s.Image) - logrus.Debugf("Synced files for %s...\nCopied: %s\nDeleted: %s\n", s.Image, s.Copy, s.Delete) } case len(changed.needsRebuild) > 0: bRes, err := r.Build(ctx, out, r.Tagger, changed.needsRebuild) diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 8ab01d131a1..467ce9f5871 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -123,7 +123,7 @@ func NewTestSyncer() *TestSyncer { } } -func (t *TestSyncer) Sync(s *sync.Item) error { +func (t *TestSyncer) Sync(ctx context.Context, s *sync.Item) error { if t.err != nil { return t.err } diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index f94bcb9c0ac..d5849480a79 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -17,6 +17,7 @@ limitations under the License. package sync import ( + "context" "fmt" "path" "path/filepath" @@ -29,7 +30,7 @@ import ( ) type Syncer interface { - Sync(s *Item) error + Sync(context.Context, *Item) error } type Item struct { diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index ea310375177..175c36a5a02 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -21,7 +21,6 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" ) From 34ddc9669f9e2e0c6d70f6e58d86543b1ea0297b Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 11:40:33 +0200 Subject: [PATCH 52/61] Add an error if not all the files were synced Signed-off-by: David Gageot --- pkg/skaffold/kubernetes/sync.go | 41 ++++++++++++++++++---------- pkg/skaffold/kubernetes/sync_test.go | 1 + 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index a98f58453a6..6e7e92737f0 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -37,7 +37,7 @@ type KubectlSyncer struct{} func (k *KubectlSyncer) Sync(ctx context.Context, s *sync.Item) error { if len(s.Copy) > 0 { - logrus.Infoln("Copying files:", s.Copy) + logrus.Infoln("Copying files:", s.Copy, "to", s.Image) if err := perform(ctx, s.Image, s.Copy, copyFileFn); err != nil { return errors.Wrap(err, "copying files") @@ -45,7 +45,7 @@ func (k *KubectlSyncer) Sync(ctx context.Context, s *sync.Item) error { } if len(s.Delete) > 0 { - logrus.Infoln("Deleting files:", s.Delete) + logrus.Infoln("Deleting files:", s.Delete, "from", s.Image) if err := perform(ctx, s.Image, s.Delete, deleteFileFn); err != nil { return errors.Wrap(err, "deleting files") @@ -84,22 +84,35 @@ func perform(ctx context.Context, image string, files map[string]string, cmdFn f return errors.Wrap(err, "getting pods") } + performed := 0 for _, p := range pods.Items { for _, c := range p.Spec.Containers { - if c.Image == image { - var e errgroup.Group - for src, dst := range files { - src, dst := src, dst - e.Go(func() error { - cmd := cmdFn(ctx, p, c, src, dst) - return util.RunCmd(cmd) - }) - } - if err := e.Wait(); err != nil { - return errors.Wrap(err, "syncing files") - } + if c.Image != image { + continue + } + + var e errgroup.Group + for src, dst := range files { + src, dst := src, dst + e.Go(func() error { + cmd := cmdFn(ctx, p, c, src, dst) + if err := util.RunCmd(cmd); err != nil { + return err + } + + performed++ + return nil + }) + } + if err := e.Wait(); err != nil { + return errors.Wrap(err, "syncing files") } } } + + if performed != len(files) { + return errors.New("couldn't sync all the files") + } + return nil } diff --git a/pkg/skaffold/kubernetes/sync_test.go b/pkg/skaffold/kubernetes/sync_test.go index 84bcd8e0436..36910ee3755 100644 --- a/pkg/skaffold/kubernetes/sync_test.go +++ b/pkg/skaffold/kubernetes/sync_test.go @@ -111,6 +111,7 @@ func TestPerform(t *testing.T) { image: "gcr.io/different-pod:123", files: map[string]string{"test.go": "/test.go"}, cmdFn: fakeCmd, + shouldErr: true, }, } From 1017663b3c8567d92fa731b130c8fbdf0ba5bc6f Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 11:53:59 +0200 Subject: [PATCH 53/61] Pods are not labeled by Skaffold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can’t use skaffold labels to narrow down the list of pods. Signed-off-by: David Gageot --- pkg/skaffold/kubernetes/sync.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 6e7e92737f0..34934291e93 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -20,9 +20,7 @@ import ( "context" "fmt" "os/exec" - "strings" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/sirupsen/logrus" @@ -63,23 +61,13 @@ func copyFileFn(ctx context.Context, pod v1.Pod, container v1.Container, src, ds return exec.CommandContext(ctx, "kubectl", "cp", src, fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, dst), "-c", container.Name) } -func labelSelector() string { - var reqs []string - for k, v := range constants.Labels.DefaultLabels { - reqs = append(reqs, fmt.Sprintf("%s=%s", k, v)) - } - return strings.Join(reqs, ",") -} - func perform(ctx context.Context, image string, files map[string]string, cmdFn func(context.Context, v1.Pod, v1.Container, string, string) *exec.Cmd) error { client, err := Client() if err != nil { return errors.Wrap(err, "getting k8s client") } - pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{ - LabelSelector: labelSelector(), - }) + pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{}) if err != nil { return errors.Wrap(err, "getting pods") } From 81495237eb61397cb038c7c3c22629c995e60ea7 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 11:59:26 +0200 Subject: [PATCH 54/61] Make the code go-routine safe Signed-off-by: David Gageot --- pkg/skaffold/kubernetes/sync.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 34934291e93..3693653eef5 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "os/exec" + "sync/atomic" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" @@ -72,33 +73,35 @@ func perform(ctx context.Context, image string, files map[string]string, cmdFn f return errors.Wrap(err, "getting pods") } - performed := 0 + var performed int32 + var e errgroup.Group + for _, p := range pods.Items { for _, c := range p.Spec.Containers { if c.Image != image { continue } - var e errgroup.Group for src, dst := range files { - src, dst := src, dst + cmd := cmdFn(ctx, p, c, src, dst) + e.Go(func() error { - cmd := cmdFn(ctx, p, c, src, dst) if err := util.RunCmd(cmd); err != nil { return err } - performed++ + atomic.AddInt32(&performed, 1) return nil }) } - if err := e.Wait(); err != nil { - return errors.Wrap(err, "syncing files") - } } } - if performed != len(files) { + if err := e.Wait(); err != nil { + return errors.Wrap(err, "syncing files") + } + + if int(performed) != len(files) { return errors.New("couldn't sync all the files") } From c53bb8643ad048332a9a73e2b76c014948e4e02f Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 16:44:00 +0200 Subject: [PATCH 55/61] Remove duplication --- pkg/skaffold/kubernetes/sync.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 3693653eef5..24a1046419d 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -35,20 +35,16 @@ import ( type KubectlSyncer struct{} func (k *KubectlSyncer) Sync(ctx context.Context, s *sync.Item) error { - if len(s.Copy) > 0 { - logrus.Infoln("Copying files:", s.Copy, "to", s.Image) + logrus.Infoln("Copying files:", s.Copy, "to", s.Image) - if err := perform(ctx, s.Image, s.Copy, copyFileFn); err != nil { - return errors.Wrap(err, "copying files") - } + if err := perform(ctx, s.Image, s.Copy, copyFileFn); err != nil { + return errors.Wrap(err, "copying files") } - if len(s.Delete) > 0 { - logrus.Infoln("Deleting files:", s.Delete, "from", s.Image) + logrus.Infoln("Deleting files:", s.Delete, "from", s.Image) - if err := perform(ctx, s.Image, s.Delete, deleteFileFn); err != nil { - return errors.Wrap(err, "deleting files") - } + if err := perform(ctx, s.Image, s.Delete, deleteFileFn); err != nil { + return errors.Wrap(err, "deleting files") } return nil @@ -63,6 +59,10 @@ func copyFileFn(ctx context.Context, pod v1.Pod, container v1.Container, src, ds } func perform(ctx context.Context, image string, files map[string]string, cmdFn func(context.Context, v1.Pod, v1.Container, string, string) *exec.Cmd) error { + if len(files) == 0 { + return nil + } + client, err := Client() if err != nil { return errors.Wrap(err, "getting k8s client") From 3e8f198c69914461c8db6aada55b8036eb9cc2f4 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 5 Oct 2018 17:03:03 +0200 Subject: [PATCH 56/61] Deal with files synced to multiple pods. Sync files in a sequential manner, it makes the code simpler and anyway, the typical use case will be to sync one file with one pod. Signed-off-by: David Gageot --- pkg/skaffold/kubernetes/sync.go | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 24a1046419d..4e442c62921 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -20,14 +20,12 @@ import ( "context" "fmt" "os/exec" - "sync/atomic" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" - "github.com/sirupsen/logrus" - "golang.org/x/sync/errgroup" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -73,8 +71,7 @@ func perform(ctx context.Context, image string, files map[string]string, cmdFn f return errors.Wrap(err, "getting pods") } - var performed int32 - var e errgroup.Group + synced := map[string]bool{} for _, p := range pods.Items { for _, c := range p.Spec.Containers { @@ -84,24 +81,16 @@ func perform(ctx context.Context, image string, files map[string]string, cmdFn f for src, dst := range files { cmd := cmdFn(ctx, p, c, src, dst) + if err := util.RunCmd(cmd); err != nil { + return err + } - e.Go(func() error { - if err := util.RunCmd(cmd); err != nil { - return err - } - - atomic.AddInt32(&performed, 1) - return nil - }) + synced[src] = true } } } - if err := e.Wait(); err != nil { - return errors.Wrap(err, "syncing files") - } - - if int(performed) != len(files) { + if len(synced) != len(files) { return errors.New("couldn't sync all the files") } From 2e3df182e97f0863082db3394136a271ab166ff1 Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Wed, 3 Oct 2018 13:57:25 -0700 Subject: [PATCH 57/61] Rename SkaffoldConfig to SkaffoldPipeline --- cmd/skaffold/app/cmd/init.go | 7 ++-- cmd/skaffold/app/cmd/runner.go | 6 ++-- integration/examples/annotated-skaffold.yaml | 1 - integration/examples/bazel/skaffold.yaml | 1 - .../examples/getting-started/skaffold.yaml | 1 - .../examples/helm-deployment/skaffold.yaml | 1 - integration/examples/kaniko/skaffold.yaml | 1 - integration/examples/kustomize/skaffold.yaml | 1 - .../examples/microservices/skaffold.yaml | 1 - .../skaffold.yaml | 1 - pkg/skaffold/runner/runner.go | 4 +-- pkg/skaffold/runner/runner_test.go | 22 ++++++------- pkg/skaffold/schema/latest/config.go | 15 ++++----- pkg/skaffold/schema/latest/defaults.go | 22 ++++++------- pkg/skaffold/schema/latest/upgrade.go | 2 +- pkg/skaffold/schema/profile_test.go | 4 +-- pkg/skaffold/schema/profiles.go | 7 ++-- pkg/skaffold/schema/v1alpha1/config.go | 32 +++++++++---------- pkg/skaffold/schema/v1alpha1/upgrade.go | 4 +-- pkg/skaffold/schema/v1alpha2/config.go | 14 ++++---- pkg/skaffold/schema/v1alpha2/defaults.go | 26 +++++++-------- pkg/skaffold/schema/v1alpha2/upgrade.go | 4 +-- pkg/skaffold/schema/v1alpha3/config.go | 14 ++++---- pkg/skaffold/schema/v1alpha3/defaults.go | 26 +++++++-------- pkg/skaffold/schema/v1alpha3/upgrade.go | 5 ++- pkg/skaffold/schema/versions.go | 8 ++--- pkg/skaffold/schema/versions_test.go | 30 ++++++++--------- 27 files changed, 124 insertions(+), 136 deletions(-) diff --git a/cmd/skaffold/app/cmd/init.go b/cmd/skaffold/app/cmd/init.go index bf5e7d048a0..e78d5fcaa0e 100644 --- a/cmd/skaffold/app/cmd/init.go +++ b/cmd/skaffold/app/cmd/init.go @@ -132,7 +132,7 @@ func doInit(out io.Writer) error { } } - cfg, err := generateSkaffoldConfig(k8sConfigs, pairs) + cfg, err := generateSkaffoldPipeline(k8sConfigs, pairs) if err != nil { return err } @@ -261,14 +261,13 @@ func processBuildArtifacts(pairs []dockerfilePair) latest.BuildConfig { return config } -func generateSkaffoldConfig(k8sConfigs []string, dockerfilePairs []dockerfilePair) ([]byte, error) { +func generateSkaffoldPipeline(k8sConfigs []string, dockerfilePairs []dockerfilePair) ([]byte, error) { // if we're here, the user has no skaffold yaml so we need to generate one // if the user doesn't have any k8s yamls, generate one for each dockerfile logrus.Info("generating skaffold config") - config := &latest.SkaffoldConfig{ + config := &latest.SkaffoldPipeline{ APIVersion: latest.Version, - Kind: "Config", } if err := config.SetDefaultValues(); err != nil { return nil, errors.Wrap(err, "generating default config") diff --git a/cmd/skaffold/app/cmd/runner.go b/cmd/skaffold/app/cmd/runner.go index ac3e248e892..9da35519617 100644 --- a/cmd/skaffold/app/cmd/runner.go +++ b/cmd/skaffold/app/cmd/runner.go @@ -24,8 +24,8 @@ import ( "github.com/pkg/errors" ) -// newRunner creates a SkaffoldRunner and returns the SkaffoldConfig associated with it. -func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *latest.SkaffoldConfig, error) { +// newRunner creates a SkaffoldRunner and returns the SkaffoldPipeline associated with it. +func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *latest.SkaffoldPipeline, error) { parsed, err := schema.ParseConfig(opts.ConfigurationFile, true) if err != nil { return nil, nil, errors.Wrap(err, "parsing skaffold config") @@ -35,7 +35,7 @@ func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *latest.Sk return nil, nil, errors.Wrap(err, "invalid config") } - config := parsed.(*latest.SkaffoldConfig) + config := parsed.(*latest.SkaffoldPipeline) err = schema.ApplyProfiles(config, opts.Profiles) if err != nil { return nil, nil, errors.Wrap(err, "applying profiles") diff --git a/integration/examples/annotated-skaffold.yaml b/integration/examples/annotated-skaffold.yaml index f7c3e550271..d8cb9454452 100644 --- a/integration/examples/annotated-skaffold.yaml +++ b/integration/examples/annotated-skaffold.yaml @@ -1,5 +1,4 @@ apiVersion: skaffold/v1alpha4 -kind: Config build: # tagPolicy determines how skaffold is going to tag your images. # We provide a few strategies here, although you most likely won't need to care! diff --git a/integration/examples/bazel/skaffold.yaml b/integration/examples/bazel/skaffold.yaml index b85d2066fb5..a75dc19a9ab 100644 --- a/integration/examples/bazel/skaffold.yaml +++ b/integration/examples/bazel/skaffold.yaml @@ -1,5 +1,4 @@ apiVersion: skaffold/v1alpha4 -kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-bazel diff --git a/integration/examples/getting-started/skaffold.yaml b/integration/examples/getting-started/skaffold.yaml index 7ace8c5b6f8..cbf8b060805 100644 --- a/integration/examples/getting-started/skaffold.yaml +++ b/integration/examples/getting-started/skaffold.yaml @@ -1,5 +1,4 @@ apiVersion: skaffold/v1alpha4 -kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-example diff --git a/integration/examples/helm-deployment/skaffold.yaml b/integration/examples/helm-deployment/skaffold.yaml index 9e21a0d59da..2b2c5117b7e 100644 --- a/integration/examples/helm-deployment/skaffold.yaml +++ b/integration/examples/helm-deployment/skaffold.yaml @@ -1,5 +1,4 @@ apiVersion: skaffold/v1alpha4 -kind: Config build: tagPolicy: sha256: {} diff --git a/integration/examples/kaniko/skaffold.yaml b/integration/examples/kaniko/skaffold.yaml index 4bf0641b32f..2facbaa93bf 100644 --- a/integration/examples/kaniko/skaffold.yaml +++ b/integration/examples/kaniko/skaffold.yaml @@ -1,5 +1,4 @@ apiVersion: skaffold/v1alpha4 -kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-example diff --git a/integration/examples/kustomize/skaffold.yaml b/integration/examples/kustomize/skaffold.yaml index 95aa61e789c..6e8426f5945 100644 --- a/integration/examples/kustomize/skaffold.yaml +++ b/integration/examples/kustomize/skaffold.yaml @@ -1,4 +1,3 @@ apiVersion: skaffold/v1alpha4 -kind: Config deploy: kustomize: {} diff --git a/integration/examples/microservices/skaffold.yaml b/integration/examples/microservices/skaffold.yaml index e1fa83ab55a..a2a9ac884e8 100644 --- a/integration/examples/microservices/skaffold.yaml +++ b/integration/examples/microservices/skaffold.yaml @@ -1,5 +1,4 @@ apiVersion: skaffold/v1alpha4 -kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/leeroy-web diff --git a/integration/examples/tagging-with-environment-variables/skaffold.yaml b/integration/examples/tagging-with-environment-variables/skaffold.yaml index f4e2a3769f0..ed99cf9efe1 100644 --- a/integration/examples/tagging-with-environment-variables/skaffold.yaml +++ b/integration/examples/tagging-with-environment-variables/skaffold.yaml @@ -1,5 +1,4 @@ apiVersion: skaffold/v1alpha4 -kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-example diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index e9aac715715..5a41ef1ac01 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -61,8 +61,8 @@ type SkaffoldRunner struct { builds []build.Artifact } -// NewForConfig returns a new SkaffoldRunner for a SkaffoldConfig -func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldConfig) (*SkaffoldRunner, error) { +// NewForConfig returns a new SkaffoldRunner for a SkaffoldPipeline +func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldPipeline) (*SkaffoldRunner, error) { kubeContext, err := kubectx.CurrentContext() if err != nil { return nil, errors.Wrap(err, "getting current cluster context") diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 8ab01d131a1..334b546b116 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -180,7 +180,7 @@ func (t *TestWatcher) Run(ctx context.Context, trigger watch.Trigger, onChange f func TestNewForConfig(t *testing.T) { var tests = []struct { description string - config *latest.SkaffoldConfig + config *latest.SkaffoldPipeline shouldErr bool expectedBuilder build.Builder expectedTester test.Tester @@ -188,7 +188,7 @@ func TestNewForConfig(t *testing.T) { }{ { description: "local builder config", - config: &latest.SkaffoldConfig{ + config: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ TagPolicy: latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}, BuildType: latest.BuildType{ @@ -207,7 +207,7 @@ func TestNewForConfig(t *testing.T) { }, { description: "bad tagger config", - config: &latest.SkaffoldConfig{ + config: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ TagPolicy: latest.TagPolicy{}, BuildType: latest.BuildType{ @@ -224,7 +224,7 @@ func TestNewForConfig(t *testing.T) { }, { description: "unknown builder", - config: &latest.SkaffoldConfig{ + config: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{}, }, shouldErr: true, @@ -234,7 +234,7 @@ func TestNewForConfig(t *testing.T) { }, { description: "unknown tagger", - config: &latest.SkaffoldConfig{ + config: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ TagPolicy: latest.TagPolicy{}, BuildType: latest.BuildType{ @@ -248,7 +248,7 @@ func TestNewForConfig(t *testing.T) { }, { description: "unknown deployer", - config: &latest.SkaffoldConfig{ + config: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ TagPolicy: latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}, BuildType: latest.BuildType{ @@ -280,7 +280,7 @@ func TestNewForConfig(t *testing.T) { func TestRun(t *testing.T) { var tests = []struct { description string - config *latest.SkaffoldConfig + config *latest.SkaffoldPipeline builder build.Builder tester test.Tester deployer deploy.Deployer @@ -288,14 +288,14 @@ func TestRun(t *testing.T) { }{ { description: "run no error", - config: &latest.SkaffoldConfig{}, + config: &latest.SkaffoldPipeline{}, builder: &TestBuilder{}, tester: &TestTester{}, deployer: &TestDeployer{}, }, { description: "run build error", - config: &latest.SkaffoldConfig{}, + config: &latest.SkaffoldPipeline{}, builder: &TestBuilder{ errors: []error{fmt.Errorf("")}, }, @@ -304,7 +304,7 @@ func TestRun(t *testing.T) { }, { description: "run deploy error", - config: &latest.SkaffoldConfig{ + config: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ Artifacts: []*latest.Artifact{ { @@ -322,7 +322,7 @@ func TestRun(t *testing.T) { }, { description: "run test error", - config: &latest.SkaffoldConfig{ + config: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ Artifacts: []*latest.Artifact{ { diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 6bc2d77a9e9..fa5686e6960 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -24,14 +24,13 @@ import ( const Version string = "skaffold/v1alpha4" -// NewSkaffoldConfig creates a SkaffoldConfig -func NewSkaffoldConfig() util.VersionedConfig { - return new(SkaffoldConfig) +// NewSkaffoldPipeline creates a SkaffoldPipeline +func NewSkaffoldPipeline() util.VersionedConfig { + return new(SkaffoldPipeline) } -type SkaffoldConfig struct { +type SkaffoldPipeline struct { APIVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` Build BuildConfig `yaml:"build,omitempty"` Test []TestCase `yaml:"test,omitempty"` @@ -39,7 +38,7 @@ type SkaffoldConfig struct { Profiles []Profile `yaml:"profiles,omitempty"` } -func (c *SkaffoldConfig) GetVersion() string { +func (c *SkaffoldPipeline) GetVersion() string { return c.APIVersion } @@ -245,8 +244,8 @@ type BazelArtifact struct { BuildTarget string `yaml:"target"` } -// Parse reads a SkaffoldConfig from yaml. -func (c *SkaffoldConfig) Parse(contents []byte, useDefaults bool) error { +// Parse reads a SkaffoldPipeline from yaml. +func (c *SkaffoldPipeline) Parse(contents []byte, useDefaults bool) error { if err := yaml.UnmarshalStrict(contents, c); err != nil { return err } diff --git a/pkg/skaffold/schema/latest/defaults.go b/pkg/skaffold/schema/latest/defaults.go index 8850720d035..262a9c50f64 100644 --- a/pkg/skaffold/schema/latest/defaults.go +++ b/pkg/skaffold/schema/latest/defaults.go @@ -28,7 +28,7 @@ import ( ) // SetDefaultValues makes sure default values are set. -func (c *SkaffoldConfig) SetDefaultValues() error { +func (c *SkaffoldPipeline) SetDefaultValues() error { c.defaultToLocalBuild() c.defaultToKubectlDeploy() c.setDefaultCloudBuildDockerImage() @@ -54,7 +54,7 @@ func (c *SkaffoldConfig) SetDefaultValues() error { return nil } -func (c *SkaffoldConfig) defaultToLocalBuild() { +func (c *SkaffoldPipeline) defaultToLocalBuild() { if c.Build.BuildType != (BuildType{}) { return } @@ -63,7 +63,7 @@ func (c *SkaffoldConfig) defaultToLocalBuild() { c.Build.BuildType.LocalBuild = &LocalBuild{} } -func (c *SkaffoldConfig) defaultToKubectlDeploy() { +func (c *SkaffoldPipeline) defaultToKubectlDeploy() { if c.Deploy.DeployType != (DeployType{}) { return } @@ -72,7 +72,7 @@ func (c *SkaffoldConfig) defaultToKubectlDeploy() { c.Deploy.DeployType.KubectlDeploy = &KubectlDeploy{} } -func (c *SkaffoldConfig) setDefaultCloudBuildDockerImage() { +func (c *SkaffoldPipeline) setDefaultCloudBuildDockerImage() { cloudBuild := c.Build.BuildType.GoogleCloudBuild if cloudBuild == nil { return @@ -81,7 +81,7 @@ func (c *SkaffoldConfig) setDefaultCloudBuildDockerImage() { cloudBuild.DockerImage = valueOrDefault(cloudBuild.DockerImage, constants.DefaultCloudBuildDockerImage) } -func (c *SkaffoldConfig) setDefaultTagger() { +func (c *SkaffoldPipeline) setDefaultTagger() { if c.Build.TagPolicy != (TagPolicy{}) { return } @@ -89,7 +89,7 @@ func (c *SkaffoldConfig) setDefaultTagger() { c.Build.TagPolicy = TagPolicy{GitTagger: &GitTagger{}} } -func (c *SkaffoldConfig) setDefaultKustomizePath() { +func (c *SkaffoldPipeline) setDefaultKustomizePath() { kustomize := c.Deploy.KustomizeDeploy if kustomize == nil { return @@ -98,13 +98,13 @@ func (c *SkaffoldConfig) setDefaultKustomizePath() { kustomize.KustomizePath = valueOrDefault(kustomize.KustomizePath, constants.DefaultKustomizationPath) } -func (c *SkaffoldConfig) setDefaultKubectlManifests() { +func (c *SkaffoldPipeline) setDefaultKubectlManifests() { if c.Deploy.KubectlDeploy != nil && len(c.Deploy.KubectlDeploy.Manifests) == 0 { c.Deploy.KubectlDeploy.Manifests = constants.DefaultKubectlManifests } } -func (c *SkaffoldConfig) defaultToDockerArtifact(a *Artifact) { +func (c *SkaffoldPipeline) defaultToDockerArtifact(a *Artifact) { if a.ArtifactType == (ArtifactType{}) { a.ArtifactType = ArtifactType{ DockerArtifact: &DockerArtifact{}, @@ -112,17 +112,17 @@ func (c *SkaffoldConfig) defaultToDockerArtifact(a *Artifact) { } } -func (c *SkaffoldConfig) setDefaultDockerfile(a *Artifact) { +func (c *SkaffoldPipeline) setDefaultDockerfile(a *Artifact) { if a.DockerArtifact != nil { a.DockerArtifact.DockerfilePath = valueOrDefault(a.DockerArtifact.DockerfilePath, constants.DefaultDockerfilePath) } } -func (c *SkaffoldConfig) setDefaultWorkspace(a *Artifact) { +func (c *SkaffoldPipeline) setDefaultWorkspace(a *Artifact) { a.Workspace = valueOrDefault(a.Workspace, ".") } -func (c *SkaffoldConfig) withKanikoConfig(operations ...func(kaniko *KanikoBuild) error) error { +func (c *SkaffoldPipeline) withKanikoConfig(operations ...func(kaniko *KanikoBuild) error) error { if kaniko := c.Build.KanikoBuild; kaniko != nil { for _, operation := range operations { if err := operation(kaniko); err != nil { diff --git a/pkg/skaffold/schema/latest/upgrade.go b/pkg/skaffold/schema/latest/upgrade.go index 7c4459819d8..e6ee9481f5f 100644 --- a/pkg/skaffold/schema/latest/upgrade.go +++ b/pkg/skaffold/schema/latest/upgrade.go @@ -23,6 +23,6 @@ import ( ) // Upgrade upgrades a configuration to the next version. -func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { +func (config *SkaffoldPipeline) Upgrade() (util.VersionedConfig, error) { return nil, errors.New("not implemented yet") } diff --git a/pkg/skaffold/schema/profile_test.go b/pkg/skaffold/schema/profile_test.go index 8690e50ecfc..2634d5a89da 100644 --- a/pkg/skaffold/schema/profile_test.go +++ b/pkg/skaffold/schema/profile_test.go @@ -26,9 +26,9 @@ import ( func TestApplyProfiles(t *testing.T) { tests := []struct { description string - config *latest.SkaffoldConfig + config *latest.SkaffoldPipeline profile string - expected *latest.SkaffoldConfig + expected *latest.SkaffoldPipeline shouldErr bool }{ { diff --git a/pkg/skaffold/schema/profiles.go b/pkg/skaffold/schema/profiles.go index fd191393151..7c428c4f777 100644 --- a/pkg/skaffold/schema/profiles.go +++ b/pkg/skaffold/schema/profiles.go @@ -29,7 +29,7 @@ import ( // ApplyProfiles returns configuration modified by the application // of a list of profiles. -func ApplyProfiles(c *latest.SkaffoldConfig, profiles []string) error { +func ApplyProfiles(c *latest.SkaffoldPipeline, profiles []string) error { byName := profilesByName(c.Profiles) for _, name := range profiles { profile, present := byName[name] @@ -46,13 +46,12 @@ func ApplyProfiles(c *latest.SkaffoldConfig, profiles []string) error { return nil } -func applyProfile(config *latest.SkaffoldConfig, profile latest.Profile) { +func applyProfile(config *latest.SkaffoldPipeline, profile latest.Profile) { logrus.Infof("applying profile: %s", profile.Name) // this intentionally removes the Profiles field from the returned config - *config = latest.SkaffoldConfig{ + *config = latest.SkaffoldPipeline{ APIVersion: config.APIVersion, - 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/v1alpha1/config.go b/pkg/skaffold/schema/v1alpha1/config.go index c60c87afd78..50014853ec1 100644 --- a/pkg/skaffold/schema/v1alpha1/config.go +++ b/pkg/skaffold/schema/v1alpha1/config.go @@ -25,14 +25,14 @@ import ( const Version string = "skaffold/v1alpha1" -// NewSkaffoldConfig creates a SkaffoldConfig -func NewSkaffoldConfig() util.VersionedConfig { - return new(SkaffoldConfig) +// NewSkaffoldPipeline creates a SkaffoldPipeline +func NewSkaffoldPipeline() util.VersionedConfig { + return new(SkaffoldPipeline) } -// SkaffoldConfig is the top level config object +// SkaffoldPipeline is the top level config object // that is parsed from a skaffold.yaml -type SkaffoldConfig struct { +type SkaffoldPipeline struct { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` @@ -40,7 +40,7 @@ type SkaffoldConfig struct { Deploy DeployConfig `yaml:"deploy"` } -func (config *SkaffoldConfig) GetVersion() string { +func (config *SkaffoldPipeline) GetVersion() string { return config.APIVersion } @@ -113,40 +113,40 @@ type Artifact struct { BuildArgs map[string]*string `yaml:"buildArgs,omitempty"` } -// DefaultDevSkaffoldConfig is a partial set of defaults for the SkaffoldConfig +// DefaultDevSkaffoldPipeline is a partial set of defaults for the SkaffoldPipeline // when dev mode is specified. // Each API is responsible for setting its own defaults that are not top level. -var defaultDevSkaffoldConfig = &SkaffoldConfig{ +var defaultDevSkaffoldPipeline = &SkaffoldPipeline{ Build: BuildConfig{ TagPolicy: constants.DefaultDevTagStrategy, }, } -// DefaultRunSkaffoldConfig is a partial set of defaults for the SkaffoldConfig +// DefaultRunSkaffoldPipeline is a partial set of defaults for the SkaffoldPipeline // when run mode is specified. // Each API is responsible for setting its own defaults that are not top level. -var defaultRunSkaffoldConfig = &SkaffoldConfig{ +var defaultRunSkaffoldPipeline = &SkaffoldPipeline{ Build: BuildConfig{ TagPolicy: constants.DefaultRunTagStrategy, }, } -// Parse reads from an io.Reader and unmarshals the result into a SkaffoldConfig. +// Parse reads from an io.Reader and unmarshals the result into a SkaffoldPipeline. // The default config argument provides default values for the config, // which can be overridden if present in the config file. -func (config *SkaffoldConfig) Parse(contents []byte, useDefault bool) error { +func (config *SkaffoldPipeline) Parse(contents []byte, useDefault bool) error { if useDefault { *config = *config.getDefaultForMode(false) } else { - *config = SkaffoldConfig{} + *config = SkaffoldPipeline{} } return yaml.UnmarshalStrict(contents, config) } -func (config *SkaffoldConfig) getDefaultForMode(dev bool) *SkaffoldConfig { +func (config *SkaffoldPipeline) getDefaultForMode(dev bool) *SkaffoldPipeline { if dev { - return defaultDevSkaffoldConfig + return defaultDevSkaffoldPipeline } - return defaultRunSkaffoldConfig + return defaultRunSkaffoldPipeline } diff --git a/pkg/skaffold/schema/v1alpha1/upgrade.go b/pkg/skaffold/schema/v1alpha1/upgrade.go index 314a797585c..4f0edbc7d39 100644 --- a/pkg/skaffold/schema/v1alpha1/upgrade.go +++ b/pkg/skaffold/schema/v1alpha1/upgrade.go @@ -24,7 +24,7 @@ import ( ) // Upgrade upgrades a configuration to the next version. -func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { +func (config *SkaffoldPipeline) Upgrade() (util.VersionedConfig, error) { var tagPolicy next.TagPolicy if config.Build.TagPolicy == constants.TagStrategySha256 { tagPolicy = next.TagPolicy{ @@ -91,7 +91,7 @@ func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { } } - return &next.SkaffoldConfig{ + return &next.SkaffoldPipeline{ APIVersion: next.Version, Kind: config.Kind, Deploy: next.DeployConfig{ diff --git a/pkg/skaffold/schema/v1alpha2/config.go b/pkg/skaffold/schema/v1alpha2/config.go index 2ff49c167f8..b4ad37b5bb2 100644 --- a/pkg/skaffold/schema/v1alpha2/config.go +++ b/pkg/skaffold/schema/v1alpha2/config.go @@ -24,12 +24,12 @@ import ( const Version string = "skaffold/v1alpha2" -// NewSkaffoldConfig creates a SkaffoldConfig -func NewSkaffoldConfig() util.VersionedConfig { - return new(SkaffoldConfig) +// NewSkaffoldPipeline creates a SkaffoldPipeline +func NewSkaffoldPipeline() util.VersionedConfig { + return new(SkaffoldPipeline) } -type SkaffoldConfig struct { +type SkaffoldPipeline struct { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` @@ -38,7 +38,7 @@ type SkaffoldConfig struct { Profiles []Profile `yaml:"profiles,omitempty"` } -func (c *SkaffoldConfig) GetVersion() string { +func (c *SkaffoldPipeline) GetVersion() string { return c.APIVersion } @@ -224,8 +224,8 @@ type BazelArtifact struct { BuildTarget string `yaml:"target"` } -// Parse reads a SkaffoldConfig from yaml. -func (c *SkaffoldConfig) Parse(contents []byte, useDefaults bool) error { +// Parse reads a SkaffoldPipeline from yaml. +func (c *SkaffoldPipeline) Parse(contents []byte, useDefaults bool) error { if err := yaml.UnmarshalStrict(contents, c); err != nil { return err } diff --git a/pkg/skaffold/schema/v1alpha2/defaults.go b/pkg/skaffold/schema/v1alpha2/defaults.go index c5aebd5ad7e..0eb8024e235 100644 --- a/pkg/skaffold/schema/v1alpha2/defaults.go +++ b/pkg/skaffold/schema/v1alpha2/defaults.go @@ -28,7 +28,7 @@ import ( ) // SetDefaultValues makes sure default values are set. -func (c *SkaffoldConfig) SetDefaultValues() error { +func (c *SkaffoldPipeline) SetDefaultValues() error { c.defaultToLocalBuild() c.defaultToKubectlDeploy() c.setDefaultCloudBuildDockerImage() @@ -52,7 +52,7 @@ func (c *SkaffoldConfig) SetDefaultValues() error { return nil } -func (c *SkaffoldConfig) defaultToLocalBuild() { +func (c *SkaffoldPipeline) defaultToLocalBuild() { if c.Build.BuildType != (BuildType{}) { return } @@ -61,7 +61,7 @@ func (c *SkaffoldConfig) defaultToLocalBuild() { c.Build.BuildType.LocalBuild = &LocalBuild{} } -func (c *SkaffoldConfig) defaultToKubectlDeploy() { +func (c *SkaffoldPipeline) defaultToKubectlDeploy() { if c.Deploy.DeployType != (DeployType{}) { return } @@ -70,7 +70,7 @@ func (c *SkaffoldConfig) defaultToKubectlDeploy() { c.Deploy.DeployType.KubectlDeploy = &KubectlDeploy{} } -func (c *SkaffoldConfig) setDefaultCloudBuildDockerImage() { +func (c *SkaffoldPipeline) setDefaultCloudBuildDockerImage() { cloudBuild := c.Build.BuildType.GoogleCloudBuild if cloudBuild == nil { return @@ -81,7 +81,7 @@ func (c *SkaffoldConfig) setDefaultCloudBuildDockerImage() { } } -func (c *SkaffoldConfig) setDefaultTagger() { +func (c *SkaffoldPipeline) setDefaultTagger() { if c.Build.TagPolicy != (TagPolicy{}) { return } @@ -89,19 +89,19 @@ func (c *SkaffoldConfig) setDefaultTagger() { c.Build.TagPolicy = TagPolicy{GitTagger: &GitTagger{}} } -func (c *SkaffoldConfig) setDefaultKustomizePath() { +func (c *SkaffoldPipeline) setDefaultKustomizePath() { if c.Deploy.KustomizeDeploy != nil && c.Deploy.KustomizeDeploy.KustomizePath == "" { c.Deploy.KustomizeDeploy.KustomizePath = constants.DefaultKustomizationPath } } -func (c *SkaffoldConfig) setDefaultKubectlManifests() { +func (c *SkaffoldPipeline) setDefaultKubectlManifests() { if c.Deploy.KubectlDeploy != nil && len(c.Deploy.KubectlDeploy.Manifests) == 0 { c.Deploy.KubectlDeploy.Manifests = constants.DefaultKubectlManifests } } -func (c *SkaffoldConfig) defaultToDockerArtifact(a *Artifact) { +func (c *SkaffoldPipeline) defaultToDockerArtifact(a *Artifact) { if a.ArtifactType == (ArtifactType{}) { a.ArtifactType = ArtifactType{ DockerArtifact: &DockerArtifact{}, @@ -109,19 +109,19 @@ func (c *SkaffoldConfig) defaultToDockerArtifact(a *Artifact) { } } -func (c *SkaffoldConfig) setDefaultDockerfile(a *Artifact) { +func (c *SkaffoldPipeline) setDefaultDockerfile(a *Artifact) { if a.DockerArtifact != nil && a.DockerArtifact.DockerfilePath == "" { a.DockerArtifact.DockerfilePath = constants.DefaultDockerfilePath } } -func (c *SkaffoldConfig) setDefaultWorkspace(a *Artifact) { +func (c *SkaffoldPipeline) setDefaultWorkspace(a *Artifact) { if a.Workspace == "" { a.Workspace = "." } } -func (c *SkaffoldConfig) setDefaultKanikoNamespace() error { +func (c *SkaffoldPipeline) setDefaultKanikoNamespace() error { kaniko := c.Build.KanikoBuild if kaniko == nil { return nil @@ -139,7 +139,7 @@ func (c *SkaffoldConfig) setDefaultKanikoNamespace() error { return nil } -func (c *SkaffoldConfig) setDefaultKanikoTimeout() { +func (c *SkaffoldPipeline) setDefaultKanikoTimeout() { kaniko := c.Build.KanikoBuild if kaniko == nil { return @@ -150,7 +150,7 @@ func (c *SkaffoldConfig) setDefaultKanikoTimeout() { } } -func (c *SkaffoldConfig) setDefaultKanikoSecret() error { +func (c *SkaffoldPipeline) setDefaultKanikoSecret() error { kaniko := c.Build.KanikoBuild if kaniko == nil { return nil diff --git a/pkg/skaffold/schema/v1alpha2/upgrade.go b/pkg/skaffold/schema/v1alpha2/upgrade.go index ef23a6a3f10..8e96899e2cb 100644 --- a/pkg/skaffold/schema/v1alpha2/upgrade.go +++ b/pkg/skaffold/schema/v1alpha2/upgrade.go @@ -25,7 +25,7 @@ import ( ) // Upgrade upgrades a configuration to the next version. -func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { +func (config *SkaffoldPipeline) Upgrade() (util.VersionedConfig, error) { // convert Deploy (should be the same) var newDeploy next.DeployConfig if err := convert(config.Deploy, &newDeploy); err != nil { @@ -81,7 +81,7 @@ func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { } } - return &next.SkaffoldConfig{ + return &next.SkaffoldPipeline{ APIVersion: next.Version, Kind: config.Kind, Deploy: newDeploy, diff --git a/pkg/skaffold/schema/v1alpha3/config.go b/pkg/skaffold/schema/v1alpha3/config.go index cb35735b426..9dcd2d1ad31 100644 --- a/pkg/skaffold/schema/v1alpha3/config.go +++ b/pkg/skaffold/schema/v1alpha3/config.go @@ -24,12 +24,12 @@ import ( const Version string = "skaffold/v1alpha3" -// NewSkaffoldConfig creates a SkaffoldConfig -func NewSkaffoldConfig() util.VersionedConfig { - return new(SkaffoldConfig) +// NewSkaffoldPipeline creates a SkaffoldPipeline +func NewSkaffoldPipeline() util.VersionedConfig { + return new(SkaffoldPipeline) } -type SkaffoldConfig struct { +type SkaffoldPipeline struct { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` @@ -38,7 +38,7 @@ type SkaffoldConfig struct { Profiles []Profile `yaml:"profiles,omitempty"` } -func (c *SkaffoldConfig) GetVersion() string { +func (c *SkaffoldPipeline) GetVersion() string { return c.APIVersion } @@ -230,8 +230,8 @@ type BazelArtifact struct { BuildTarget string `yaml:"target"` } -// Parse reads a SkaffoldConfig from yaml. -func (c *SkaffoldConfig) Parse(contents []byte, useDefaults bool) error { +// Parse reads a SkaffoldPipeline from yaml. +func (c *SkaffoldPipeline) Parse(contents []byte, useDefaults bool) error { if err := yaml.UnmarshalStrict(contents, c); err != nil { return err } diff --git a/pkg/skaffold/schema/v1alpha3/defaults.go b/pkg/skaffold/schema/v1alpha3/defaults.go index 423be3953fc..a304ba81e22 100644 --- a/pkg/skaffold/schema/v1alpha3/defaults.go +++ b/pkg/skaffold/schema/v1alpha3/defaults.go @@ -28,7 +28,7 @@ import ( ) // SetDefaultValues makes sure default values are set. -func (c *SkaffoldConfig) SetDefaultValues() error { +func (c *SkaffoldPipeline) SetDefaultValues() error { c.defaultToLocalBuild() c.defaultToKubectlDeploy() c.setDefaultCloudBuildDockerImage() @@ -52,7 +52,7 @@ func (c *SkaffoldConfig) SetDefaultValues() error { return nil } -func (c *SkaffoldConfig) defaultToLocalBuild() { +func (c *SkaffoldPipeline) defaultToLocalBuild() { if c.Build.BuildType != (BuildType{}) { return } @@ -61,7 +61,7 @@ func (c *SkaffoldConfig) defaultToLocalBuild() { c.Build.BuildType.LocalBuild = &LocalBuild{} } -func (c *SkaffoldConfig) defaultToKubectlDeploy() { +func (c *SkaffoldPipeline) defaultToKubectlDeploy() { if c.Deploy.DeployType != (DeployType{}) { return } @@ -70,7 +70,7 @@ func (c *SkaffoldConfig) defaultToKubectlDeploy() { c.Deploy.DeployType.KubectlDeploy = &KubectlDeploy{} } -func (c *SkaffoldConfig) setDefaultCloudBuildDockerImage() { +func (c *SkaffoldPipeline) setDefaultCloudBuildDockerImage() { cloudBuild := c.Build.BuildType.GoogleCloudBuild if cloudBuild == nil { return @@ -81,7 +81,7 @@ func (c *SkaffoldConfig) setDefaultCloudBuildDockerImage() { } } -func (c *SkaffoldConfig) setDefaultTagger() { +func (c *SkaffoldPipeline) setDefaultTagger() { if c.Build.TagPolicy != (TagPolicy{}) { return } @@ -89,19 +89,19 @@ func (c *SkaffoldConfig) setDefaultTagger() { c.Build.TagPolicy = TagPolicy{GitTagger: &GitTagger{}} } -func (c *SkaffoldConfig) setDefaultKustomizePath() { +func (c *SkaffoldPipeline) setDefaultKustomizePath() { if c.Deploy.KustomizeDeploy != nil && c.Deploy.KustomizeDeploy.KustomizePath == "" { c.Deploy.KustomizeDeploy.KustomizePath = constants.DefaultKustomizationPath } } -func (c *SkaffoldConfig) setDefaultKubectlManifests() { +func (c *SkaffoldPipeline) setDefaultKubectlManifests() { if c.Deploy.KubectlDeploy != nil && len(c.Deploy.KubectlDeploy.Manifests) == 0 { c.Deploy.KubectlDeploy.Manifests = constants.DefaultKubectlManifests } } -func (c *SkaffoldConfig) defaultToDockerArtifact(a *Artifact) { +func (c *SkaffoldPipeline) defaultToDockerArtifact(a *Artifact) { if a.ArtifactType == (ArtifactType{}) { a.ArtifactType = ArtifactType{ DockerArtifact: &DockerArtifact{}, @@ -109,19 +109,19 @@ func (c *SkaffoldConfig) defaultToDockerArtifact(a *Artifact) { } } -func (c *SkaffoldConfig) setDefaultDockerfile(a *Artifact) { +func (c *SkaffoldPipeline) setDefaultDockerfile(a *Artifact) { if a.DockerArtifact != nil && a.DockerArtifact.DockerfilePath == "" { a.DockerArtifact.DockerfilePath = constants.DefaultDockerfilePath } } -func (c *SkaffoldConfig) setDefaultWorkspace(a *Artifact) { +func (c *SkaffoldPipeline) setDefaultWorkspace(a *Artifact) { if a.Workspace == "" { a.Workspace = "." } } -func (c *SkaffoldConfig) setDefaultKanikoNamespace() error { +func (c *SkaffoldPipeline) setDefaultKanikoNamespace() error { kaniko := c.Build.KanikoBuild if kaniko == nil { return nil @@ -139,7 +139,7 @@ func (c *SkaffoldConfig) setDefaultKanikoNamespace() error { return nil } -func (c *SkaffoldConfig) setDefaultKanikoTimeout() { +func (c *SkaffoldPipeline) setDefaultKanikoTimeout() { kaniko := c.Build.KanikoBuild if kaniko == nil { return @@ -150,7 +150,7 @@ func (c *SkaffoldConfig) setDefaultKanikoTimeout() { } } -func (c *SkaffoldConfig) setDefaultKanikoSecret() error { +func (c *SkaffoldPipeline) setDefaultKanikoSecret() error { kaniko := c.Build.KanikoBuild if kaniko == nil { return nil diff --git a/pkg/skaffold/schema/v1alpha3/upgrade.go b/pkg/skaffold/schema/v1alpha3/upgrade.go index eae40b3f347..287f2dcf6af 100644 --- a/pkg/skaffold/schema/v1alpha3/upgrade.go +++ b/pkg/skaffold/schema/v1alpha3/upgrade.go @@ -25,7 +25,7 @@ import ( ) // Upgrade upgrades a configuration to the next version. -func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { +func (config *SkaffoldPipeline) Upgrade() (util.VersionedConfig, error) { // convert Deploy (should be the same) var newDeploy next.DeployConfig if err := convert(config.Deploy, &newDeploy); err != nil { @@ -46,9 +46,8 @@ func (config *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) { return nil, errors.Wrap(err, "converting new build") } - return &next.SkaffoldConfig{ + return &next.SkaffoldPipeline{ APIVersion: next.Version, - Kind: config.Kind, Deploy: newDeploy, Build: newBuild, Profiles: newProfiles, diff --git a/pkg/skaffold/schema/versions.go b/pkg/skaffold/schema/versions.go index 86904a75eac..0a414c49be0 100644 --- a/pkg/skaffold/schema/versions.go +++ b/pkg/skaffold/schema/versions.go @@ -35,10 +35,10 @@ type APIVersion struct { } var schemaVersions = map[string]func() util.VersionedConfig{ - v1alpha1.Version: v1alpha1.NewSkaffoldConfig, - v1alpha2.Version: v1alpha2.NewSkaffoldConfig, - v1alpha3.Version: v1alpha3.NewSkaffoldConfig, - latest.Version: latest.NewSkaffoldConfig, + v1alpha1.Version: v1alpha1.NewSkaffoldPipeline, + v1alpha2.Version: v1alpha2.NewSkaffoldPipeline, + v1alpha3.Version: v1alpha3.NewSkaffoldPipeline, + latest.Version: latest.NewSkaffoldPipeline, } // ParseConfig reads a configuration file. diff --git a/pkg/skaffold/schema/versions_test.go b/pkg/skaffold/schema/versions_test.go index ad3b4a2b9b0..5b10601e046 100644 --- a/pkg/skaffold/schema/versions_test.go +++ b/pkg/skaffold/schema/versions_test.go @@ -173,7 +173,7 @@ func TestParseConfig(t *testing.T) { tmp, cleanup := testutil.NewTempDir(t) defer cleanup() - yaml := fmt.Sprintf("apiVersion: %s\nkind: Config\n%s", latest.Version, test.config) + yaml := fmt.Sprintf("apiVersion: %s\n%s", latest.Version, test.config) tmp.Write("skaffold.yaml", yaml) cfg, err := ParseConfig(tmp.Path("skaffold.yaml"), true) @@ -183,16 +183,16 @@ func TestParseConfig(t *testing.T) { } } -func config(ops ...func(*latest.SkaffoldConfig)) *latest.SkaffoldConfig { - cfg := &latest.SkaffoldConfig{APIVersion: latest.Version, Kind: "Config"} +func config(ops ...func(*latest.SkaffoldPipeline)) *latest.SkaffoldPipeline { + cfg := &latest.SkaffoldPipeline{APIVersion: latest.Version} for _, op := range ops { op(cfg) } return cfg } -func withLocalBuild(ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldConfig) { - return func(cfg *latest.SkaffoldConfig) { +func withLocalBuild(ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldPipeline) { + return func(cfg *latest.SkaffoldPipeline) { b := latest.BuildConfig{BuildType: latest.BuildType{LocalBuild: &latest.LocalBuild{}}} for _, op := range ops { op(&b) @@ -201,8 +201,8 @@ func withLocalBuild(ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldConfi } } -func withGoogleCloudBuild(id string, ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldConfig) { - return func(cfg *latest.SkaffoldConfig) { +func withGoogleCloudBuild(id string, ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldPipeline) { + return func(cfg *latest.SkaffoldPipeline) { b := latest.BuildConfig{BuildType: latest.BuildType{GoogleCloudBuild: &latest.GoogleCloudBuild{ ProjectID: id, DockerImage: "gcr.io/cloud-builders/docker", @@ -214,8 +214,8 @@ func withGoogleCloudBuild(id string, ops ...func(*latest.BuildConfig)) func(*lat } } -func withKanikoBuild(bucket, secretName, namespace, secret string, timeout string, ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldConfig) { - return func(cfg *latest.SkaffoldConfig) { +func withKanikoBuild(bucket, secretName, namespace, secret string, timeout string, ops ...func(*latest.BuildConfig)) func(*latest.SkaffoldPipeline) { + return func(cfg *latest.SkaffoldPipeline) { b := latest.BuildConfig{BuildType: latest.BuildType{KanikoBuild: &latest.KanikoBuild{ BuildContext: latest.KanikoBuildContext{ GCSBucket: bucket, @@ -233,8 +233,8 @@ func withKanikoBuild(bucket, secretName, namespace, secret string, timeout strin } } -func withKubectlDeploy(manifests ...string) func(*latest.SkaffoldConfig) { - return func(cfg *latest.SkaffoldConfig) { +func withKubectlDeploy(manifests ...string) func(*latest.SkaffoldPipeline) { + return func(cfg *latest.SkaffoldPipeline) { cfg.Deploy = latest.DeployConfig{ DeployType: latest.DeployType{ KubectlDeploy: &latest.KubectlDeploy{ @@ -245,8 +245,8 @@ func withKubectlDeploy(manifests ...string) func(*latest.SkaffoldConfig) { } } -func withHelmDeploy() func(*latest.SkaffoldConfig) { - return func(cfg *latest.SkaffoldConfig) { +func withHelmDeploy() func(*latest.SkaffoldPipeline) { + return func(cfg *latest.SkaffoldPipeline) { cfg.Deploy = latest.DeployConfig{ DeployType: latest.DeployType{ HelmDeploy: &latest.HelmDeploy{}, @@ -295,8 +295,8 @@ func withShaTagger() func(*latest.BuildConfig) { return withTagPolicy(latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}) } -func withProfiles(profiles ...latest.Profile) func(*latest.SkaffoldConfig) { - return func(cfg *latest.SkaffoldConfig) { +func withProfiles(profiles ...latest.Profile) func(*latest.SkaffoldPipeline) { + return func(cfg *latest.SkaffoldPipeline) { cfg.Profiles = profiles } } From 016e475951f3effbd60b8b2509049b9417339f18 Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Thu, 4 Oct 2018 14:46:21 -0700 Subject: [PATCH 58/61] add back kind --- integration/examples/annotated-skaffold.yaml | 1 + integration/examples/bazel/skaffold.yaml | 1 + integration/examples/getting-started/skaffold.yaml | 1 + integration/examples/helm-deployment/skaffold.yaml | 1 + integration/examples/kaniko/skaffold.yaml | 1 + integration/examples/kustomize/skaffold.yaml | 1 + integration/examples/microservices/skaffold.yaml | 1 + .../examples/tagging-with-environment-variables/skaffold.yaml | 1 + pkg/skaffold/schema/latest/config.go | 1 + pkg/skaffold/schema/profiles.go | 1 + pkg/skaffold/schema/versions_test.go | 4 ++-- 11 files changed, 12 insertions(+), 2 deletions(-) diff --git a/integration/examples/annotated-skaffold.yaml b/integration/examples/annotated-skaffold.yaml index d8cb9454452..f7c3e550271 100644 --- a/integration/examples/annotated-skaffold.yaml +++ b/integration/examples/annotated-skaffold.yaml @@ -1,4 +1,5 @@ apiVersion: skaffold/v1alpha4 +kind: Config build: # tagPolicy determines how skaffold is going to tag your images. # We provide a few strategies here, although you most likely won't need to care! diff --git a/integration/examples/bazel/skaffold.yaml b/integration/examples/bazel/skaffold.yaml index a75dc19a9ab..b85d2066fb5 100644 --- a/integration/examples/bazel/skaffold.yaml +++ b/integration/examples/bazel/skaffold.yaml @@ -1,4 +1,5 @@ apiVersion: skaffold/v1alpha4 +kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-bazel diff --git a/integration/examples/getting-started/skaffold.yaml b/integration/examples/getting-started/skaffold.yaml index cbf8b060805..7ace8c5b6f8 100644 --- a/integration/examples/getting-started/skaffold.yaml +++ b/integration/examples/getting-started/skaffold.yaml @@ -1,4 +1,5 @@ apiVersion: skaffold/v1alpha4 +kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-example diff --git a/integration/examples/helm-deployment/skaffold.yaml b/integration/examples/helm-deployment/skaffold.yaml index 2b2c5117b7e..9e21a0d59da 100644 --- a/integration/examples/helm-deployment/skaffold.yaml +++ b/integration/examples/helm-deployment/skaffold.yaml @@ -1,4 +1,5 @@ apiVersion: skaffold/v1alpha4 +kind: Config build: tagPolicy: sha256: {} diff --git a/integration/examples/kaniko/skaffold.yaml b/integration/examples/kaniko/skaffold.yaml index 2facbaa93bf..4bf0641b32f 100644 --- a/integration/examples/kaniko/skaffold.yaml +++ b/integration/examples/kaniko/skaffold.yaml @@ -1,4 +1,5 @@ apiVersion: skaffold/v1alpha4 +kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-example diff --git a/integration/examples/kustomize/skaffold.yaml b/integration/examples/kustomize/skaffold.yaml index 6e8426f5945..95aa61e789c 100644 --- a/integration/examples/kustomize/skaffold.yaml +++ b/integration/examples/kustomize/skaffold.yaml @@ -1,3 +1,4 @@ apiVersion: skaffold/v1alpha4 +kind: Config deploy: kustomize: {} diff --git a/integration/examples/microservices/skaffold.yaml b/integration/examples/microservices/skaffold.yaml index a2a9ac884e8..e1fa83ab55a 100644 --- a/integration/examples/microservices/skaffold.yaml +++ b/integration/examples/microservices/skaffold.yaml @@ -1,4 +1,5 @@ apiVersion: skaffold/v1alpha4 +kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/leeroy-web diff --git a/integration/examples/tagging-with-environment-variables/skaffold.yaml b/integration/examples/tagging-with-environment-variables/skaffold.yaml index ed99cf9efe1..f4e2a3769f0 100644 --- a/integration/examples/tagging-with-environment-variables/skaffold.yaml +++ b/integration/examples/tagging-with-environment-variables/skaffold.yaml @@ -1,4 +1,5 @@ apiVersion: skaffold/v1alpha4 +kind: Config build: artifacts: - image: gcr.io/k8s-skaffold/skaffold-example diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index fa5686e6960..5d32e57b180 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -31,6 +31,7 @@ func NewSkaffoldPipeline() util.VersionedConfig { type SkaffoldPipeline struct { APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` Build BuildConfig `yaml:"build,omitempty"` Test []TestCase `yaml:"test,omitempty"` diff --git a/pkg/skaffold/schema/profiles.go b/pkg/skaffold/schema/profiles.go index 7c428c4f777..23f5fd3d76d 100644 --- a/pkg/skaffold/schema/profiles.go +++ b/pkg/skaffold/schema/profiles.go @@ -52,6 +52,7 @@ func applyProfile(config *latest.SkaffoldPipeline, profile latest.Profile) { // this intentionally removes the Profiles field from the returned config *config = latest.SkaffoldPipeline{ APIVersion: config.APIVersion, + 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/versions_test.go b/pkg/skaffold/schema/versions_test.go index 5b10601e046..ccfefd3bae6 100644 --- a/pkg/skaffold/schema/versions_test.go +++ b/pkg/skaffold/schema/versions_test.go @@ -173,7 +173,7 @@ func TestParseConfig(t *testing.T) { tmp, cleanup := testutil.NewTempDir(t) defer cleanup() - yaml := fmt.Sprintf("apiVersion: %s\n%s", latest.Version, test.config) + yaml := fmt.Sprintf("apiVersion: %s\nkind: Config\n%s", latest.Version, test.config) tmp.Write("skaffold.yaml", yaml) cfg, err := ParseConfig(tmp.Path("skaffold.yaml"), true) @@ -184,7 +184,7 @@ func TestParseConfig(t *testing.T) { } func config(ops ...func(*latest.SkaffoldPipeline)) *latest.SkaffoldPipeline { - cfg := &latest.SkaffoldPipeline{APIVersion: latest.Version} + cfg := &latest.SkaffoldPipeline{APIVersion: latest.Version, Kind: "Config"} for _, op := range ops { op(cfg) } From 07bca7d88001fad5f3a2b22a1dafd31ca6ad042f Mon Sep 17 00:00:00 2001 From: Nick Kubala Date: Thu, 4 Oct 2018 15:12:47 -0700 Subject: [PATCH 59/61] nits --- cmd/skaffold/app/cmd/init.go | 24 ++++++++++++------------ pkg/skaffold/runner/runner_test.go | 26 +++++++++++++------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/skaffold/app/cmd/init.go b/cmd/skaffold/app/cmd/init.go index e78d5fcaa0e..0a96e703a46 100644 --- a/cmd/skaffold/app/cmd/init.go +++ b/cmd/skaffold/app/cmd/init.go @@ -132,18 +132,18 @@ func doInit(out io.Writer) error { } } - cfg, err := generateSkaffoldPipeline(k8sConfigs, pairs) + pipeline, err := generateSkaffoldPipeline(k8sConfigs, pairs) if err != nil { return err } if opts.ConfigurationFile == "-" { - out.Write(cfg) + out.Write(pipeline) return nil } if !force { - fmt.Fprintln(out, string(cfg)) + fmt.Fprintln(out, string(pipeline)) reader := bufio.NewReader(os.Stdin) confirmLoop: @@ -165,7 +165,7 @@ func doInit(out io.Writer) error { } } - if err := ioutil.WriteFile(opts.ConfigurationFile, cfg, 0644); err != nil { + if err := ioutil.WriteFile(opts.ConfigurationFile, pipeline, 0644); err != nil { return errors.Wrap(err, "writing config to file") } @@ -266,15 +266,15 @@ func generateSkaffoldPipeline(k8sConfigs []string, dockerfilePairs []dockerfileP // if the user doesn't have any k8s yamls, generate one for each dockerfile logrus.Info("generating skaffold config") - config := &latest.SkaffoldPipeline{ + pipeline := &latest.SkaffoldPipeline{ APIVersion: latest.Version, } - if err := config.SetDefaultValues(); err != nil { - return nil, errors.Wrap(err, "generating default config") + if err := pipeline.SetDefaultValues(); err != nil { + return nil, errors.Wrap(err, "generating default pipeline") } - config.Build = processBuildArtifacts(dockerfilePairs) - config.Deploy = latest.DeployConfig{ + pipeline.Build = processBuildArtifacts(dockerfilePairs) + pipeline.Deploy = latest.DeployConfig{ DeployType: latest.DeployType{ KubectlDeploy: &latest.KubectlDeploy{ Manifests: k8sConfigs, @@ -282,12 +282,12 @@ func generateSkaffoldPipeline(k8sConfigs []string, dockerfilePairs []dockerfileP }, } - cfgStr, err := yaml.Marshal(config) + pipelineStr, err := yaml.Marshal(pipeline) if err != nil { - return nil, errors.Wrap(err, "marshaling generated config") + return nil, errors.Wrap(err, "marshaling generated pipeline") } - return cfgStr, nil + return pipelineStr, nil } // parseKubernetesYaml attempts to parse k8s objects from a yaml file diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 334b546b116..bbb09d8445a 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -180,7 +180,7 @@ func (t *TestWatcher) Run(ctx context.Context, trigger watch.Trigger, onChange f func TestNewForConfig(t *testing.T) { var tests = []struct { description string - config *latest.SkaffoldPipeline + pipeline *latest.SkaffoldPipeline shouldErr bool expectedBuilder build.Builder expectedTester test.Tester @@ -188,7 +188,7 @@ func TestNewForConfig(t *testing.T) { }{ { description: "local builder config", - config: &latest.SkaffoldPipeline{ + pipeline: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ TagPolicy: latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}, BuildType: latest.BuildType{ @@ -207,7 +207,7 @@ func TestNewForConfig(t *testing.T) { }, { description: "bad tagger config", - config: &latest.SkaffoldPipeline{ + pipeline: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ TagPolicy: latest.TagPolicy{}, BuildType: latest.BuildType{ @@ -224,7 +224,7 @@ func TestNewForConfig(t *testing.T) { }, { description: "unknown builder", - config: &latest.SkaffoldPipeline{ + pipeline: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{}, }, shouldErr: true, @@ -234,7 +234,7 @@ func TestNewForConfig(t *testing.T) { }, { description: "unknown tagger", - config: &latest.SkaffoldPipeline{ + pipeline: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ TagPolicy: latest.TagPolicy{}, BuildType: latest.BuildType{ @@ -248,7 +248,7 @@ func TestNewForConfig(t *testing.T) { }, { description: "unknown deployer", - config: &latest.SkaffoldPipeline{ + pipeline: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ TagPolicy: latest.TagPolicy{ShaTagger: &latest.ShaTagger{}}, BuildType: latest.BuildType{ @@ -263,7 +263,7 @@ func TestNewForConfig(t *testing.T) { t.Run(test.description, func(t *testing.T) { cfg, err := NewForConfig(&config.SkaffoldOptions{ Trigger: "polling", - }, test.config) + }, test.pipeline) testutil.CheckError(t, test.shouldErr, err) if cfg != nil { @@ -280,7 +280,7 @@ func TestNewForConfig(t *testing.T) { func TestRun(t *testing.T) { var tests = []struct { description string - config *latest.SkaffoldPipeline + pipeline *latest.SkaffoldPipeline builder build.Builder tester test.Tester deployer deploy.Deployer @@ -288,14 +288,14 @@ func TestRun(t *testing.T) { }{ { description: "run no error", - config: &latest.SkaffoldPipeline{}, + pipeline: &latest.SkaffoldPipeline{}, builder: &TestBuilder{}, tester: &TestTester{}, deployer: &TestDeployer{}, }, { description: "run build error", - config: &latest.SkaffoldPipeline{}, + pipeline: &latest.SkaffoldPipeline{}, builder: &TestBuilder{ errors: []error{fmt.Errorf("")}, }, @@ -304,7 +304,7 @@ func TestRun(t *testing.T) { }, { description: "run deploy error", - config: &latest.SkaffoldPipeline{ + pipeline: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ Artifacts: []*latest.Artifact{ { @@ -322,7 +322,7 @@ func TestRun(t *testing.T) { }, { description: "run test error", - config: &latest.SkaffoldPipeline{ + pipeline: &latest.SkaffoldPipeline{ Build: latest.BuildConfig{ Artifacts: []*latest.Artifact{ { @@ -354,7 +354,7 @@ func TestRun(t *testing.T) { Tagger: &tag.ChecksumTagger{}, opts: &config.SkaffoldOptions{}, } - err := runner.Run(context.Background(), ioutil.Discard, test.config.Build.Artifacts) + err := runner.Run(context.Background(), ioutil.Discard, test.pipeline.Build.Artifacts) testutil.CheckError(t, test.shouldErr, err) }) From f25a9222f15404a8aa4f6b0dcd6689150cc169e0 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Mon, 8 Oct 2018 15:26:10 +0200 Subject: [PATCH 60/61] Update labels when deploying to namespace other than default Fixes #1112 Signed-off-by: David Gageot --- cmd/skaffold/app/cmd/cmd.go | 2 +- pkg/skaffold/deploy/kubectl.go | 10 ++++++---- pkg/skaffold/deploy/kustomize.go | 2 +- pkg/skaffold/deploy/labels.go | 3 +++ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cmd/skaffold/app/cmd/cmd.go b/cmd/skaffold/app/cmd/cmd.go index 83b33a6ec9e..103c7c5dfa0 100644 --- a/cmd/skaffold/app/cmd/cmd.go +++ b/cmd/skaffold/app/cmd/cmd.go @@ -148,7 +148,7 @@ func AddRunDevFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&opts.ConfigurationFile, "filename", "f", "skaffold.yaml", "Filename or URL to the pipeline file") cmd.Flags().BoolVar(&opts.Notification, "toot", false, "Emit a terminal beep after the deploy is complete") cmd.Flags().StringArrayVarP(&opts.Profiles, "profile", "p", nil, "Activate profiles by name") - cmd.Flags().StringVarP(&opts.Namespace, "namespace", "n", "", "Run Helm deployments in the specified namespace") + cmd.Flags().StringVarP(&opts.Namespace, "namespace", "n", "", "Run deployments in the specified namespace") } func AddFixFlags(cmd *cobra.Command) { diff --git a/pkg/skaffold/deploy/kubectl.go b/pkg/skaffold/deploy/kubectl.go index 3eef482db37..977ffdadadc 100644 --- a/pkg/skaffold/deploy/kubectl.go +++ b/pkg/skaffold/deploy/kubectl.go @@ -86,7 +86,7 @@ func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []bu return nil, errors.Wrap(err, "apply") } - return parseManifestsForDeploys(updated) + return parseManifestsForDeploys(k.kubectl.Namespace, updated) } // Cleanup deletes what was deployed by calling Deploy. @@ -128,12 +128,14 @@ func (k *KubectlDeployer) manifestFiles(manifests []string) ([]string, error) { return filteredManifests, nil } -func parseManifestsForDeploys(manifests kubectl.ManifestList) ([]Artifact, error) { - results := []Artifact{} +func parseManifestsForDeploys(namespace string, manifests kubectl.ManifestList) ([]Artifact, error) { + var results []Artifact + for _, manifest := range manifests { b := bufio.NewReader(bytes.NewReader(manifest)) - results = append(results, parseReleaseInfo("", b)...) + results = append(results, parseReleaseInfo(namespace, b)...) } + return results, nil } diff --git a/pkg/skaffold/deploy/kustomize.go b/pkg/skaffold/deploy/kustomize.go index e34ad5217a0..87545dcb35e 100644 --- a/pkg/skaffold/deploy/kustomize.go +++ b/pkg/skaffold/deploy/kustomize.go @@ -98,7 +98,7 @@ func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds [] return nil, errors.Wrap(err, "apply") } - return parseManifestsForDeploys(updated) + return parseManifestsForDeploys(k.kubectl.Namespace, updated) } // Cleanup deletes what was deployed by calling Deploy. diff --git a/pkg/skaffold/deploy/labels.go b/pkg/skaffold/deploy/labels.go index 843e7f7a6fa..52de5bff64e 100644 --- a/pkg/skaffold/deploy/labels.go +++ b/pkg/skaffold/deploy/labels.go @@ -147,10 +147,13 @@ func updateRuntimeObject(client dynamic.Interface, disco discovery.DiscoveryInte if err != nil { return errors.Wrap(err, "getting group version resource from obj") } + ns, err := resolveNamespace(namespace) if err != nil { return errors.Wrap(err, "resolving namespace") } + logrus.Debugln("Patching", name, "in namespace", ns) + if _, err := client.Resource(gvr).Namespace(ns).Patch(name, types.StrategicMergePatchType, p); err != nil { return errors.Wrapf(err, "patching resource %s/%s", namespace, name) } From cabcee3c49066fa57f643f17dde74a46f9932701 Mon Sep 17 00:00:00 2001 From: balopat Date: Mon, 8 Oct 2018 11:42:49 -0400 Subject: [PATCH 61/61] remove project level skaffold.yaml --- skaffold.yaml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 skaffold.yaml diff --git a/skaffold.yaml b/skaffold.yaml deleted file mode 100644 index 2979b09396f..00000000000 --- a/skaffold.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: skaffold/v1alpha3 -kind: Config -build: - artifacts: - - image: gcr.io/k8s-skaffold/skaffold - docker: - dockerfile: deploy/skaffold/Dockerfile -deploy: - kubectl: - manifests: - - deploy/skaffold/*