Unverified Commit baa0a3fc authored by Matt Farina's avatar Matt Farina Committed by GitHub
Browse files

Merge pull request #7785 from mattfarina/fix-6896

Restores display of object status when displaying helm status
Showing with 162 additions and 58 deletions
+162 -58
......@@ -23,6 +23,7 @@ import (
goerrors "errors"
"fmt"
"io"
"io/ioutil"
"log"
"sort"
"strings"
......@@ -42,6 +43,8 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
......@@ -51,13 +54,14 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
cachetools "k8s.io/client-go/tools/cache"
watchtools "k8s.io/client-go/tools/watch"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/validation"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/get"
)
......@@ -158,6 +162,40 @@ func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result,
return result, scrubValidationError(err)
}
// BuildUnstructuredTable reads Kubernetes objects and returns unstructured infos
// as a Table. This is meant for viewing resources and displaying them in a table.
// This is similar to BuildUnstructured but transforms the request for table
// display.
func (c *Client) BuildUnstructuredTable(namespace string, reader io.Reader) (Result, error) {
var result Result
result, err := c.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(namespace).
DefaultNamespace().
Stream(reader, "").
Flatten().
TransformRequests(transformRequests).
Do().Infos()
return result, scrubValidationError(err)
}
// This is used to retrieve a table view of the data. A table view is how kubectl
// retrieves the information Helm displays as resources in status. Note, table
// data is returned as a Table type that does not conform to the runtime.Object
// interface but is that type. So, you can't transform it into Go objects easily.
func transformRequests(req *rest.Request) {
// The request headers are for both the v1 and v1beta1 versions of the table
// as Kubernetes 1.14 and older used the beta version.
req.SetHeader("Accept", strings.Join([]string{
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName),
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName),
"application/json",
}, ","))
}
// Validate reads Kubernetes manifests and validates the content.
//
// This function does not actually do schema validation of manifests. Adding
......@@ -170,6 +208,7 @@ func (c *Client) Validate(namespace string, reader io.Reader) error {
DefaultNamespace().
// Schema(c.validator()). // No schema validation
Stream(reader, "").
Latest().
Flatten().
Do().Infos()
return scrubValidationError(err)
......@@ -199,7 +238,7 @@ func resourceInfoToObject(info *resource.Info, c *Client) runtime.Object {
return internalObj
}
func sortByKey(objs map[string](map[string]runtime.Object)) []string {
func sortByKey(objs map[string][]runtime.Object) []string {
var keys []string
// Create a simple slice, so we can sort it
for key := range objs {
......@@ -210,24 +249,79 @@ func sortByKey(objs map[string](map[string]runtime.Object)) []string {
return keys
}
// We have slices of tables that need to be sorted by name. In this case the
// self link is used so the sorting will include namespace and name.
func sortTableSlice(objs []runtime.Object) []runtime.Object {
// If there are 0 or 1 objects to sort there is nothing to sort so
// the list can be returned
if len(objs) < 2 {
return objs
}
ntbl := &metav1.Table{}
unstr, ok := objs[0].(*unstructured.Unstructured)
if !ok {
return objs
}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, ntbl); err != nil {
return objs
}
// Sort the list of objects
var newObjs []runtime.Object
namesCache := make(map[string]runtime.Object, len(objs))
var names []string
for _, obj := range objs {
unstr, ok := obj.(*unstructured.Unstructured)
if !ok {
return objs
}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, ntbl); err != nil {
return objs
}
namesCache[ntbl.GetSelfLink()] = obj
names = append(names, ntbl.GetSelfLink())
}
sort.Strings(names)
for _, name := range names {
newObjs = append(newObjs, namesCache[name])
}
return newObjs
}
// Get gets Kubernetes resources as pretty-printed string.
//
// Namespace will set the namespace.
func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
// Since we don't know what order the objects come in, let's group them by the types and then sort them, so
// that when we print them, they come out looking good (headers apply to subgroups, etc.).
objs := make(map[string](map[string]runtime.Object))
objs := make(map[string][]runtime.Object)
gk := make(map[string]schema.GroupKind)
mux := &sync.Mutex{}
infos, err := c.BuildUnstructured(namespace, reader)
// The contents of the reader are used two times. The bytes are coppied out
// for use in future readers.
b, err := ioutil.ReadAll(reader)
if err != nil {
return "", err
}
var objPods = make(map[string][]v1.Pod)
// Get the table display for the objects associated with the release. This
// is done in table format so that it can be displayed in the status in
// the same way kubectl displays the resource information.
// Note, the response returns unstructured data instead of typed objects.
// These cannot be easily (i.e., via the go packages) transformed into
// Go types.
tinfos, err := c.BuildUnstructuredTable(namespace, bytes.NewBuffer(b))
if err != nil {
return "", err
}
missing := []string{}
err = perform(infos, func(info *resource.Info) error {
err = perform(tinfos, func(info *resource.Info) error {
mux.Lock()
defer mux.Unlock()
c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name)
......@@ -241,18 +335,36 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
// versions per cluster, but this certainly won't hurt anything, so let's be safe.
gvk := info.ResourceMapping().GroupVersionKind
vk := gvk.Version + "/" + gvk.Kind
gk[vk] = gvk.GroupKind()
// Initialize map. The main map groups resources based on version/kind
// The second level is a simple 'Name' to 'Object', that will help sort
// the individual resource later
if objs[vk] == nil {
objs[vk] = make(map[string]runtime.Object)
objs[vk] = []runtime.Object{}
}
// Map between the resource name to the underlying info object
objs[vk][info.Name] = resourceInfoToObject(info, c)
objs[vk] = append(objs[vk], resourceInfoToObject(info, c))
return nil
})
if err != nil {
return "", err
}
// This section finds related resources (e.g., pods). Before looking up pods
// the resources the pods are made from need to be looked up in a manner
// that can be turned into Go types and worked with.
infos, err := c.BuildUnstructured(namespace, bytes.NewBuffer(b))
if err != nil {
return "", err
}
err = perform(infos, func(info *resource.Info) error {
mux.Lock()
defer mux.Unlock()
//Get the relation pods
objPods, err = c.getSelectRelationPod(info, objPods)
objs, err = c.getSelectRelationPod(info, objs)
if err != nil {
c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
}
......@@ -263,25 +375,11 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
return "", err
}
//here, we will add the objPods to the objs
for key, podItems := range objPods {
for i := range podItems {
pod := &core.Pod{}
scheme.Scheme.Convert(&podItems[i], pod, nil)
if objs[key+"(related)"] == nil {
objs[key+"(related)"] = make(map[string]runtime.Object)
}
objs[key+"(related)"][pod.ObjectMeta.Name] = runtime.Object(pod)
}
}
// Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so
// spin through them and print them. Printer is cool since it prints the header only when
// an object type changes, so we can just rely on that. Problem is it doesn't seem to keep
// track of tab widths.
buf := new(bytes.Buffer)
printFlags := get.NewHumanPrintFlags()
// Sort alphabetically by version/kind keys
vkKeys := sortByKey(objs)
......@@ -290,20 +388,29 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) {
if _, err = fmt.Fprintf(buf, "==> %s\n", t); err != nil {
return "", err
}
typePrinter, _ := printFlags.ToPrinter("")
vk := objs[t]
var sortedResources []string
for resource := range objs[t] {
sortedResources = append(sortedResources, resource)
// The request made for tables returns each Kubernetes object as its
// own table. The normal sorting provided by kubectl and cli-runtime
// does not handle this case. Here we sort within each of our own
// grouping.
vk = sortTableSlice(vk)
// The printer flag setup follows a simalar setup to kubectl
printFlags := get.NewHumanPrintFlags()
if lgk, ok := gk[t]; ok {
printFlags.SetKind(lgk)
}
sort.Strings(sortedResources)
printer, _ := printFlags.ToPrinter("")
printer, err = printers.NewTypeSetter(scheme.Scheme).WrapToPrinter(printer, nil)
if err != nil {
return "", err
}
printer = &get.TablePrinter{Delegate: printer}
// Now that each individual resource within the specific version/kind
// is sorted, we print each resource using the k8s printer
vk := objs[t]
for _, resourceName := range sortedResources {
if err := typePrinter.PrintObj(vk[resourceName], buf); err != nil {
c.Log("failed to print object type %s, object: %q :\n %v", t, resourceName, err)
for _, resource := range vk {
if err := printer.PrintObj(resource, buf); err != nil {
c.Log("failed to print object type %s: %v", t, err)
return "", err
}
}
......@@ -985,11 +1092,11 @@ func isPodComplete(event watch.Event) (bool, error) {
return false, nil
}
//get a kubernetes resources' relation pods
// get a kubernetes resources' relation pods
// kubernetes resource used select labels to relate pods
func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]v1.Pod) (map[string][]v1.Pod, error) {
func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]runtime.Object) (map[string][]runtime.Object, error) {
if info == nil {
return objPods, nil
return objs, nil
}
c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name)
......@@ -997,34 +1104,31 @@ func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]
versioned := asVersionedOrUnstructured(info)
selector, ok := getSelectorFromObject(versioned)
if !ok {
return objPods, nil
return objs, nil
}
client, _ := c.KubernetesClientSet()
pods, err := client.CoreV1().Pods(info.Namespace).List(metav1.ListOptions{
LabelSelector: labels.Set(selector).AsSelector().String(),
})
// The related pods are looked up in Table format so that their display can
// be printed in a manner similar to kubectl when it get pods. The response
// can be used with a table printer.
infos, err := c.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(info.Namespace).
DefaultNamespace().
ResourceTypes("pods").
LabelSelector(labels.Set(selector).AsSelector().String()).
TransformRequests(transformRequests).
Do().Infos()
if err != nil {
return objPods, err
return objs, err
}
for _, pod := range pods.Items {
vk := "v1/Pod"
if !isFoundPod(objPods[vk], pod) {
objPods[vk] = append(objPods[vk], pod)
}
for _, info := range infos {
vk := "v1/Pod(related)"
objs[vk] = append(objs[vk], info.Object)
}
return objPods, nil
}
func isFoundPod(podItem []v1.Pod, pod v1.Pod) bool {
for _, value := range podItem {
if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) {
return true
}
}
return false
return objs, nil
}
func asVersionedOrUnstructured(info *resource.Info) runtime.Object {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment