From 02a0593df01488eea9080e1c729d394e8ed25cb2 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Tue, 11 Nov 2014 17:31:13 -0800 Subject: [PATCH] Add --watch to get command --- pkg/kubectl/cmd/get.go | 20 ++++++++++++-- pkg/kubectl/resource_printer.go | 12 ++++++--- pkg/kubectl/resthelper.go | 12 +++++++++ pkg/kubectl/watchloop.go | 47 +++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 pkg/kubectl/watchloop.go diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index fd0af00c6a8..45e7859d8ca 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -20,6 +20,7 @@ import ( "fmt" "io" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/spf13/cobra" @@ -50,7 +51,7 @@ Examples: mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, f.Mapper) selector := GetFlagString(cmd, "selector") - labels, err := labels.ParseSelector(selector) + labelSelector, err := labels.ParseSelector(selector) checkErr(err) client, err := f.Client(cmd, mapping) @@ -68,12 +69,26 @@ Examples: printer, err := kubectl.GetPrinter(outputVersion, outputFormat, templateFile, defaultPrinter) checkErr(err) - obj, err := kubectl.NewRESTHelper(client, mapping).Get(namespace, name, labels) + restHelper := kubectl.NewRESTHelper(client, mapping) + obj, err := restHelper.Get(namespace, name, labelSelector) checkErr(err) if err := printer.PrintObj(obj, out); err != nil { checkErr(fmt.Errorf("Unable to output the provided object: %v", err)) } + + if GetFlagBool(cmd, "watch") { + vi, err := latest.InterfacesFor(outputVersion) + checkErr(err) + + rv, err := vi.MetadataAccessor.ResourceVersion(obj) + checkErr(err) + + w, err := restHelper.Watch(namespace, rv, labelSelector, labels.Set{}.AsSelector()) + checkErr(err) + + kubectl.WatchLoop(w, printer, out) + } }, } cmd.Flags().StringP("output", "o", "", "Output format: json|yaml|template|templatefile") @@ -81,5 +96,6 @@ Examples: cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers") cmd.Flags().StringP("template", "t", "", "Template string or path to template file to use when --output=template or --output=templatefile") cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on") + cmd.Flags().BoolP("watch", "w", false, "After listing/getting the requested object, watch for changes.") return cmd } diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 399a7395c39..a39fa555458 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -154,10 +154,14 @@ type handlerEntry struct { printFunc reflect.Value } -// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide more elegant output. +// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide +// more elegant output. It is not threadsafe, but you may call PrintObj repeatedly; headers +// will only be printed if the object type changes. This makes it useful for printing items +// recieved from watches. type HumanReadablePrinter struct { handlerMap map[reflect.Type]*handlerEntry noHeaders bool + lastType reflect.Type } // IsVersioned returns false-- human readable printers do not make versioned output. @@ -348,9 +352,11 @@ func printEventList(list *api.EventList, w io.Writer) error { func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error { w := tabwriter.NewWriter(output, 20, 5, 3, ' ', 0) defer w.Flush() - if handler := h.handlerMap[reflect.TypeOf(obj)]; handler != nil { - if !h.noHeaders { + t := reflect.TypeOf(obj) + if handler := h.handlerMap[t]; handler != nil { + if !h.noHeaders && t != h.lastType { h.printHeader(handler.columns, w) + h.lastType = t } args := []reflect.Value{reflect.ValueOf(obj), reflect.ValueOf(w)} resultValue := handler.printFunc.Call(args)[0] diff --git a/pkg/kubectl/resthelper.go b/pkg/kubectl/resthelper.go index 48708263fa6..eb36daa2ad7 100644 --- a/pkg/kubectl/resthelper.go +++ b/pkg/kubectl/resthelper.go @@ -20,6 +20,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" ) // RESTHelper provides methods for retrieving or mutating a RESTful @@ -49,6 +50,17 @@ func (m *RESTHelper) Get(namespace, name string, selector labels.Selector) (runt return m.RESTClient.Get().Path(m.Resource).Namespace(namespace).Path(name).SelectorParam("labels", selector).Do().Get() } +func (m *RESTHelper) Watch(namespace, resourceVersion string, labelSelector, fieldSelector labels.Selector) (watch.Interface, error) { + return m.RESTClient.Get(). + Path("watch"). + Path(m.Resource). + Namespace(namespace). + Param("resourceVersion", resourceVersion). + SelectorParam("labels", labelSelector). + SelectorParam("fields", fieldSelector). + Watch() +} + func (m *RESTHelper) Delete(namespace, name string) error { return m.RESTClient.Delete().Path(m.Resource).Namespace(namespace).Path(name).Do().Error() } diff --git a/pkg/kubectl/watchloop.go b/pkg/kubectl/watchloop.go new file mode 100644 index 00000000000..2e6d3b9f88b --- /dev/null +++ b/pkg/kubectl/watchloop.go @@ -0,0 +1,47 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 ( + "io" + "os" + "os/signal" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" +) + +// WatchLoop loops, writing objects in the events from w to printer. +// If user sends interrupt signal, shut down cleanly. Otherwise, never return. +func WatchLoop(w watch.Interface, printer ResourcePrinter, out io.Writer) { + signals := make(chan os.Signal, 1) + signal.Notify(signals, os.Interrupt) + defer signal.Stop(signals) + for { + select { + case event, ok := <-w.ResultChan(): + if !ok { + return + } + // TODO: need to print out added/modified/deleted! + if err := printer.PrintObj(event.Object, out); err != nil { + w.Stop() + } + case <-signals: + w.Stop() + } + } +}