From d60e7a6eaeddc52c3a5997d7f1a12ad15e9ace1b Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Fri, 9 Aug 2019 10:45:15 -0700 Subject: [PATCH 1/7] wip --- pkg/skaffold/deploy/deploy.go | 24 +++- pkg/skaffold/deploy/helm.go | 6 +- pkg/skaffold/deploy/kubectl.go | 22 +-- pkg/skaffold/deploy/kubectl/namespace_test.go | 126 ++++++++++++++++++ pkg/skaffold/deploy/kubectl/namespaces.go | 69 ++++++++++ pkg/skaffold/deploy/kustomize.go | 21 +-- pkg/skaffold/event/event.go | 7 + pkg/skaffold/runner/deploy.go | 5 +- pkg/skaffold/runner/notification.go | 12 +- pkg/skaffold/runner/runcontext/context.go | 23 ++++ .../runner/runcontext/context_test.go | 69 ++++++++++ pkg/skaffold/runner/runner_test.go | 13 +- pkg/skaffold/runner/timings.go | 11 +- 13 files changed, 370 insertions(+), 38 deletions(-) create mode 100644 pkg/skaffold/deploy/kubectl/namespace_test.go create mode 100644 pkg/skaffold/deploy/kubectl/namespaces.go create mode 100644 pkg/skaffold/runner/runcontext/context_test.go diff --git a/pkg/skaffold/deploy/deploy.go b/pkg/skaffold/deploy/deploy.go index bad7c4cf644..4fd0a4de6b1 100644 --- a/pkg/skaffold/deploy/deploy.go +++ b/pkg/skaffold/deploy/deploy.go @@ -30,7 +30,7 @@ type Deployer interface { // Deploy should ensure that the build results are deployed to the Kubernetes // cluster. - Deploy(context.Context, io.Writer, []build.Artifact, []Labeller) error + Deploy(context.Context, io.Writer, []build.Artifact, []Labeller) *DeployResult // Dependencies returns a list of files that the deployer depends on. // In dev mode, a redeploy will be triggered @@ -39,3 +39,25 @@ type Deployer interface { // Cleanup deletes what was deployed by calling Deploy. Cleanup(context.Context, io.Writer) error } + + +type DeployResult struct{ + err error + namespaces []string +} + +func NewDeployErrorResult(err error) *DeployResult { + return &DeployResult{err : err} +} + +func NewDeploySuccessResult(namespaces []string) *DeployResult { + return &DeployResult{namespaces: namespaces} +} + +func (d *DeployResult) Namespaces() []string { + return d.namespaces +} + +func (d *DeployResult) InError() (bool, error) { + return d.err!=nil, d.err +} \ No newline at end of file diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index a8d7d6c0614..4e76b7e98b9 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -70,7 +70,7 @@ func (h *HelmDeployer) Labels() map[string]string { } } -func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) error { +func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *DeployResult { var dRes []Artifact event.DeployInProgress() @@ -81,7 +81,7 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build releaseName, _ := evaluateReleaseName(r.Name) event.DeployFailed(err) - return errors.Wrapf(err, "deploying %s", releaseName) + return NewDeployErrorResult(errors.Wrapf(err, "deploying %s", releaseName)) } dRes = append(dRes, results...) @@ -92,7 +92,7 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build labels := merge(labellers...) labelDeployResults(labels, dRes) - return nil + return NewDeploySuccessResult(nil) } func (h *HelmDeployer) Dependencies() ([]string, error) { diff --git a/pkg/skaffold/deploy/kubectl.go b/pkg/skaffold/deploy/kubectl.go index 3cc48f672f3..7ffbf4af142 100644 --- a/pkg/skaffold/deploy/kubectl.go +++ b/pkg/skaffold/deploy/kubectl.go @@ -71,7 +71,7 @@ func (k *KubectlDeployer) Labels() map[string]string { // Deploy templates the provided manifests with a simple `find and replace` and // runs `kubectl apply` on those manifests -func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) error { +func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *DeployResult { color.Default.Fprintln(out, "kubectl client version:", k.kubectl.Version(ctx)) if err := k.kubectl.CheckVersion(ctx); err != nil { color.Default.Fprintln(out, err) @@ -80,7 +80,7 @@ func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []bu manifests, err := k.readManifests(ctx) if err != nil { event.DeployFailed(err) - return errors.Wrap(err, "reading manifests") + return NewDeployErrorResult(errors.Wrap(err, "reading manifests")) } for _, m := range k.RemoteManifests { @@ -102,38 +102,44 @@ func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []bu logrus.Debugln("manifests", manifests.String()) if len(manifests) == 0 { - return nil + return NewDeploySuccessResult(nil) } event.DeployInProgress() + namespaces, err := manifests.CollectNamespaces() + if err != nil{ + event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace." + + "This might cause port-forward and deploy health-check to fail.")) + } + manifests, err = manifests.ReplaceImages(builds, k.defaultRepo) if err != nil { event.DeployFailed(err) - return errors.Wrap(err, "replacing images in manifests") + return NewDeployErrorResult(errors.Wrap(err, "replacing images in manifests")) } manifests, err = manifests.SetLabels(merge(labellers...)) if err != nil { event.DeployFailed(err) - return errors.Wrap(err, "setting labels in manifests") + return NewDeployErrorResult(errors.Wrap(err, "setting labels in manifests")) } for _, transform := range manifestTransforms { manifests, err = transform(manifests, builds, k.insecureRegistries) if err != nil { event.DeployFailed(err) - return errors.Wrap(err, "unable to transform manifests") + return NewDeployErrorResult(errors.Wrap(err, "unable to transform manifests")) } } if err := k.kubectl.Apply(ctx, out, manifests); err != nil { event.DeployFailed(err) - return errors.Wrap(err, "kubectl error") + return NewDeployErrorResult(errors.Wrap(err, "kubectl error")) } event.DeployComplete() - return nil + return NewDeploySuccessResult(namespaces) } // Cleanup deletes what was deployed by calling Deploy. diff --git a/pkg/skaffold/deploy/kubectl/namespace_test.go b/pkg/skaffold/deploy/kubectl/namespace_test.go new file mode 100644 index 00000000000..7328f10b5c1 --- /dev/null +++ b/pkg/skaffold/deploy/kubectl/namespace_test.go @@ -0,0 +1,126 @@ +/* +Copyright 2019 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 kubectl + +import ( + "fmt" + "testing" + + "github.com/GoogleContainerTools/skaffold/testutil" +) + +func TestCollectLabels(t *testing.T) { + tests := []struct { + description string + manifests ManifestList + expected []string + }{ + { + description: "single manifest in the list", + manifests: ManifestList{[]byte(` +apiVersion: v1 +kind: Pod +metadata: + name: getting-started + namespace: test +spec: + containers: + - image: gcr.io/k8s-skaffold/example + name: example +`)}, + expected: []string{"test"}, + }, { + description: "multiple manifest in the list with different namespaces", + manifests: ManifestList{[]byte(` +apiVersion: v1 +kind: Pod +metadata: + name: foo + namespace: test-foo +spec: + containers: + - image: gcr.io/k8s-skaffold/example + name: example`), []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: bar + namespace: test-bar +spec: + containers: + - image: gcr.io/k8s-skaffold/example + name: example +`)}, + expected: []string{"test-foo", "test-bar"}, + }, { + description: "multiple manifest but same namespace", + manifests: ManifestList{[]byte(` +apiVersion: v1 +kind: Pod +metadata: + name: foo + namespace: test +spec: + containers: + - image: gcr.io/k8s-skaffold/example + name: example`), []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: bar + namespace: test +spec: + containers: + - image: gcr.io/k8s-skaffold/example + name: example +`)}, + expected: []string{"test"}, + }, { + description: "multiple manifest but no namespace", + manifests: ManifestList{[]byte(` +apiVersion: v1 +kind: Pod +metadata: + name: foo +spec: + containers: + - image: gcr.io/k8s-skaffold/example + name: example`), []byte(` +apiVersion: v1 +kind: Pod +metadata: + name: bar +spec: + containers: + - image: gcr.io/k8s-skaffold/example + name: example +`)}, + expected: []string{}, + }, { + description: "empty manifest", + manifests: ManifestList{[]byte(``)}, + expected: []string{}, + }, + } + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + actual, err := test.manifests.CollectNamespaces() + fmt.Println(actual) + t.CheckErrorAndDeepEqual(false, err, test.expected, actual) + }) + } +} diff --git a/pkg/skaffold/deploy/kubectl/namespaces.go b/pkg/skaffold/deploy/kubectl/namespaces.go new file mode 100644 index 00000000000..018d0a17414 --- /dev/null +++ b/pkg/skaffold/deploy/kubectl/namespaces.go @@ -0,0 +1,69 @@ +/* +Copyright 2019 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 kubectl + +import ( + "sort" + + "github.com/pkg/errors" + + "github.com/GoogleContainerTools/skaffold/integration/examples/bazel/bazel-bazel/external/go_sdk/src/strings" +) + +// CollectNamespaces returns all the namespaces in the manifests. +func (l *ManifestList) CollectNamespaces() ([]string, error) { + replacer := newNamespaceCollector() + + if _, err := l.Visit(replacer); err != nil { + return nil, errors.Wrap(err, "collecting namespaces") + } + namespaces := make([]string, len(replacer.namespaces)) + for ns, _ := range replacer.namespaces { + namespaces = append(namespaces, ns) + } + sort.Strings(namespaces) + return namespaces, nil +} + +type namespaceCollector struct { + namespaces map[string]bool +} + +func newNamespaceCollector() *namespaceCollector { + return &namespaceCollector{ + namespaces: map[string]bool{}, + } +} + +func (r *namespaceCollector) Matches(key string) bool { + return key == "metadata" +} + +func (r *namespaceCollector) NewValue(old interface{}) (bool, interface{}) { + metadata, ok := old.(map[interface{}]interface{}) + if !ok { + return false, nil + } + if nsValue, present := metadata["namespace"]; present { + if ns := strings.TrimSpace(nsValue.(string)); ns != "" { + r.namespaces[ns] = true + } + return true, metadata + } + + return false, nil +} diff --git a/pkg/skaffold/deploy/kustomize.go b/pkg/skaffold/deploy/kustomize.go index 54a0cd57fda..24619172b9f 100644 --- a/pkg/skaffold/deploy/kustomize.go +++ b/pkg/skaffold/deploy/kustomize.go @@ -93,7 +93,7 @@ func (k *KustomizeDeployer) Labels() map[string]string { } // Deploy runs `kubectl apply` on the manifest generated by kustomize. -func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) error { +func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *DeployResult { color.Default.Fprintln(out, "kubectl client version:", k.kubectl.Version(ctx)) if err := k.kubectl.CheckVersion(ctx); err != nil { color.Default.Fprintln(out, err) @@ -102,42 +102,47 @@ func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds [] manifests, err := k.readManifests(ctx) if err != nil { event.DeployFailed(err) - return errors.Wrap(err, "reading manifests") + return NewDeployErrorResult(errors.Wrap(err, "reading manifests")) } if len(manifests) == 0 { - return nil + return NewDeploySuccessResult(nil) } event.DeployInProgress() + namespaces, err := manifests.CollectNamespaces() + if err != nil{ + event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace." + + "This might cause port-forward and deploy health-check to fail.")) + } manifests, err = manifests.ReplaceImages(builds, k.defaultRepo) if err != nil { event.DeployFailed(err) - return errors.Wrap(err, "replacing images in manifests") + return NewDeployErrorResult(errors.Wrap(err, "replacing images in manifests")) } manifests, err = manifests.SetLabels(merge(labellers...)) if err != nil { event.DeployFailed(err) - return errors.Wrap(err, "setting labels in manifests") + return NewDeployErrorResult(errors.Wrap(err, "setting labels in manifests")) } for _, transform := range manifestTransforms { manifests, err = transform(manifests, builds, k.insecureRegistries) if err != nil { event.DeployFailed(err) - return errors.Wrap(err, "unable to transform manifests") + return NewDeployErrorResult(errors.Wrap(err, "unable to transform manifests")) } } if err := k.kubectl.Apply(ctx, out, manifests); err != nil { event.DeployFailed(err) - return errors.Wrap(err, "kubectl error") + return NewDeployErrorResult(errors.Wrap(err, "kubectl error")) } event.DeployComplete() - return nil + return NewDeploySuccessResult(namespaces) } // Cleanup deletes what was deployed by calling Deploy. diff --git a/pkg/skaffold/event/event.go b/pkg/skaffold/event/event.go index 39894f1d5cb..957e9632952 100644 --- a/pkg/skaffold/event/event.go +++ b/pkg/skaffold/event/event.go @@ -32,6 +32,7 @@ const ( InProgress = "In Progress" Complete = "Complete" Failed = "Failed" + Info = "Information" ) var handler = &eventHandler{} @@ -154,6 +155,12 @@ func DeployFailed(err error) { handler.handleDeployEvent(&proto.DeployEvent{Status: Failed, Err: err.Error()}) } +// DeployEvent notifies that a deployment of non fatal errors during deploy w +func DeployInfoEvent(err error) { + handler.handleDeployEvent(&proto.DeployEvent{Status: Info , Err: err.Error()}) +} + + // DeployComplete notifies that a deployment has completed. func DeployComplete() { handler.handleDeployEvent(&proto.DeployEvent{Status: Complete}) diff --git a/pkg/skaffold/runner/deploy.go b/pkg/skaffold/runner/deploy.go index e3ddddfddce..9d9e1dada9e 100644 --- a/pkg/skaffold/runner/deploy.go +++ b/pkg/skaffold/runner/deploy.go @@ -34,11 +34,12 @@ func (r *SkaffoldRunner) Deploy(ctx context.Context, out io.Writer, artifacts [] } } - err := r.deployer.Deploy(ctx, out, artifacts, r.labellers) + deployResult := r.deployer.Deploy(ctx, out, artifacts, r.labellers) r.hasDeployed = true - if err != nil { + if isErr, err := deployResult.InError(); isErr { return err } + r.runCtx.UpdateNamespaces(deployResult.Namespaces()) return r.performStatusCheck(ctx, out) } diff --git a/pkg/skaffold/runner/notification.go b/pkg/skaffold/runner/notification.go index a2b5d12bdc2..b79aefdd5bc 100644 --- a/pkg/skaffold/runner/notification.go +++ b/pkg/skaffold/runner/notification.go @@ -41,12 +41,10 @@ type withNotification struct { deploy.Deployer } -func (w withNotification) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) error { - if err := w.Deployer.Deploy(ctx, out, builds, labellers); err != nil { - return err +func (w withNotification) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) *deploy.DeployResult { + dr := w.Deployer.Deploy(ctx, out, builds, labellers); + if isErr, _ := dr.InError(); isErr { + fmt.Fprint(out, terminalBell) } - - fmt.Fprint(out, terminalBell) - - return nil + return dr } diff --git a/pkg/skaffold/runner/runcontext/context.go b/pkg/skaffold/runner/runcontext/context.go index f5cdaea8b62..f3a4f5b7ac2 100644 --- a/pkg/skaffold/runner/runcontext/context.go +++ b/pkg/skaffold/runner/runcontext/context.go @@ -18,6 +18,7 @@ package runcontext import ( "os" + "sort" configutil "github.com/GoogleContainerTools/skaffold/cmd/skaffold/app/cmd/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" @@ -85,3 +86,25 @@ func GetRunContext(opts config.SkaffoldOptions, cfg latest.Pipeline) (*RunContex InsecureRegistries: insecureRegistries, }, nil } + + +func (r *RunContext) UpdateNamespaces(ns []string) { + if len(ns) == 0 { + return + } + + nsMap := map[string]bool{} + for _, ns := range append(ns, r.Namespaces...) { + nsMap[ns] = true + } + + // Update RunContext Namespace + updated := make([]string, len(nsMap)) + i :=0 + for k, _ := range(nsMap) { + updated[i] = k + i++ + } + sort.Strings(updated) + r.Namespaces = updated +} \ No newline at end of file diff --git a/pkg/skaffold/runner/runcontext/context_test.go b/pkg/skaffold/runner/runcontext/context_test.go new file mode 100644 index 00000000000..5d2219cd2ff --- /dev/null +++ b/pkg/skaffold/runner/runcontext/context_test.go @@ -0,0 +1,69 @@ +/* +Copyright 2019 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 runcontext + +import ( + "testing" + + "github.com/GoogleContainerTools/skaffold/testutil" +) + +func TestRunContext_UpdateNamespaces(t *testing.T) { + tests := []struct { + description string + runContext *RunContext + namespaces []string + expected []string + }{ + { + description: "update namespace when not present in runContext", + runContext: &RunContext{Namespaces: []string{"test"}}, + namespaces: []string{"another"}, + expected: []string{"another", "test"}, + }, + { + description: "update namespace with duplicates should not return duplicate", + runContext: &RunContext{Namespaces: []string{"test", "foo"}}, + namespaces: []string{"another", "foo", "another"}, + expected: []string{"another", "foo", "test"}, + }, + { + description: "update namespaces when namespaces is empty", + runContext: &RunContext{Namespaces: []string{"test", "foo"}}, + namespaces: []string{}, + expected: []string{"test", "foo"}, + }, + { + description: "update namespaces when runcontext namespaces is empty", + runContext: &RunContext{Namespaces: []string{}}, + namespaces: []string{"test", "another"}, + expected: []string{"another", "test"}, + }, + { + description: "update namespaces when both namespaces and runcontext namespaces is empty", + runContext: &RunContext{Namespaces: []string{}}, + namespaces: []string{}, + expected: []string{}, + }, + } + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + test.runContext.UpdateNamespaces(test.namespaces) + t.CheckDeepEqual(test.expected, test.runContext.Namespaces) + }) + } +} diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 28e5d721cf7..666330b9227 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -50,6 +50,7 @@ type TestBench struct { syncErrors []error testErrors []error deployErrors []error + namespaces []string devLoop func(context.Context, io.Writer) error firstMonitor func(bool) error @@ -79,6 +80,12 @@ func (t *TestBench) WithDeployErrors(deployErrors []error) *TestBench { return t } +func (t *TestBench) WithDeployNamespaces(ns []string) *TestBench { + t.namespaces = ns + return t +} + + func (t *TestBench) WithTestErrors(testErrors []error) *TestBench { t.testErrors = testErrors return t @@ -151,17 +158,17 @@ func (t *TestBench) Test(_ context.Context, _ io.Writer, artifacts []build.Artif return nil } -func (t *TestBench) Deploy(_ context.Context, _ io.Writer, artifacts []build.Artifact, _ []deploy.Labeller) error { +func (t *TestBench) Deploy(_ context.Context, _ io.Writer, artifacts []build.Artifact, _ []deploy.Labeller) *deploy.DeployResult { if len(t.deployErrors) > 0 { err := t.deployErrors[0] t.deployErrors = t.deployErrors[1:] if err != nil { - return err + return deploy.NewDeployErrorResult(err) } } t.currentActions.Deployed = findTags(artifacts) - return nil + return deploy.NewDeploySuccessResult(t.namespaces) } func (t *TestBench) Actions() []Actions { diff --git a/pkg/skaffold/runner/timings.go b/pkg/skaffold/runner/timings.go index e7816827eca..fa09e41d1e7 100644 --- a/pkg/skaffold/runner/timings.go +++ b/pkg/skaffold/runner/timings.go @@ -82,16 +82,15 @@ func (w withTimings) Test(ctx context.Context, out io.Writer, builds []build.Art return nil } -func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) error { +func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) *deploy.DeployResult { start := time.Now() color.Default.Fprintln(out, "Starting deploy...") - if err := w.Deployer.Deploy(ctx, out, builds, labellers); err != nil { - return err + dr := w.Deployer.Deploy(ctx, out, builds, labellers) + if isErr, _ := dr.InError(); !isErr { + color.Default.Fprintln(out, "Deploy complete in", time.Since(start)) } - - color.Default.Fprintln(out, "Deploy complete in", time.Since(start)) - return nil + return dr } func (w withTimings) Cleanup(ctx context.Context, out io.Writer) error { From c2a287fba646821eebc85fad67dfbb1b3b443a80 Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Mon, 12 Aug 2019 13:35:02 -0700 Subject: [PATCH 2/7] test --- go.mod | 1 + pkg/skaffold/deploy/deploy.go | 4 ++++ pkg/skaffold/deploy/helm.go | 2 +- pkg/skaffold/deploy/helm_test.go | 2 +- pkg/skaffold/deploy/kubectl/namespace_test.go | 4 +--- pkg/skaffold/deploy/kubectl/namespaces.go | 7 ++++--- pkg/skaffold/deploy/kubectl_test.go | 8 ++++---- pkg/skaffold/deploy/kustomize_test.go | 2 +- vendor/modules.txt | 6 +++--- 9 files changed, 20 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 24fac4ce87c..512852dc22e 100644 --- a/go.mod +++ b/go.mod @@ -79,6 +79,7 @@ require ( k8s.io/apimachinery v0.0.0-20190620073744-d16981aedf33 k8s.io/client-go v0.0.0-20190620074045-585a16d2e773 k8s.io/kubectl v0.0.0-20190622051205-955b067cc6d3 + k8s.io/utils v0.0.0-20190221042446-c2654d5206da knative.dev/pkg v0.0.0-20190730155243-972acd413fb9 // indirect ) diff --git a/pkg/skaffold/deploy/deploy.go b/pkg/skaffold/deploy/deploy.go index 4fd0a4de6b1..63efc9b82bb 100644 --- a/pkg/skaffold/deploy/deploy.go +++ b/pkg/skaffold/deploy/deploy.go @@ -60,4 +60,8 @@ func (d *DeployResult) Namespaces() []string { func (d *DeployResult) InError() (bool, error) { return d.err!=nil, d.err +} + +func (d *DeployResult) GetError() error { + return d.err } \ No newline at end of file diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index 4e76b7e98b9..f1f7f831365 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -390,7 +390,7 @@ func (h *HelmDeployer) packageChart(ctx context.Context, r latest.HelmRelease) ( func (h *HelmDeployer) getReleaseInfo(ctx context.Context, release string) (*bufio.Reader, error) { var releaseInfo bytes.Buffer - if err := h.helm(ctx, &releaseInfo, false, "get", release); err != nil { + if err := h.helm(ctx, &releaseInfo, false, "get", "manifest", release); err != nil { return nil, fmt.Errorf("error retrieving helm deployment info: %s", releaseInfo.String()) } return bufio.NewReader(&releaseInfo), nil diff --git a/pkg/skaffold/deploy/helm_test.go b/pkg/skaffold/deploy/helm_test.go index eff319f1855..60c0a70ed4c 100644 --- a/pkg/skaffold/deploy/helm_test.go +++ b/pkg/skaffold/deploy/helm_test.go @@ -480,7 +480,7 @@ func TestHelmDeploy(t *testing.T) { t.Override(&util.DefaultExecCommand, test.cmd) event.InitializeState(test.runContext.Cfg.Build) - err := NewHelmDeployer(test.runContext).Deploy(context.Background(), ioutil.Discard, test.builds, nil) + err := NewHelmDeployer(test.runContext).Deploy(context.Background(), ioutil.Discard, test.builds, nil).GetError() t.CheckError(test.shouldErr, err) }) diff --git a/pkg/skaffold/deploy/kubectl/namespace_test.go b/pkg/skaffold/deploy/kubectl/namespace_test.go index 7328f10b5c1..fc569a160f3 100644 --- a/pkg/skaffold/deploy/kubectl/namespace_test.go +++ b/pkg/skaffold/deploy/kubectl/namespace_test.go @@ -17,7 +17,6 @@ limitations under the License. package kubectl import ( - "fmt" "testing" "github.com/GoogleContainerTools/skaffold/testutil" @@ -65,7 +64,7 @@ spec: - image: gcr.io/k8s-skaffold/example name: example `)}, - expected: []string{"test-foo", "test-bar"}, + expected: []string{"test-bar", "test-foo"}, }, { description: "multiple manifest but same namespace", manifests: ManifestList{[]byte(` @@ -119,7 +118,6 @@ spec: for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { actual, err := test.manifests.CollectNamespaces() - fmt.Println(actual) t.CheckErrorAndDeepEqual(false, err, test.expected, actual) }) } diff --git a/pkg/skaffold/deploy/kubectl/namespaces.go b/pkg/skaffold/deploy/kubectl/namespaces.go index 018d0a17414..3ec72fba38b 100644 --- a/pkg/skaffold/deploy/kubectl/namespaces.go +++ b/pkg/skaffold/deploy/kubectl/namespaces.go @@ -18,10 +18,9 @@ package kubectl import ( "sort" + "strings" "github.com/pkg/errors" - - "github.com/GoogleContainerTools/skaffold/integration/examples/bazel/bazel-bazel/external/go_sdk/src/strings" ) // CollectNamespaces returns all the namespaces in the manifests. @@ -32,8 +31,10 @@ func (l *ManifestList) CollectNamespaces() ([]string, error) { return nil, errors.Wrap(err, "collecting namespaces") } namespaces := make([]string, len(replacer.namespaces)) + i := 0 for ns, _ := range replacer.namespaces { - namespaces = append(namespaces, ns) + namespaces[i] = ns + i++ } sort.Strings(namespaces) return namespaces, nil diff --git a/pkg/skaffold/deploy/kubectl_test.go b/pkg/skaffold/deploy/kubectl_test.go index 2217269154f..b4fbf028052 100644 --- a/pkg/skaffold/deploy/kubectl_test.go +++ b/pkg/skaffold/deploy/kubectl_test.go @@ -195,7 +195,7 @@ func TestKubectlDeploy(t *testing.T) { t.CheckNoError(err) t.CheckDeepEqual(test.expectedDependencies, dependencies) - err = k.Deploy(context.Background(), ioutil.Discard, test.builds, nil) + err = k.Deploy(context.Background(), ioutil.Discard, test.builds, nil).GetError() t.CheckError(test.shouldErr, err) }) } @@ -392,21 +392,21 @@ spec: err := deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, {ImageName: "leeroy-app", Tag: "leeroy-app:v1"}, - }, labellers) + }, labellers).GetError() t.CheckNoError(err) // Deploy one manifest since only one image is updated err = deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, {ImageName: "leeroy-app", Tag: "leeroy-app:v2"}, - }, labellers) + }, labellers).GetError() t.CheckNoError(err) // Deploy zero manifest since no image is updated err = deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, {ImageName: "leeroy-app", Tag: "leeroy-app:v2"}, - }, labellers) + }, labellers).GetError() t.CheckNoError(err) }) } diff --git a/pkg/skaffold/deploy/kustomize_test.go b/pkg/skaffold/deploy/kustomize_test.go index 58f33a03346..fcc74eefde2 100644 --- a/pkg/skaffold/deploy/kustomize_test.go +++ b/pkg/skaffold/deploy/kustomize_test.go @@ -85,7 +85,7 @@ func TestKustomizeDeploy(t *testing.T) { Force: test.forceDeploy, }, }) - err := k.Deploy(context.Background(), ioutil.Discard, test.builds, nil) + err := k.Deploy(context.Background(), ioutil.Discard, test.builds, nil).GetError() t.CheckError(test.shouldErr, err) }) diff --git a/vendor/modules.txt b/vendor/modules.txt index 451f0a469d4..6cd739e7e44 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -540,9 +540,9 @@ k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/pkg/version k8s.io/apimachinery/pkg/runtime/serializer/streaming k8s.io/apimachinery/pkg/runtime/serializer/versioning -k8s.io/apimachinery/pkg/util/validation/field -k8s.io/apimachinery/pkg/util/validation k8s.io/apimachinery/pkg/api/equality +k8s.io/apimachinery/pkg/util/validation +k8s.io/apimachinery/pkg/util/validation/field k8s.io/apimachinery/third_party/forked/golang/reflect k8s.io/apimachinery/pkg/util/clock k8s.io/apimachinery/pkg/runtime/serializer/protobuf @@ -666,10 +666,10 @@ k8s.io/client-go/util/keyutil k8s.io/client-go/tools/clientcmd/api/v1 k8s.io/client-go/transport/spdy k8s.io/client-go/util/exec -k8s.io/client-go/third_party/forked/golang/template k8s.io/client-go/informers k8s.io/client-go/informers/core/v1 k8s.io/client-go/tools/cache +k8s.io/client-go/third_party/forked/golang/template k8s.io/client-go/informers/admissionregistration k8s.io/client-go/informers/apps k8s.io/client-go/informers/auditregistration From be007bf7e075b71f5b1de2a20a80500b2bbe8167 Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Mon, 12 Aug 2019 14:50:59 -0700 Subject: [PATCH 3/7] more tests --- go.mod | 1 - pkg/skaffold/deploy/deploy.go | 25 +++++---- pkg/skaffold/deploy/helm.go | 28 +++++++--- pkg/skaffold/deploy/kubectl.go | 8 +-- pkg/skaffold/deploy/kubectl/namespaces.go | 2 +- pkg/skaffold/deploy/kustomize.go | 8 +-- pkg/skaffold/event/event.go | 3 +- pkg/skaffold/runner/deploy.go | 2 +- pkg/skaffold/runner/deploy_test.go | 52 ++++++++++++++++++- pkg/skaffold/runner/notification.go | 6 +-- pkg/skaffold/runner/runcontext/context.go | 7 ++- .../runner/runcontext/context_test.go | 40 +++++++------- pkg/skaffold/runner/runner_test.go | 3 +- pkg/skaffold/runner/timings.go | 4 +- vendor/modules.txt | 6 +-- 15 files changed, 127 insertions(+), 68 deletions(-) diff --git a/go.mod b/go.mod index 512852dc22e..24fac4ce87c 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,6 @@ require ( k8s.io/apimachinery v0.0.0-20190620073744-d16981aedf33 k8s.io/client-go v0.0.0-20190620074045-585a16d2e773 k8s.io/kubectl v0.0.0-20190622051205-955b067cc6d3 - k8s.io/utils v0.0.0-20190221042446-c2654d5206da knative.dev/pkg v0.0.0-20190730155243-972acd413fb9 // indirect ) diff --git a/pkg/skaffold/deploy/deploy.go b/pkg/skaffold/deploy/deploy.go index 63efc9b82bb..f0e8fae42ee 100644 --- a/pkg/skaffold/deploy/deploy.go +++ b/pkg/skaffold/deploy/deploy.go @@ -30,7 +30,7 @@ type Deployer interface { // Deploy should ensure that the build results are deployed to the Kubernetes // cluster. - Deploy(context.Context, io.Writer, []build.Artifact, []Labeller) *DeployResult + Deploy(context.Context, io.Writer, []build.Artifact, []Labeller) *Result // Dependencies returns a list of files that the deployer depends on. // In dev mode, a redeploy will be triggered @@ -40,28 +40,27 @@ type Deployer interface { Cleanup(context.Context, io.Writer) error } - -type DeployResult struct{ - err error +type Result struct { + err error namespaces []string } -func NewDeployErrorResult(err error) *DeployResult { - return &DeployResult{err : err} +func NewDeployErrorResult(err error) *Result { + return &Result{err: err} } -func NewDeploySuccessResult(namespaces []string) *DeployResult { - return &DeployResult{namespaces: namespaces} +func NewDeploySuccessResult(namespaces []string) *Result { + return &Result{namespaces: namespaces} } -func (d *DeployResult) Namespaces() []string { +func (d *Result) Namespaces() []string { return d.namespaces } -func (d *DeployResult) InError() (bool, error) { - return d.err!=nil, d.err +func (d *Result) IsError() (bool, error) { + return d.err != nil, d.err } -func (d *DeployResult) GetError() error { +func (d *Result) GetError() error { return d.err -} \ No newline at end of file +} diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index f1f7f831365..b1e03dea9ca 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -29,6 +29,11 @@ import ( "strconv" "strings" + "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" @@ -37,10 +42,6 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" - homedir "github.com/mitchellh/go-homedir" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - yaml "gopkg.in/yaml.v2" ) type HelmDeployer struct { @@ -70,10 +71,11 @@ func (h *HelmDeployer) Labels() map[string]string { } } -func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *DeployResult { +func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *Result { var dRes []Artifact event.DeployInProgress() + nsMap := map[string]bool{} for _, r := range h.Releases { results, err := h.deployRelease(ctx, out, r, builds) @@ -83,6 +85,10 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build event.DeployFailed(err) return NewDeployErrorResult(errors.Wrapf(err, "deploying %s", releaseName)) } + // collect namespaces + for _, r := range results { + nsMap[r.Namespace] = true + } dRes = append(dRes, results...) } @@ -92,7 +98,15 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build labels := merge(labellers...) labelDeployResults(labels, dRes) - return NewDeploySuccessResult(nil) + // Collect namespaces in a string + namespaces := make([]string, len(nsMap)) + i := 0 + for k := range nsMap { + namespaces[i] = k + i++ + } + + return NewDeploySuccessResult(namespaces) } func (h *HelmDeployer) Dependencies() ([]string, error) { @@ -390,7 +404,7 @@ func (h *HelmDeployer) packageChart(ctx context.Context, r latest.HelmRelease) ( func (h *HelmDeployer) getReleaseInfo(ctx context.Context, release string) (*bufio.Reader, error) { var releaseInfo bytes.Buffer - if err := h.helm(ctx, &releaseInfo, false, "get", "manifest", release); err != nil { + if err := h.helm(ctx, &releaseInfo, false, "get", release); err != nil { return nil, fmt.Errorf("error retrieving helm deployment info: %s", releaseInfo.String()) } return bufio.NewReader(&releaseInfo), nil diff --git a/pkg/skaffold/deploy/kubectl.go b/pkg/skaffold/deploy/kubectl.go index 7ffbf4af142..18981767950 100644 --- a/pkg/skaffold/deploy/kubectl.go +++ b/pkg/skaffold/deploy/kubectl.go @@ -71,7 +71,7 @@ func (k *KubectlDeployer) Labels() map[string]string { // Deploy templates the provided manifests with a simple `find and replace` and // runs `kubectl apply` on those manifests -func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *DeployResult { +func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *Result { color.Default.Fprintln(out, "kubectl client version:", k.kubectl.Version(ctx)) if err := k.kubectl.CheckVersion(ctx); err != nil { color.Default.Fprintln(out, err) @@ -108,8 +108,8 @@ func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []bu event.DeployInProgress() namespaces, err := manifests.CollectNamespaces() - if err != nil{ - event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace." + + if err != nil { + event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace."+ "This might cause port-forward and deploy health-check to fail.")) } @@ -139,7 +139,7 @@ func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []bu } event.DeployComplete() - return NewDeploySuccessResult(namespaces) + return NewDeploySuccessResult(namespaces) } // Cleanup deletes what was deployed by calling Deploy. diff --git a/pkg/skaffold/deploy/kubectl/namespaces.go b/pkg/skaffold/deploy/kubectl/namespaces.go index 3ec72fba38b..0f978e97e2a 100644 --- a/pkg/skaffold/deploy/kubectl/namespaces.go +++ b/pkg/skaffold/deploy/kubectl/namespaces.go @@ -32,7 +32,7 @@ func (l *ManifestList) CollectNamespaces() ([]string, error) { } namespaces := make([]string, len(replacer.namespaces)) i := 0 - for ns, _ := range replacer.namespaces { + for ns := range replacer.namespaces { namespaces[i] = ns i++ } diff --git a/pkg/skaffold/deploy/kustomize.go b/pkg/skaffold/deploy/kustomize.go index 24619172b9f..46c8a4bde98 100644 --- a/pkg/skaffold/deploy/kustomize.go +++ b/pkg/skaffold/deploy/kustomize.go @@ -93,7 +93,7 @@ func (k *KustomizeDeployer) Labels() map[string]string { } // Deploy runs `kubectl apply` on the manifest generated by kustomize. -func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *DeployResult { +func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []Labeller) *Result { color.Default.Fprintln(out, "kubectl client version:", k.kubectl.Version(ctx)) if err := k.kubectl.CheckVersion(ctx); err != nil { color.Default.Fprintln(out, err) @@ -112,9 +112,9 @@ func (k *KustomizeDeployer) Deploy(ctx context.Context, out io.Writer, builds [] event.DeployInProgress() namespaces, err := manifests.CollectNamespaces() - if err != nil{ - event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace." + - "This might cause port-forward and deploy health-check to fail.")) + if err != nil { + event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace."+ + "This might cause port-forward and deploy health-check to fail.")) } manifests, err = manifests.ReplaceImages(builds, k.defaultRepo) if err != nil { diff --git a/pkg/skaffold/event/event.go b/pkg/skaffold/event/event.go index 957e9632952..b767a691e3d 100644 --- a/pkg/skaffold/event/event.go +++ b/pkg/skaffold/event/event.go @@ -157,10 +157,9 @@ func DeployFailed(err error) { // DeployEvent notifies that a deployment of non fatal errors during deploy w func DeployInfoEvent(err error) { - handler.handleDeployEvent(&proto.DeployEvent{Status: Info , Err: err.Error()}) + handler.handleDeployEvent(&proto.DeployEvent{Status: Info, Err: err.Error()}) } - // DeployComplete notifies that a deployment has completed. func DeployComplete() { handler.handleDeployEvent(&proto.DeployEvent{Status: Complete}) diff --git a/pkg/skaffold/runner/deploy.go b/pkg/skaffold/runner/deploy.go index 9d9e1dada9e..51b68428b09 100644 --- a/pkg/skaffold/runner/deploy.go +++ b/pkg/skaffold/runner/deploy.go @@ -36,7 +36,7 @@ func (r *SkaffoldRunner) Deploy(ctx context.Context, out io.Writer, artifacts [] deployResult := r.deployer.Deploy(ctx, out, artifacts, r.labellers) r.hasDeployed = true - if isErr, err := deployResult.InError(); isErr { + if isErr, err := deployResult.IsError(); isErr { return err } r.runCtx.UpdateNamespaces(deployResult.Namespaces()) diff --git a/pkg/skaffold/runner/deploy_test.go b/pkg/skaffold/runner/deploy_test.go index 8b59da8dcee..f5802b0f498 100644 --- a/pkg/skaffold/runner/deploy_test.go +++ b/pkg/skaffold/runner/deploy_test.go @@ -20,14 +20,16 @@ import ( "bytes" "context" "errors" + "io/ioutil" "strings" "testing" + "k8s.io/client-go/tools/clientcmd/api" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext" "github.com/GoogleContainerTools/skaffold/testutil" - "k8s.io/client-go/tools/clientcmd/api" ) func TestDeploy(t *testing.T) { @@ -80,3 +82,51 @@ func TestDeploy(t *testing.T) { }) } } + +func TestDeployNamespace(t *testing.T) { + tests := []struct { + description string + Namespaces []string + testBench *TestBench + expected []string + }{ + { + description: "deploy shd add all namespaces to run Context", + Namespaces: []string{"test", "test-ns"}, + testBench: NewTestBench().WithDeployNamespaces([]string{"test-ns", "test-ns-1"}), + expected: []string{"test", "test-ns", "test-ns-1"}, + }, + { + description: "deploy without command opts namespace", + testBench: NewTestBench().WithDeployNamespaces([]string{"test-ns", "test-ns-1"}), + expected: []string{"test-ns", "test-ns-1"}, + }, + { + description: "deploy with no namespaces returned", + Namespaces: []string{"test"}, + testBench: &TestBench{}, + expected: []string{"test"}, + }, + } + + dummyStatusCheck := func(ctx context.Context, l *deploy.DefaultLabeller, runCtx *runcontext.RunContext) error { + return nil + } + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + t.SetupFakeKubernetesContext( + api.Config{CurrentContext: "cluster1"}) + t.Override(&statusCheck, dummyStatusCheck) + + runner := createRunner(t, test.testBench, nil) + runner.runCtx.Namespaces = test.Namespaces + + runner.Deploy(context.Background(), ioutil.Discard, []build.Artifact{ + {ImageName: "img1", Tag: "img1:tag1"}, + {ImageName: "img2", Tag: "img2:tag2"}, + }) + + t.CheckDeepEqual(test.expected, runner.runCtx.Namespaces) + }) + } +} diff --git a/pkg/skaffold/runner/notification.go b/pkg/skaffold/runner/notification.go index b79aefdd5bc..37d49921bb3 100644 --- a/pkg/skaffold/runner/notification.go +++ b/pkg/skaffold/runner/notification.go @@ -41,9 +41,9 @@ type withNotification struct { deploy.Deployer } -func (w withNotification) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) *deploy.DeployResult { - dr := w.Deployer.Deploy(ctx, out, builds, labellers); - if isErr, _ := dr.InError(); isErr { +func (w withNotification) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) *deploy.Result { + dr := w.Deployer.Deploy(ctx, out, builds, labellers) + if isErr, _ := dr.IsError(); isErr { fmt.Fprint(out, terminalBell) } return dr diff --git a/pkg/skaffold/runner/runcontext/context.go b/pkg/skaffold/runner/runcontext/context.go index f3a4f5b7ac2..e1184d5d7d3 100644 --- a/pkg/skaffold/runner/runcontext/context.go +++ b/pkg/skaffold/runner/runcontext/context.go @@ -87,7 +87,6 @@ func GetRunContext(opts config.SkaffoldOptions, cfg latest.Pipeline) (*RunContex }, nil } - func (r *RunContext) UpdateNamespaces(ns []string) { if len(ns) == 0 { return @@ -100,11 +99,11 @@ func (r *RunContext) UpdateNamespaces(ns []string) { // Update RunContext Namespace updated := make([]string, len(nsMap)) - i :=0 - for k, _ := range(nsMap) { + i := 0 + for k := range nsMap { updated[i] = k i++ } sort.Strings(updated) r.Namespaces = updated -} \ No newline at end of file +} diff --git a/pkg/skaffold/runner/runcontext/context_test.go b/pkg/skaffold/runner/runcontext/context_test.go index 5d2219cd2ff..5e1e9b15bbb 100644 --- a/pkg/skaffold/runner/runcontext/context_test.go +++ b/pkg/skaffold/runner/runcontext/context_test.go @@ -24,40 +24,40 @@ import ( func TestRunContext_UpdateNamespaces(t *testing.T) { tests := []struct { - description string - runContext *RunContext - namespaces []string - expected []string + description string + runContext *RunContext + namespaces []string + expected []string }{ { description: "update namespace when not present in runContext", - runContext: &RunContext{Namespaces: []string{"test"}}, - namespaces: []string{"another"}, - expected: []string{"another", "test"}, + runContext: &RunContext{Namespaces: []string{"test"}}, + namespaces: []string{"another"}, + expected: []string{"another", "test"}, }, { description: "update namespace with duplicates should not return duplicate", - runContext: &RunContext{Namespaces: []string{"test", "foo"}}, - namespaces: []string{"another", "foo", "another"}, - expected: []string{"another", "foo", "test"}, + runContext: &RunContext{Namespaces: []string{"test", "foo"}}, + namespaces: []string{"another", "foo", "another"}, + expected: []string{"another", "foo", "test"}, }, { - description: "update namespaces when namespaces is empty", - runContext: &RunContext{Namespaces: []string{"test", "foo"}}, - namespaces: []string{}, - expected: []string{"test", "foo"}, + description: "update namespaces when namespaces is empty", + runContext: &RunContext{Namespaces: []string{"test", "foo"}}, + namespaces: []string{}, + expected: []string{"test", "foo"}, }, { description: "update namespaces when runcontext namespaces is empty", - runContext: &RunContext{Namespaces: []string{}}, - namespaces: []string{"test", "another"}, - expected: []string{"another", "test"}, + runContext: &RunContext{Namespaces: []string{}}, + namespaces: []string{"test", "another"}, + expected: []string{"another", "test"}, }, { description: "update namespaces when both namespaces and runcontext namespaces is empty", - runContext: &RunContext{Namespaces: []string{}}, - namespaces: []string{}, - expected: []string{}, + runContext: &RunContext{Namespaces: []string{}}, + namespaces: []string{}, + expected: []string{}, }, } for _, test := range tests { diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 666330b9227..c6679700ecf 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -85,7 +85,6 @@ func (t *TestBench) WithDeployNamespaces(ns []string) *TestBench { return t } - func (t *TestBench) WithTestErrors(testErrors []error) *TestBench { t.testErrors = testErrors return t @@ -158,7 +157,7 @@ func (t *TestBench) Test(_ context.Context, _ io.Writer, artifacts []build.Artif return nil } -func (t *TestBench) Deploy(_ context.Context, _ io.Writer, artifacts []build.Artifact, _ []deploy.Labeller) *deploy.DeployResult { +func (t *TestBench) Deploy(_ context.Context, _ io.Writer, artifacts []build.Artifact, _ []deploy.Labeller) *deploy.Result { if len(t.deployErrors) > 0 { err := t.deployErrors[0] t.deployErrors = t.deployErrors[1:] diff --git a/pkg/skaffold/runner/timings.go b/pkg/skaffold/runner/timings.go index fa09e41d1e7..abfe0fec2e2 100644 --- a/pkg/skaffold/runner/timings.go +++ b/pkg/skaffold/runner/timings.go @@ -82,12 +82,12 @@ func (w withTimings) Test(ctx context.Context, out io.Writer, builds []build.Art return nil } -func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) *deploy.DeployResult { +func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) *deploy.Result { start := time.Now() color.Default.Fprintln(out, "Starting deploy...") dr := w.Deployer.Deploy(ctx, out, builds, labellers) - if isErr, _ := dr.InError(); !isErr { + if isErr, _ := dr.IsError(); !isErr { color.Default.Fprintln(out, "Deploy complete in", time.Since(start)) } return dr diff --git a/vendor/modules.txt b/vendor/modules.txt index 6cd739e7e44..451f0a469d4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -540,9 +540,9 @@ k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/pkg/version k8s.io/apimachinery/pkg/runtime/serializer/streaming k8s.io/apimachinery/pkg/runtime/serializer/versioning -k8s.io/apimachinery/pkg/api/equality -k8s.io/apimachinery/pkg/util/validation k8s.io/apimachinery/pkg/util/validation/field +k8s.io/apimachinery/pkg/util/validation +k8s.io/apimachinery/pkg/api/equality k8s.io/apimachinery/third_party/forked/golang/reflect k8s.io/apimachinery/pkg/util/clock k8s.io/apimachinery/pkg/runtime/serializer/protobuf @@ -666,10 +666,10 @@ k8s.io/client-go/util/keyutil k8s.io/client-go/tools/clientcmd/api/v1 k8s.io/client-go/transport/spdy k8s.io/client-go/util/exec +k8s.io/client-go/third_party/forked/golang/template k8s.io/client-go/informers k8s.io/client-go/informers/core/v1 k8s.io/client-go/tools/cache -k8s.io/client-go/third_party/forked/golang/template k8s.io/client-go/informers/admissionregistration k8s.io/client-go/informers/apps k8s.io/client-go/informers/auditregistration From 7ae27c75e5d456f8c5a55a52d87a84b904e6145a Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Mon, 12 Aug 2019 16:28:28 -0700 Subject: [PATCH 4/7] fix typo --- pkg/skaffold/event/event.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/skaffold/event/event.go b/pkg/skaffold/event/event.go index b767a691e3d..8b2bc7c4d9c 100644 --- a/pkg/skaffold/event/event.go +++ b/pkg/skaffold/event/event.go @@ -155,7 +155,7 @@ func DeployFailed(err error) { handler.handleDeployEvent(&proto.DeployEvent{Status: Failed, Err: err.Error()}) } -// DeployEvent notifies that a deployment of non fatal errors during deploy w +// DeployEvent notifies that a deployment of non fatal interesting errors during deploy. func DeployInfoEvent(err error) { handler.handleDeployEvent(&proto.DeployEvent{Status: Info, Err: err.Error()}) } From adcfede6efddbd50e3474dc770ea7b4aac046aee Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Mon, 12 Aug 2019 16:50:23 -0700 Subject: [PATCH 5/7] code review comments --- pkg/skaffold/deploy/helm.go | 8 +++----- pkg/skaffold/deploy/kubectl.go | 6 +++--- pkg/skaffold/deploy/kubectl/namespaces.go | 7 +++---- pkg/skaffold/event/event.go | 2 +- pkg/skaffold/runner/notification.go | 2 +- pkg/skaffold/runner/runcontext/context.go | 6 ++---- 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index b1e03dea9ca..7e7df957f7e 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -99,11 +99,9 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build labelDeployResults(labels, dRes) // Collect namespaces in a string - namespaces := make([]string, len(nsMap)) - i := 0 - for k := range nsMap { - namespaces[i] = k - i++ + namespaces := make([]string, 0, len(nsMap)) + for ns := range nsMap { + namespaces = append(namespaces, ns) } return NewDeploySuccessResult(namespaces) diff --git a/pkg/skaffold/deploy/kubectl.go b/pkg/skaffold/deploy/kubectl.go index 18981767950..a9ad476934c 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 for _, m := range k.RemoteManifests { manifest, err := k.readRemoteManifest(ctx, m) if err != nil { - return errors.Wrap(err, "get remote manifests") + return NewDeployErrorResult(errors.Wrap(err, "get remote manifests")) } manifests = append(manifests, manifest) @@ -95,7 +95,7 @@ func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []bu if len(k.originalImages) == 0 { k.originalImages, err = manifests.GetImages() if err != nil { - return errors.Wrap(err, "get images from manifests") + return NewDeployErrorResult(errors.Wrap(err, "get images from manifests")) } } @@ -109,7 +109,7 @@ func (k *KubectlDeployer) Deploy(ctx context.Context, out io.Writer, builds []bu namespaces, err := manifests.CollectNamespaces() if err != nil { - event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace."+ + event.DeployInfoEvent(errors.Wrap(err, "could not fetch deployed resource namespace. "+ "This might cause port-forward and deploy health-check to fail.")) } diff --git a/pkg/skaffold/deploy/kubectl/namespaces.go b/pkg/skaffold/deploy/kubectl/namespaces.go index 0f978e97e2a..c94070708ad 100644 --- a/pkg/skaffold/deploy/kubectl/namespaces.go +++ b/pkg/skaffold/deploy/kubectl/namespaces.go @@ -30,11 +30,10 @@ func (l *ManifestList) CollectNamespaces() ([]string, error) { if _, err := l.Visit(replacer); err != nil { return nil, errors.Wrap(err, "collecting namespaces") } - namespaces := make([]string, len(replacer.namespaces)) - i := 0 + + namespaces := make([]string, 0, len(replacer.namespaces)) for ns := range replacer.namespaces { - namespaces[i] = ns - i++ + namespaces = append(namespaces, ns) } sort.Strings(namespaces) return namespaces, nil diff --git a/pkg/skaffold/event/event.go b/pkg/skaffold/event/event.go index 8b2bc7c4d9c..0a34995ba92 100644 --- a/pkg/skaffold/event/event.go +++ b/pkg/skaffold/event/event.go @@ -150,7 +150,7 @@ func DeployInProgress() { handler.handleDeployEvent(&proto.DeployEvent{Status: InProgress}) } -// DeployFailed notifies that a deployment has failed. +// DeployFailed notifies that non-fatal errors were encountered during a deployment. func DeployFailed(err error) { handler.handleDeployEvent(&proto.DeployEvent{Status: Failed, Err: err.Error()}) } diff --git a/pkg/skaffold/runner/notification.go b/pkg/skaffold/runner/notification.go index 37d49921bb3..057124429f7 100644 --- a/pkg/skaffold/runner/notification.go +++ b/pkg/skaffold/runner/notification.go @@ -43,7 +43,7 @@ type withNotification struct { func (w withNotification) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) *deploy.Result { dr := w.Deployer.Deploy(ctx, out, builds, labellers) - if isErr, _ := dr.IsError(); isErr { + if isErr, _ := dr.IsError(); !isErr { fmt.Fprint(out, terminalBell) } return dr diff --git a/pkg/skaffold/runner/runcontext/context.go b/pkg/skaffold/runner/runcontext/context.go index e1184d5d7d3..945fa03fc85 100644 --- a/pkg/skaffold/runner/runcontext/context.go +++ b/pkg/skaffold/runner/runcontext/context.go @@ -98,11 +98,9 @@ func (r *RunContext) UpdateNamespaces(ns []string) { } // Update RunContext Namespace - updated := make([]string, len(nsMap)) - i := 0 + updated := make([]string, 0, len(nsMap)) for k := range nsMap { - updated[i] = k - i++ + updated = append(updated, k) } sort.Strings(updated) r.Namespaces = updated From 86e8416cb8c7cf3ee8e93bdf3a148bd3f3be26ea Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Wed, 14 Aug 2019 09:43:58 -0700 Subject: [PATCH 6/7] fix namespaces --- go.mod | 3 +- go.sum | 2 + pkg/skaffold/deploy/helm.go | 4 +- pkg/skaffold/deploy/kubectl/namespaces.go | 2 +- pkg/skaffold/deploy/util.go | 32 ++- pkg/skaffold/deploy/util_test.go | 144 ++++++++++++ .../google/go-cmp/cmp/cmpopts/equate.go | 89 ++++++++ .../google/go-cmp/cmp/cmpopts/ignore.go | 207 ++++++++++++++++++ .../google/go-cmp/cmp/cmpopts/sort.go | 147 +++++++++++++ .../go-cmp/cmp/cmpopts/struct_filter.go | 182 +++++++++++++++ .../google/go-cmp/cmp/cmpopts/xform.go | 35 +++ .../google/go-cmp/cmp/internal/value/sort.go | 4 +- .../google/go-cmp/cmp/internal/value/zero.go | 9 +- .../google/go-cmp/cmp/report_compare.go | 2 +- .../google/go-cmp/cmp/report_reflect.go | 1 - .../google/go-cmp/cmp/report_slices.go | 4 +- .../google/go-cmp/cmp/report_text.go | 7 +- vendor/modules.txt | 9 +- 18 files changed, 862 insertions(+), 21 deletions(-) create mode 100644 pkg/skaffold/deploy/util_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go create mode 100644 vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go diff --git a/go.mod b/go.mod index 24fac4ce87c..ec7345fefe5 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/gogo/protobuf v1.1.1 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/protobuf v1.3.2 - github.com/google/go-cmp v0.3.0 + github.com/google/go-cmp v0.3.1 github.com/google/go-containerregistry v0.0.0-20190717132004-e8c6a4993fa7 github.com/google/go-github v17.0.0+incompatible github.com/google/go-querystring v1.0.0 // indirect @@ -79,6 +79,7 @@ require ( k8s.io/apimachinery v0.0.0-20190620073744-d16981aedf33 k8s.io/client-go v0.0.0-20190620074045-585a16d2e773 k8s.io/kubectl v0.0.0-20190622051205-955b067cc6d3 + k8s.io/utils v0.0.0-20190221042446-c2654d5206da knative.dev/pkg v0.0.0-20190730155243-972acd413fb9 // indirect ) diff --git a/go.sum b/go.sum index 3ba0dd54b66..2df61a63646 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-containerregistry v0.0.0-20190717132004-e8c6a4993fa7 h1:Tx9G0xLagQdDraS6GdVgRLFOXWURkSnC8RbszNCqUUc= github.com/google/go-containerregistry v0.0.0-20190717132004-e8c6a4993fa7/go.mod h1:yZAFP63pRshzrEYLXLGPmUt0Ay+2zdjmMN1loCnRLUk= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index 7e7df957f7e..79eddf8cee5 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -87,7 +87,9 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build } // collect namespaces for _, r := range results { - nsMap[r.Namespace] = true + if trimmed := strings.TrimSpace(r.Namespace); trimmed != "" { + nsMap[trimmed] = true + } } dRes = append(dRes, results...) diff --git a/pkg/skaffold/deploy/kubectl/namespaces.go b/pkg/skaffold/deploy/kubectl/namespaces.go index c94070708ad..71949cccc15 100644 --- a/pkg/skaffold/deploy/kubectl/namespaces.go +++ b/pkg/skaffold/deploy/kubectl/namespaces.go @@ -30,7 +30,7 @@ func (l *ManifestList) CollectNamespaces() ([]string, error) { if _, err := l.Visit(replacer); err != nil { return nil, errors.Wrap(err, "collecting namespaces") } - + namespaces := make([]string, 0, len(replacer.namespaces)) for ns := range replacer.namespaces { namespaces = append(namespaces, ns) diff --git a/pkg/skaffold/deploy/util.go b/pkg/skaffold/deploy/util.go index bca7da4d820..a86a44a63ab 100644 --- a/pkg/skaffold/deploy/util.go +++ b/pkg/skaffold/deploy/util.go @@ -18,6 +18,7 @@ package deploy import ( "bufio" + "bytes" "fmt" "io" @@ -25,15 +26,17 @@ import ( k8syaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/kubernetes/scheme" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl" ) -func parseRuntimeObject(namespace string, b []byte) (Artifact, error) { +func parseRuntimeObject(namespace string, b []byte) (*Artifact, error) { d := scheme.Codecs.UniversalDeserializer() obj, _, err := d.Decode(b, nil, nil) if err != nil { - return Artifact{}, fmt.Errorf("error decoding parsed yaml: %s", err.Error()) + return nil, fmt.Errorf("error decoding parsed yaml: %s", err.Error()) } - return Artifact{ + return &Artifact{ Obj: obj, Namespace: namespace, }, nil @@ -51,12 +54,31 @@ func parseReleaseInfo(namespace string, b *bufio.Reader) []Artifact { logrus.Infof("error parsing object from string: %s", err.Error()) continue } - obj, err := parseRuntimeObject(namespace, doc) + objNamespace, err := getObjectNamespaceIfDefined(doc, namespace) + if err != nil { + logrus.Infof("error parsing object from string: %s", err.Error()) + continue + } + obj, err := parseRuntimeObject(objNamespace, doc) if err != nil { logrus.Infof(err.Error()) } else { - results = append(results, obj) + results = append(results, *obj) } } return results } + +func getObjectNamespaceIfDefined(doc []byte, ns string) (string, error) { + if i := bytes.Index(doc, []byte("apiVersion")); i >= 0 { + manifests := kubectl.ManifestList{doc[i:]} + namespaces, err := manifests.CollectNamespaces() + if err != nil { + return ns, err + } + if len(namespaces) > 0 { + return namespaces[0], nil + } + } + return ns, nil +} diff --git a/pkg/skaffold/deploy/util_test.go b/pkg/skaffold/deploy/util_test.go new file mode 100644 index 00000000000..ac43699284c --- /dev/null +++ b/pkg/skaffold/deploy/util_test.go @@ -0,0 +1,144 @@ +/* +Copyright 2019 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 deploy + +import ( + "bufio" + "bytes" + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/GoogleContainerTools/skaffold/testutil" +) + +func TestParseReleaseInfo(t *testing.T) { + tests := []struct { + description string + yaml []byte + expected []Artifact + }{ + { + description: "parse valid release info yaml with single artifact with namespace", + yaml: []byte(`# Source: skaffold-helm/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: skaffold-helm-skaffold-helm + namespace: test + labels: + app: skaffold-helm + chart: skaffold-helm-0.1.0 + release: skaffold-helm + heritage: Tiller +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: nginx + selector: + app: skaffold-helm + release: skaffold-helm`), + expected: []Artifact{{Namespace: "test"}}, + }, + { + description: "parse valid release info yaml with single artifact without namespace sets helm namespace", + yaml: []byte(`# Source: skaffold-helm/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: skaffold-helm-skaffold-helm + labels: + app: skaffold-helm + chart: skaffold-helm-0.1.0 + release: skaffold-helm + heritage: Tiller +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: nginx + selector: + app: skaffold-helm + release: skaffold-helm`), + expected: []Artifact{{ + Namespace: "testNamespace", + }}, + }, + { + description: "parse valid release info yaml with multiple artifacts", + yaml: []byte(`# Source: skaffold-helm/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: skaffold-helm-skaffold-helm + labels: + app: skaffold-helm + chart: skaffold-helm-0.1.0 + release: skaffold-helm + heritage: Tiller +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: nginx + selector: + app: skaffold-helm + release: skaffold-helm +--- +# Source: skaffold-helm/templates/ingress.yaml +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: skaffold-helm-skaffold-helm + namespace: test + labels: + app: skaffold-helm + chart: skaffold-helm-0.1.0 + release: skaffold-helm + heritage: Tiller + annotations: +spec: + rules: + - http: + paths: + - path: / + backend: + serviceName: skaffold-helm-skaffold-helm + servicePort: 80`), + expected: []Artifact{{Namespace: "testNamespace"}, {Namespace: "test"}}, + }, + { + description: "parse invalid release info yaml", + yaml: []byte(`invalid release info`), + expected: []Artifact{}, + }, + } + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + r := bufio.NewReader(bytes.NewBuffer(test.yaml)) + actual := parseReleaseInfo(testNamespace, r) + t.CheckDeepEqual(test.expected, actual, cmpopts.IgnoreFields(Artifact{}, "Obj")) + }) + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go new file mode 100644 index 00000000000..41bbddc61b2 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go @@ -0,0 +1,89 @@ +// Copyright 2017, 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.md file. + +// Package cmpopts provides common options for the cmp package. +package cmpopts + +import ( + "math" + "reflect" + + "github.com/google/go-cmp/cmp" +) + +func equateAlways(_, _ interface{}) bool { return true } + +// EquateEmpty returns a Comparer option that determines all maps and slices +// with a length of zero to be equal, regardless of whether they are nil. +// +// EquateEmpty can be used in conjunction with SortSlices and SortMaps. +func EquateEmpty() cmp.Option { + return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways)) +} + +func isEmpty(x, y interface{}) bool { + vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) + return (x != nil && y != nil && vx.Type() == vy.Type()) && + (vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) && + (vx.Len() == 0 && vy.Len() == 0) +} + +// EquateApprox returns a Comparer option that determines float32 or float64 +// values to be equal if they are within a relative fraction or absolute margin. +// This option is not used when either x or y is NaN or infinite. +// +// The fraction determines that the difference of two values must be within the +// smaller fraction of the two values, while the margin determines that the two +// values must be within some absolute margin. +// To express only a fraction or only a margin, use 0 for the other parameter. +// The fraction and margin must be non-negative. +// +// The mathematical expression used is equivalent to: +// |x-y| ≤ max(fraction*min(|x|, |y|), margin) +// +// EquateApprox can be used in conjunction with EquateNaNs. +func EquateApprox(fraction, margin float64) cmp.Option { + if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) { + panic("margin or fraction must be a non-negative number") + } + a := approximator{fraction, margin} + return cmp.Options{ + cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)), + cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)), + } +} + +type approximator struct{ frac, marg float64 } + +func areRealF64s(x, y float64) bool { + return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0) +} +func areRealF32s(x, y float32) bool { + return areRealF64s(float64(x), float64(y)) +} +func (a approximator) compareF64(x, y float64) bool { + relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y)) + return math.Abs(x-y) <= math.Max(a.marg, relMarg) +} +func (a approximator) compareF32(x, y float32) bool { + return a.compareF64(float64(x), float64(y)) +} + +// EquateNaNs returns a Comparer option that determines float32 and float64 +// NaN values to be equal. +// +// EquateNaNs can be used in conjunction with EquateApprox. +func EquateNaNs() cmp.Option { + return cmp.Options{ + cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)), + cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)), + } +} + +func areNaNsF64s(x, y float64) bool { + return math.IsNaN(x) && math.IsNaN(y) +} +func areNaNsF32s(x, y float32) bool { + return areNaNsF64s(float64(x), float64(y)) +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go new file mode 100644 index 00000000000..ff8e785d4e8 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go @@ -0,0 +1,207 @@ +// Copyright 2017, 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.md file. + +package cmpopts + +import ( + "fmt" + "reflect" + "unicode" + "unicode/utf8" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/internal/function" +) + +// IgnoreFields returns an Option that ignores exported fields of the +// given names on a single struct type. +// The struct type is specified by passing in a value of that type. +// +// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a +// specific sub-field that is embedded or nested within the parent struct. +// +// This does not handle unexported fields; use IgnoreUnexported instead. +func IgnoreFields(typ interface{}, names ...string) cmp.Option { + sf := newStructFilter(typ, names...) + return cmp.FilterPath(sf.filter, cmp.Ignore()) +} + +// IgnoreTypes returns an Option that ignores all values assignable to +// certain types, which are specified by passing in a value of each type. +func IgnoreTypes(typs ...interface{}) cmp.Option { + tf := newTypeFilter(typs...) + return cmp.FilterPath(tf.filter, cmp.Ignore()) +} + +type typeFilter []reflect.Type + +func newTypeFilter(typs ...interface{}) (tf typeFilter) { + for _, typ := range typs { + t := reflect.TypeOf(typ) + if t == nil { + // This occurs if someone tries to pass in sync.Locker(nil) + panic("cannot determine type; consider using IgnoreInterfaces") + } + tf = append(tf, t) + } + return tf +} +func (tf typeFilter) filter(p cmp.Path) bool { + if len(p) < 1 { + return false + } + t := p.Last().Type() + for _, ti := range tf { + if t.AssignableTo(ti) { + return true + } + } + return false +} + +// IgnoreInterfaces returns an Option that ignores all values or references of +// values assignable to certain interface types. These interfaces are specified +// by passing in an anonymous struct with the interface types embedded in it. +// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}. +func IgnoreInterfaces(ifaces interface{}) cmp.Option { + tf := newIfaceFilter(ifaces) + return cmp.FilterPath(tf.filter, cmp.Ignore()) +} + +type ifaceFilter []reflect.Type + +func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) { + t := reflect.TypeOf(ifaces) + if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct { + panic("input must be an anonymous struct") + } + for i := 0; i < t.NumField(); i++ { + fi := t.Field(i) + switch { + case !fi.Anonymous: + panic("struct cannot have named fields") + case fi.Type.Kind() != reflect.Interface: + panic("embedded field must be an interface type") + case fi.Type.NumMethod() == 0: + // This matches everything; why would you ever want this? + panic("cannot ignore empty interface") + default: + tf = append(tf, fi.Type) + } + } + return tf +} +func (tf ifaceFilter) filter(p cmp.Path) bool { + if len(p) < 1 { + return false + } + t := p.Last().Type() + for _, ti := range tf { + if t.AssignableTo(ti) { + return true + } + if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) { + return true + } + } + return false +} + +// IgnoreUnexported returns an Option that only ignores the immediate unexported +// fields of a struct, including anonymous fields of unexported types. +// In particular, unexported fields within the struct's exported fields +// of struct types, including anonymous fields, will not be ignored unless the +// type of the field itself is also passed to IgnoreUnexported. +// +// Avoid ignoring unexported fields of a type which you do not control (i.e. a +// type from another repository), as changes to the implementation of such types +// may change how the comparison behaves. Prefer a custom Comparer instead. +func IgnoreUnexported(typs ...interface{}) cmp.Option { + ux := newUnexportedFilter(typs...) + return cmp.FilterPath(ux.filter, cmp.Ignore()) +} + +type unexportedFilter struct{ m map[reflect.Type]bool } + +func newUnexportedFilter(typs ...interface{}) unexportedFilter { + ux := unexportedFilter{m: make(map[reflect.Type]bool)} + for _, typ := range typs { + t := reflect.TypeOf(typ) + if t == nil || t.Kind() != reflect.Struct { + panic(fmt.Sprintf("invalid struct type: %T", typ)) + } + ux.m[t] = true + } + return ux +} +func (xf unexportedFilter) filter(p cmp.Path) bool { + sf, ok := p.Index(-1).(cmp.StructField) + if !ok { + return false + } + return xf.m[p.Index(-2).Type()] && !isExported(sf.Name()) +} + +// isExported reports whether the identifier is exported. +func isExported(id string) bool { + r, _ := utf8.DecodeRuneInString(id) + return unicode.IsUpper(r) +} + +// IgnoreSliceElements returns an Option that ignores elements of []V. +// The discard function must be of the form "func(T) bool" which is used to +// ignore slice elements of type V, where V is assignable to T. +// Elements are ignored if the function reports true. +func IgnoreSliceElements(discardFunc interface{}) cmp.Option { + vf := reflect.ValueOf(discardFunc) + if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() { + panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) + } + return cmp.FilterPath(func(p cmp.Path) bool { + si, ok := p.Index(-1).(cmp.SliceIndex) + if !ok { + return false + } + if !si.Type().AssignableTo(vf.Type().In(0)) { + return false + } + vx, vy := si.Values() + if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() { + return true + } + if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() { + return true + } + return false + }, cmp.Ignore()) +} + +// IgnoreMapEntries returns an Option that ignores entries of map[K]V. +// The discard function must be of the form "func(T, R) bool" which is used to +// ignore map entries of type K and V, where K and V are assignable to T and R. +// Entries are ignored if the function reports true. +func IgnoreMapEntries(discardFunc interface{}) cmp.Option { + vf := reflect.ValueOf(discardFunc) + if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() { + panic(fmt.Sprintf("invalid discard function: %T", discardFunc)) + } + return cmp.FilterPath(func(p cmp.Path) bool { + mi, ok := p.Index(-1).(cmp.MapIndex) + if !ok { + return false + } + if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) { + return false + } + k := mi.Key() + vx, vy := mi.Values() + if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() { + return true + } + if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() { + return true + } + return false + }, cmp.Ignore()) +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go new file mode 100644 index 00000000000..3a4804621e9 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go @@ -0,0 +1,147 @@ +// Copyright 2017, 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.md file. + +package cmpopts + +import ( + "fmt" + "reflect" + "sort" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/internal/function" +) + +// SortSlices returns a Transformer option that sorts all []V. +// The less function must be of the form "func(T, T) bool" which is used to +// sort any slice with element type V that is assignable to T. +// +// The less function must be: +// • Deterministic: less(x, y) == less(x, y) +// • Irreflexive: !less(x, x) +// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z) +// +// The less function does not have to be "total". That is, if !less(x, y) and +// !less(y, x) for two elements x and y, their relative order is maintained. +// +// SortSlices can be used in conjunction with EquateEmpty. +func SortSlices(lessFunc interface{}) cmp.Option { + vf := reflect.ValueOf(lessFunc) + if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { + panic(fmt.Sprintf("invalid less function: %T", lessFunc)) + } + ss := sliceSorter{vf.Type().In(0), vf} + return cmp.FilterValues(ss.filter, cmp.Transformer("cmpopts.SortSlices", ss.sort)) +} + +type sliceSorter struct { + in reflect.Type // T + fnc reflect.Value // func(T, T) bool +} + +func (ss sliceSorter) filter(x, y interface{}) bool { + vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) + if !(x != nil && y != nil && vx.Type() == vy.Type()) || + !(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) || + (vx.Len() <= 1 && vy.Len() <= 1) { + return false + } + // Check whether the slices are already sorted to avoid an infinite + // recursion cycle applying the same transform to itself. + ok1 := sort.SliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) }) + ok2 := sort.SliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) }) + return !ok1 || !ok2 +} +func (ss sliceSorter) sort(x interface{}) interface{} { + src := reflect.ValueOf(x) + dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len()) + for i := 0; i < src.Len(); i++ { + dst.Index(i).Set(src.Index(i)) + } + sort.SliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) }) + ss.checkSort(dst) + return dst.Interface() +} +func (ss sliceSorter) checkSort(v reflect.Value) { + start := -1 // Start of a sequence of equal elements. + for i := 1; i < v.Len(); i++ { + if ss.less(v, i-1, i) { + // Check that first and last elements in v[start:i] are equal. + if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) { + panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i))) + } + start = -1 + } else if start == -1 { + start = i + } + } +} +func (ss sliceSorter) less(v reflect.Value, i, j int) bool { + vx, vy := v.Index(i), v.Index(j) + return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool() +} + +// SortMaps returns a Transformer option that flattens map[K]V types to be a +// sorted []struct{K, V}. The less function must be of the form +// "func(T, T) bool" which is used to sort any map with key K that is +// assignable to T. +// +// Flattening the map into a slice has the property that cmp.Equal is able to +// use Comparers on K or the K.Equal method if it exists. +// +// The less function must be: +// • Deterministic: less(x, y) == less(x, y) +// • Irreflexive: !less(x, x) +// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z) +// • Total: if x != y, then either less(x, y) or less(y, x) +// +// SortMaps can be used in conjunction with EquateEmpty. +func SortMaps(lessFunc interface{}) cmp.Option { + vf := reflect.ValueOf(lessFunc) + if !function.IsType(vf.Type(), function.Less) || vf.IsNil() { + panic(fmt.Sprintf("invalid less function: %T", lessFunc)) + } + ms := mapSorter{vf.Type().In(0), vf} + return cmp.FilterValues(ms.filter, cmp.Transformer("cmpopts.SortMaps", ms.sort)) +} + +type mapSorter struct { + in reflect.Type // T + fnc reflect.Value // func(T, T) bool +} + +func (ms mapSorter) filter(x, y interface{}) bool { + vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) + return (x != nil && y != nil && vx.Type() == vy.Type()) && + (vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) && + (vx.Len() != 0 || vy.Len() != 0) +} +func (ms mapSorter) sort(x interface{}) interface{} { + src := reflect.ValueOf(x) + outType := reflect.StructOf([]reflect.StructField{ + {Name: "K", Type: src.Type().Key()}, + {Name: "V", Type: src.Type().Elem()}, + }) + dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len()) + for i, k := range src.MapKeys() { + v := reflect.New(outType).Elem() + v.Field(0).Set(k) + v.Field(1).Set(src.MapIndex(k)) + dst.Index(i).Set(v) + } + sort.Slice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) }) + ms.checkSort(dst) + return dst.Interface() +} +func (ms mapSorter) checkSort(v reflect.Value) { + for i := 1; i < v.Len(); i++ { + if !ms.less(v, i-1, i) { + panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i))) + } + } +} +func (ms mapSorter) less(v reflect.Value, i, j int) bool { + vx, vy := v.Index(i).Field(0), v.Index(j).Field(0) + return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool() +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go new file mode 100644 index 00000000000..97f707983c0 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go @@ -0,0 +1,182 @@ +// Copyright 2017, 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.md file. + +package cmpopts + +import ( + "fmt" + "reflect" + "strings" + + "github.com/google/go-cmp/cmp" +) + +// filterField returns a new Option where opt is only evaluated on paths that +// include a specific exported field on a single struct type. +// The struct type is specified by passing in a value of that type. +// +// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a +// specific sub-field that is embedded or nested within the parent struct. +func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option { + // TODO: This is currently unexported over concerns of how helper filters + // can be composed together easily. + // TODO: Add tests for FilterField. + + sf := newStructFilter(typ, name) + return cmp.FilterPath(sf.filter, opt) +} + +type structFilter struct { + t reflect.Type // The root struct type to match on + ft fieldTree // Tree of fields to match on +} + +func newStructFilter(typ interface{}, names ...string) structFilter { + // TODO: Perhaps allow * as a special identifier to allow ignoring any + // number of path steps until the next field match? + // This could be useful when a concrete struct gets transformed into + // an anonymous struct where it is not possible to specify that by type, + // but the transformer happens to provide guarantees about the names of + // the transformed fields. + + t := reflect.TypeOf(typ) + if t == nil || t.Kind() != reflect.Struct { + panic(fmt.Sprintf("%T must be a struct", typ)) + } + var ft fieldTree + for _, name := range names { + cname, err := canonicalName(t, name) + if err != nil { + panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err)) + } + ft.insert(cname) + } + return structFilter{t, ft} +} + +func (sf structFilter) filter(p cmp.Path) bool { + for i, ps := range p { + if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) { + return true + } + } + return false +} + +// fieldTree represents a set of dot-separated identifiers. +// +// For example, inserting the following selectors: +// Foo +// Foo.Bar.Baz +// Foo.Buzz +// Nuka.Cola.Quantum +// +// Results in a tree of the form: +// {sub: { +// "Foo": {ok: true, sub: { +// "Bar": {sub: { +// "Baz": {ok: true}, +// }}, +// "Buzz": {ok: true}, +// }}, +// "Nuka": {sub: { +// "Cola": {sub: { +// "Quantum": {ok: true}, +// }}, +// }}, +// }} +type fieldTree struct { + ok bool // Whether this is a specified node + sub map[string]fieldTree // The sub-tree of fields under this node +} + +// insert inserts a sequence of field accesses into the tree. +func (ft *fieldTree) insert(cname []string) { + if ft.sub == nil { + ft.sub = make(map[string]fieldTree) + } + if len(cname) == 0 { + ft.ok = true + return + } + sub := ft.sub[cname[0]] + sub.insert(cname[1:]) + ft.sub[cname[0]] = sub +} + +// matchPrefix reports whether any selector in the fieldTree matches +// the start of path p. +func (ft fieldTree) matchPrefix(p cmp.Path) bool { + for _, ps := range p { + switch ps := ps.(type) { + case cmp.StructField: + ft = ft.sub[ps.Name()] + if ft.ok { + return true + } + if len(ft.sub) == 0 { + return false + } + case cmp.Indirect: + default: + return false + } + } + return false +} + +// canonicalName returns a list of identifiers where any struct field access +// through an embedded field is expanded to include the names of the embedded +// types themselves. +// +// For example, suppose field "Foo" is not directly in the parent struct, +// but actually from an embedded struct of type "Bar". Then, the canonical name +// of "Foo" is actually "Bar.Foo". +// +// Suppose field "Foo" is not directly in the parent struct, but actually +// a field in two different embedded structs of types "Bar" and "Baz". +// Then the selector "Foo" causes a panic since it is ambiguous which one it +// refers to. The user must specify either "Bar.Foo" or "Baz.Foo". +func canonicalName(t reflect.Type, sel string) ([]string, error) { + var name string + sel = strings.TrimPrefix(sel, ".") + if sel == "" { + return nil, fmt.Errorf("name must not be empty") + } + if i := strings.IndexByte(sel, '.'); i < 0 { + name, sel = sel, "" + } else { + name, sel = sel[:i], sel[i:] + } + + // Type must be a struct or pointer to struct. + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() != reflect.Struct { + return nil, fmt.Errorf("%v must be a struct", t) + } + + // Find the canonical name for this current field name. + // If the field exists in an embedded struct, then it will be expanded. + if !isExported(name) { + // Disallow unexported fields: + // * To discourage people from actually touching unexported fields + // * FieldByName is buggy (https://golang.org/issue/4876) + return []string{name}, fmt.Errorf("name must be exported") + } + sf, ok := t.FieldByName(name) + if !ok { + return []string{name}, fmt.Errorf("does not exist") + } + var ss []string + for i := range sf.Index { + ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name) + } + if sel == "" { + return ss, nil + } + ssPost, err := canonicalName(sf.Type, sel) + return append(ss, ssPost...), err +} diff --git a/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go b/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go new file mode 100644 index 00000000000..9d651553d78 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/cmpopts/xform.go @@ -0,0 +1,35 @@ +// Copyright 2018, 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.md file. + +package cmpopts + +import ( + "github.com/google/go-cmp/cmp" +) + +type xformFilter struct{ xform cmp.Option } + +func (xf xformFilter) filter(p cmp.Path) bool { + for _, ps := range p { + if t, ok := ps.(cmp.Transform); ok && t.Option() == xf.xform { + return false + } + } + return true +} + +// AcyclicTransformer returns a Transformer with a filter applied that ensures +// that the transformer cannot be recursively applied upon its own output. +// +// An example use case is a transformer that splits a string by lines: +// AcyclicTransformer("SplitLines", func(s string) []string{ +// return strings.Split(s, "\n") +// }) +// +// Had this been an unfiltered Transformer instead, this would result in an +// infinite cycle converting a string to []string to [][]string and so on. +func AcyclicTransformer(name string, xformFunc interface{}) cmp.Option { + xf := xformFilter{cmp.Transformer(name, xformFunc)} + return cmp.FilterPath(xf.filter, xf.xform) +} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go b/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go index 938f646f000..24fbae6e3c5 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go @@ -19,7 +19,7 @@ func SortKeys(vs []reflect.Value) []reflect.Value { } // Sort the map keys. - sort.Slice(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) }) + sort.SliceStable(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) }) // Deduplicate keys (fails for NaNs). vs2 := vs[:1] @@ -42,6 +42,8 @@ func isLess(x, y reflect.Value) bool { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return x.Uint() < y.Uint() case reflect.Float32, reflect.Float64: + // NOTE: This does not sort -0 as less than +0 + // since Go maps treat -0 and +0 as equal keys. fx, fy := x.Float(), y.Float() return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy) case reflect.Complex64, reflect.Complex128: diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go b/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go index d13a12ccfcd..06a8ffd036d 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go @@ -4,7 +4,10 @@ package value -import "reflect" +import ( + "math" + "reflect" +) // IsZero reports whether v is the zero value. // This does not rely on Interface and so can be used on unexported fields. @@ -17,9 +20,9 @@ func IsZero(v reflect.Value) bool { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: - return v.Float() == 0 + return math.Float64bits(v.Float()) == 0 case reflect.Complex64, reflect.Complex128: - return v.Complex() == 0 + return math.Float64bits(real(v.Complex())) == 0 && math.Float64bits(imag(v.Complex())) == 0 case reflect.String: return v.String() == "" case reflect.UnsafePointer: diff --git a/vendor/github.com/google/go-cmp/cmp/report_compare.go b/vendor/github.com/google/go-cmp/cmp/report_compare.go index 05efb992c53..17a05eede48 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_compare.go +++ b/vendor/github.com/google/go-cmp/cmp/report_compare.go @@ -168,7 +168,7 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind) te var isZero bool switch opts.DiffMode { case diffIdentical: - isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueX) + isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY) case diffRemoved: isZero = value.IsZero(r.Value.ValueX) case diffInserted: diff --git a/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/vendor/github.com/google/go-cmp/cmp/report_reflect.go index 5521c604c54..2761b628921 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_reflect.go +++ b/vendor/github.com/google/go-cmp/cmp/report_reflect.go @@ -208,7 +208,6 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t func formatMapKey(v reflect.Value) string { var opts formatOptions opts.TypeMode = elideType - opts.AvoidStringer = true opts.ShallowPointers = true s := opts.FormatValue(v, visitedPointers{}).String() return strings.TrimSpace(s) diff --git a/vendor/github.com/google/go-cmp/cmp/report_slices.go b/vendor/github.com/google/go-cmp/cmp/report_slices.go index 8cb3265e767..eafcf2e4c0b 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_slices.go +++ b/vendor/github.com/google/go-cmp/cmp/report_slices.go @@ -90,7 +90,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { } if r == '\n' { if maxLineLen < i-lastLineIdx { - lastLineIdx = i - lastLineIdx + maxLineLen = i - lastLineIdx } lastLineIdx = i + 1 numLines++ @@ -322,7 +322,7 @@ func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStat hadX, hadY := prev.NumRemoved > 0, prev.NumInserted > 0 hasX, hasY := next.NumRemoved > 0, next.NumInserted > 0 if ((hadX || hasX) && (hadY || hasY)) && curr.NumIdentical <= windowSize { - *prev = (*prev).Append(*curr).Append(*next) + *prev = prev.Append(*curr).Append(*next) groups = groups[:len(groups)-1] // Truncate off equal group continue } diff --git a/vendor/github.com/google/go-cmp/cmp/report_text.go b/vendor/github.com/google/go-cmp/cmp/report_text.go index 80605d0e440..8b8fcab7bdf 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_text.go +++ b/vendor/github.com/google/go-cmp/cmp/report_text.go @@ -19,6 +19,11 @@ var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0 type indentMode int func (n indentMode) appendIndent(b []byte, d diffMode) []byte { + // The output of Diff is documented as being unstable to provide future + // flexibility in changing the output for more humanly readable reports. + // This logic intentionally introduces instability to the exact output + // so that users can detect accidental reliance on stability early on, + // rather than much later when an actual change to the format occurs. if flags.Deterministic || randBool { // Use regular spaces (U+0020). switch d { @@ -360,7 +365,7 @@ func (s diffStats) String() string { // Pluralize the name (adjusting for some obscure English grammar rules). name := s.Name if sum > 1 { - name = name + "s" + name += "s" if strings.HasSuffix(name, "ys") { name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries" } diff --git a/vendor/modules.txt b/vendor/modules.txt index 451f0a469d4..db126c8f690 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -132,8 +132,9 @@ github.com/golang/protobuf/protoc-gen-go/descriptor github.com/golang/protobuf/ptypes/struct github.com/golang/protobuf/protoc-gen-go/generator/internal/remap github.com/golang/protobuf/protoc-gen-go/plugin -# github.com/google/go-cmp v0.3.0 +# github.com/google/go-cmp v0.3.1 github.com/google/go-cmp/cmp +github.com/google/go-cmp/cmp/cmpopts github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function @@ -540,9 +541,9 @@ k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/pkg/version k8s.io/apimachinery/pkg/runtime/serializer/streaming k8s.io/apimachinery/pkg/runtime/serializer/versioning -k8s.io/apimachinery/pkg/util/validation/field -k8s.io/apimachinery/pkg/util/validation k8s.io/apimachinery/pkg/api/equality +k8s.io/apimachinery/pkg/util/validation +k8s.io/apimachinery/pkg/util/validation/field k8s.io/apimachinery/third_party/forked/golang/reflect k8s.io/apimachinery/pkg/util/clock k8s.io/apimachinery/pkg/runtime/serializer/protobuf @@ -666,10 +667,10 @@ k8s.io/client-go/util/keyutil k8s.io/client-go/tools/clientcmd/api/v1 k8s.io/client-go/transport/spdy k8s.io/client-go/util/exec -k8s.io/client-go/third_party/forked/golang/template k8s.io/client-go/informers k8s.io/client-go/informers/core/v1 k8s.io/client-go/tools/cache +k8s.io/client-go/third_party/forked/golang/template k8s.io/client-go/informers/admissionregistration k8s.io/client-go/informers/apps k8s.io/client-go/informers/auditregistration From 1555157586a61f847259263c6ef0172c24f25618 Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Wed, 14 Aug 2019 13:03:32 -0700 Subject: [PATCH 7/7] nits --- pkg/skaffold/deploy/deploy.go | 4 ---- pkg/skaffold/deploy/helm.go | 4 ++-- pkg/skaffold/runner/deploy.go | 2 +- pkg/skaffold/runner/notification.go | 2 +- pkg/skaffold/runner/timings.go | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pkg/skaffold/deploy/deploy.go b/pkg/skaffold/deploy/deploy.go index f0e8fae42ee..47d7d93d8bd 100644 --- a/pkg/skaffold/deploy/deploy.go +++ b/pkg/skaffold/deploy/deploy.go @@ -57,10 +57,6 @@ func (d *Result) Namespaces() []string { return d.namespaces } -func (d *Result) IsError() (bool, error) { - return d.err != nil, d.err -} - func (d *Result) GetError() error { return d.err } diff --git a/pkg/skaffold/deploy/helm.go b/pkg/skaffold/deploy/helm.go index 79eddf8cee5..bb8f76f5bbd 100644 --- a/pkg/skaffold/deploy/helm.go +++ b/pkg/skaffold/deploy/helm.go @@ -75,7 +75,7 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build var dRes []Artifact event.DeployInProgress() - nsMap := map[string]bool{} + nsMap := map[string]struct{}{} for _, r := range h.Releases { results, err := h.deployRelease(ctx, out, r, builds) @@ -88,7 +88,7 @@ func (h *HelmDeployer) Deploy(ctx context.Context, out io.Writer, builds []build // collect namespaces for _, r := range results { if trimmed := strings.TrimSpace(r.Namespace); trimmed != "" { - nsMap[trimmed] = true + nsMap[trimmed] = struct{}{} } } diff --git a/pkg/skaffold/runner/deploy.go b/pkg/skaffold/runner/deploy.go index 51b68428b09..213387c6318 100644 --- a/pkg/skaffold/runner/deploy.go +++ b/pkg/skaffold/runner/deploy.go @@ -36,7 +36,7 @@ func (r *SkaffoldRunner) Deploy(ctx context.Context, out io.Writer, artifacts [] deployResult := r.deployer.Deploy(ctx, out, artifacts, r.labellers) r.hasDeployed = true - if isErr, err := deployResult.IsError(); isErr { + if err := deployResult.GetError(); err != nil { return err } r.runCtx.UpdateNamespaces(deployResult.Namespaces()) diff --git a/pkg/skaffold/runner/notification.go b/pkg/skaffold/runner/notification.go index 057124429f7..aac4e3584da 100644 --- a/pkg/skaffold/runner/notification.go +++ b/pkg/skaffold/runner/notification.go @@ -43,7 +43,7 @@ type withNotification struct { func (w withNotification) Deploy(ctx context.Context, out io.Writer, builds []build.Artifact, labellers []deploy.Labeller) *deploy.Result { dr := w.Deployer.Deploy(ctx, out, builds, labellers) - if isErr, _ := dr.IsError(); !isErr { + if err := dr.GetError(); err != nil { fmt.Fprint(out, terminalBell) } return dr diff --git a/pkg/skaffold/runner/timings.go b/pkg/skaffold/runner/timings.go index abfe0fec2e2..795fb38efed 100644 --- a/pkg/skaffold/runner/timings.go +++ b/pkg/skaffold/runner/timings.go @@ -87,7 +87,7 @@ func (w withTimings) Deploy(ctx context.Context, out io.Writer, builds []build.A color.Default.Fprintln(out, "Starting deploy...") dr := w.Deployer.Deploy(ctx, out, builds, labellers) - if isErr, _ := dr.IsError(); !isErr { + if err := dr.GetError(); err != nil { color.Default.Fprintln(out, "Deploy complete in", time.Since(start)) } return dr