From 107ead78ce0545a858738f07f188a380d63e603d Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Sun, 13 Aug 2017 10:22:03 -0700 Subject: [PATCH] Implement kind visitor library for kubectl - Will be used to replace "Kind" switch statements - Allow for removal of go library dependency on kubernetes/kubernetes unversioned api packages - Better support for ensuring all types are accounted for --- pkg/kubectl/BUILD | 1 + pkg/kubectl/apps/BUILD | 42 ++++++ pkg/kubectl/apps/apps_suite_test.go | 29 ++++ pkg/kubectl/apps/kind_visitor.go | 95 +++++++++++++ pkg/kubectl/apps/kind_visitor_test.go | 190 ++++++++++++++++++++++++++ 5 files changed, 357 insertions(+) create mode 100644 pkg/kubectl/apps/BUILD create mode 100644 pkg/kubectl/apps/apps_suite_test.go create mode 100644 pkg/kubectl/apps/kind_visitor.go create mode 100644 pkg/kubectl/apps/kind_visitor_test.go diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index a408dad1de..5e2a4a9114 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -184,6 +184,7 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//pkg/kubectl/apps:all-srcs", "//pkg/kubectl/cmd:all-srcs", "//pkg/kubectl/metricsutil:all-srcs", "//pkg/kubectl/plugins:all-srcs", diff --git a/pkg/kubectl/apps/BUILD b/pkg/kubectl/apps/BUILD new file mode 100644 index 0000000000..0d9d988760 --- /dev/null +++ b/pkg/kubectl/apps/BUILD @@ -0,0 +1,42 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["kind_visitor.go"], + tags = ["automanaged"], + deps = ["//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +go_test( + name = "go_default_xtest", + srcs = [ + "apps_suite_test.go", + "kind_visitor_test.go", + ], + deps = [ + ":go_default_library", + "//vendor/github.com/onsi/ginkgo:go_default_library", + "//vendor/github.com/onsi/gomega:go_default_library", + ], +) diff --git a/pkg/kubectl/apps/apps_suite_test.go b/pkg/kubectl/apps/apps_suite_test.go new file mode 100644 index 0000000000..a2300f1e44 --- /dev/null +++ b/pkg/kubectl/apps/apps_suite_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2017 The Kubernetes 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 apps_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestApps(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Apps Suite") +} diff --git a/pkg/kubectl/apps/kind_visitor.go b/pkg/kubectl/apps/kind_visitor.go new file mode 100644 index 0000000000..98443c60a5 --- /dev/null +++ b/pkg/kubectl/apps/kind_visitor.go @@ -0,0 +1,95 @@ +/* +Copyright 2017 The Kubernetes 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 apps + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// KindVisitor is used with GroupKindElement to call a particular function depending on the +// Kind of a schema.GroupKind +type KindVisitor interface { + VisitDaemonSet(kind GroupKindElement) + VisitDeployment(kind GroupKindElement) + VisitJob(kind GroupKindElement) + VisitPod(kind GroupKindElement) + VisitReplicaSet(kind GroupKindElement) + VisitReplicationController(kind GroupKindElement) + VisitStatefulSet(kind GroupKindElement) +} + +// GroupKindElement defines a Kubernetes API group elem +type GroupKindElement schema.GroupKind + +// Accept calls the Visit method on visitor that corresponds to elem's Kind +func (elem GroupKindElement) Accept(visitor KindVisitor) error { + if elem.GroupMatch("apps", "extensions") && elem.Kind == "DaemonSet" { + visitor.VisitDaemonSet(elem) + return nil + } + if elem.GroupMatch("apps", "extensions") && elem.Kind == "Deployment" { + visitor.VisitDeployment(elem) + return nil + } + if elem.GroupMatch("apps", "extensions") && elem.Kind == "Job" { + visitor.VisitDeployment(elem) + return nil + } + if elem.GroupMatch("", "core") && elem.Kind == "Pod" { + visitor.VisitPod(elem) + return nil + } + if elem.GroupMatch("apps", "extensions") && elem.Kind == "ReplicaSet" { + visitor.VisitReplicationController(elem) + return nil + } + if elem.GroupMatch("", "core") && elem.Kind == "ReplicationController" { + visitor.VisitReplicationController(elem) + return nil + } + if elem.GroupMatch("apps") && elem.Kind == "StatefulSet" { + visitor.VisitStatefulSet(elem) + return nil + } + return fmt.Errorf("no visitor method exists for %v", elem) +} + +// GroupMatch returns true if and only if elem's group matches one +// of the group arguments +func (elem GroupKindElement) GroupMatch(groups ...string) bool { + for _, g := range groups { + if elem.Group == g { + return true + } + } + return false +} + +// DefaultKindVisitor implements KindVisitor with no-op functions. +type DefaultKindVisitor struct{} + +var _ KindVisitor = &DefaultKindVisitor{} + +func (*DefaultKindVisitor) VisitDaemonSet(kind GroupKindElement) {} +func (*DefaultKindVisitor) VisitDeployment(kind GroupKindElement) {} +func (*DefaultKindVisitor) VisitJob(kind GroupKindElement) {} +func (*DefaultKindVisitor) VisitPod(kind GroupKindElement) {} +func (*DefaultKindVisitor) VisitReplicaSet(kind GroupKindElement) {} +func (*DefaultKindVisitor) VisitReplicationController(kind GroupKindElement) {} +func (*DefaultKindVisitor) VisitStatefulSet(kind GroupKindElement) {} diff --git a/pkg/kubectl/apps/kind_visitor_test.go b/pkg/kubectl/apps/kind_visitor_test.go new file mode 100644 index 0000000000..7d392b0491 --- /dev/null +++ b/pkg/kubectl/apps/kind_visitor_test.go @@ -0,0 +1,190 @@ +/* +Copyright 2017 The Kubernetes 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 apps_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "k8s.io/kubernetes/pkg/kubectl/apps" +) + +var _ = Describe("When KindVisitor accepts a GroupKind", func() { + + var visitor *TestKindVisitor + + BeforeEach(func() { + visitor = &TestKindVisitor{map[string]int{}} + }) + + It("should Visit DaemonSet iff the Kind is a DaemonSet", func() { + kind := apps.GroupKindElement{ + Kind: "DaemonSet", + Group: "apps", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "DaemonSet": 1, + })) + + kind = apps.GroupKindElement{ + Kind: "DaemonSet", + Group: "extensions", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "DaemonSet": 2, + })) + }) + + It("should Visit Deployment iff the Kind is a Deployment", func() { + kind := apps.GroupKindElement{ + Kind: "Deployment", + Group: "apps", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "Deployment": 1, + })) + + kind = apps.GroupKindElement{ + Kind: "Deployment", + Group: "extensions", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "Deployment": 2, + })) + }) + + It("should Visit Job iff the Kind is a Job", func() { + kind := apps.GroupKindElement{ + Kind: "Job", + Group: "apps", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "Job": 1, + })) + + kind = apps.GroupKindElement{ + Kind: "Job", + Group: "extensions", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "Job": 2, + })) + }) + + It("should Visit Pod iff the Kind is a Pod", func() { + kind := apps.GroupKindElement{ + Kind: "Pod", + Group: "", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "Pod": 1, + })) + + kind = apps.GroupKindElement{ + Kind: "Pod", + Group: "core", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "Pod": 2, + })) + }) + + It("should Visit ReplicationController iff the Kind is a ReplicationController", func() { + kind := apps.GroupKindElement{ + Kind: "ReplicationController", + Group: "", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "ReplicationController": 1, + })) + + kind = apps.GroupKindElement{ + Kind: "ReplicationController", + Group: "core", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "ReplicationController": 2, + })) + }) + + It("should Visit ReplicaSet iff the Kind is a ReplicaSet", func() { + kind := apps.GroupKindElement{ + Kind: "ReplicaSet", + Group: "apps", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "ReplicaSet": 1, + })) + + kind = apps.GroupKindElement{ + Kind: "ReplicaSet", + Group: "extensions", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "ReplicaSet": 2, + })) + }) + + It("should Visit StatefulSet iff the Kind is a StatefulSet", func() { + kind := apps.GroupKindElement{ + Kind: "StatefulSet", + Group: "apps", + } + Expect(kind.Accept(visitor)).ShouldNot(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{ + "StatefulSet": 1, + })) + }) + + It("should give an error if the Kind is unknown", func() { + kind := apps.GroupKindElement{ + Kind: "Unknown", + Group: "apps", + } + Expect(kind.Accept(visitor)).Should(HaveOccurred()) + Expect(visitor.visits).To(Equal(map[string]int{})) + }) +}) + +// TestKindVisitor increments a value each time a Visit method was called +type TestKindVisitor struct { + visits map[string]int +} + +var _ apps.KindVisitor = &TestKindVisitor{} + +func (t *TestKindVisitor) Visit(kind apps.GroupKindElement) { t.visits[kind.Kind] += 1 } + +func (t *TestKindVisitor) VisitDaemonSet(kind apps.GroupKindElement) { t.Visit(kind) } +func (t *TestKindVisitor) VisitDeployment(kind apps.GroupKindElement) { t.Visit(kind) } +func (t *TestKindVisitor) VisitJob(kind apps.GroupKindElement) { t.Visit(kind) } +func (t *TestKindVisitor) VisitPod(kind apps.GroupKindElement) { t.Visit(kind) } +func (t *TestKindVisitor) VisitReplicaSet(kind apps.GroupKindElement) { t.Visit(kind) } +func (t *TestKindVisitor) VisitReplicationController(kind apps.GroupKindElement) { t.Visit(kind) } +func (t *TestKindVisitor) VisitStatefulSet(kind apps.GroupKindElement) { t.Visit(kind) }