@ -23,6 +23,7 @@ import (
goerrors "errors"
goerrors "errors"
"fmt"
"fmt"
"io"
"io"
"io/ioutil"
"log"
"log"
"sort"
"sort"
"strings"
"strings"
@ -42,6 +43,8 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime"
@ -51,13 +54,14 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
cachetools "k8s.io/client-go/tools/cache"
cachetools "k8s.io/client-go/tools/cache"
watchtools "k8s.io/client-go/tools/watch"
watchtools "k8s.io/client-go/tools/watch"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/validation"
"k8s.io/kubectl/pkg/validation"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl/cmd/get"
"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 )
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.
// Validate reads Kubernetes manifests and validates the content.
//
//
// This function does not actually do schema validation of manifests. Adding
// 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 ( ) .
DefaultNamespace ( ) .
// Schema(c.validator()). // No schema validation
// Schema(c.validator()). // No schema validation
Stream ( reader , "" ) .
Stream ( reader , "" ) .
Latest ( ) .
Flatten ( ) .
Flatten ( ) .
Do ( ) . Infos ( )
Do ( ) . Infos ( )
return scrubValidationError ( err )
return scrubValidationError ( err )
@ -199,7 +238,7 @@ func resourceInfoToObject(info *resource.Info, c *Client) runtime.Object {
return internalObj
return internalObj
}
}
func sortByKey ( objs map [ string ] (map [string ] runtime . Object ) ) [ ] string {
func sortByKey ( objs map [ string ] [] runtime . Object ) [ ] string {
var keys [ ] string
var keys [ ] string
// Create a simple slice, so we can sort it
// Create a simple slice, so we can sort it
for key := range objs {
for key := range objs {
@ -210,24 +249,79 @@ func sortByKey(objs map[string](map[string]runtime.Object)) []string {
return keys
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.
// Get gets Kubernetes resources as pretty-printed string.
//
//
// Namespace will set the namespace.
// Namespace will set the namespace.
func ( c * Client ) Get ( namespace string , reader io . Reader ) ( string , error ) {
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
// 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.).
// 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 { }
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 {
if err != nil {
return "" , err
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 { }
missing := [ ] string { }
err = perform ( infos , func ( info * resource . Info ) error {
err = perform ( t infos, func ( info * resource . Info ) error {
mux . Lock ( )
mux . Lock ( )
defer mux . Unlock ( )
defer mux . Unlock ( )
c . Log ( "Doing get for %s: %q" , info . Mapping . GroupVersionKind . Kind , info . Name )
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.
// versions per cluster, but this certainly won't hurt anything, so let's be safe.
gvk := info . ResourceMapping ( ) . GroupVersionKind
gvk := info . ResourceMapping ( ) . GroupVersionKind
vk := gvk . Version + "/" + gvk . Kind
vk := gvk . Version + "/" + gvk . Kind
gk [ vk ] = gvk . GroupKind ( )
// Initialize map. The main map groups resources based on version/kind
// 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 second level is a simple 'Name' to 'Object', that will help sort
// the individual resource later
// the individual resource later
if objs [ vk ] == nil {
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
// 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
//Get the relation pods
objPods , err = c . getSelectRelationPod ( info , objPods )
obj s, err = c . getSelectRelationPod ( info , obj s)
if err != nil {
if err != nil {
c . Log ( "Warning: get the relation pod is failed, err:%s" , err . Error ( ) )
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
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
// 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
// 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
// an object type changes, so we can just rely on that. Problem is it doesn't seem to keep
// track of tab widths.
// track of tab widths.
buf := new ( bytes . Buffer )
buf := new ( bytes . Buffer )
printFlags := get . NewHumanPrintFlags ( )
// Sort alphabetically by version/kind keys
// Sort alphabetically by version/kind keys
vkKeys := sortByKey ( objs )
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 {
if _ , err = fmt . Fprintf ( buf , "==> %s\n" , t ) ; err != nil {
return "" , err
return "" , err
}
}
typePrinter, _ := printFlags . ToPrinter ( "" )
vk := objs [ t ]
var sortedResources [ ] string
// The request made for tables returns each Kubernetes object as its
for resource := range objs [ t ] {
// own table. The normal sorting provided by kubectl and cli-runtime
sortedResources = append ( sortedResources , resource )
// 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 )
}
printer , _ := printFlags . ToPrinter ( "" )
printer , err = printers . NewTypeSetter ( scheme . Scheme ) . WrapToPrinter ( printer , nil )
if err != nil {
return "" , err
}
}
sort . Strings ( sortedResources )
printer = & get . TablePrinter { Delegate : printer }
// Now that each individual resource within the specific version/kind
for _ , resource := range vk {
// is sorted, we print each resource using the k8s printer
if err := printer . PrintObj ( resource , buf ) ; err != nil {
vk := objs [ t ]
c . Log ( "failed to print object type %s: %v" , t , err )
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 )
return "" , err
return "" , err
}
}
}
}
@ -987,9 +1094,9 @@ func isPodComplete(event watch.Event) (bool, error) {
// get a kubernetes resources' relation pods
// get a kubernetes resources' relation pods
// kubernetes resource used select labels to relate pods
// kubernetes resource used select labels to relate pods
func ( c * Client ) getSelectRelationPod ( info * resource . Info , obj Pod s map [ string ] [ ] v1. Pod ) ( map [ string ] [ ] v1 . Pod , error ) {
func ( c * Client ) getSelectRelationPod ( info * resource . Info , obj s map [ string ] [ ] runtime. Object ) ( map [ string ] [ ] runtime . Object , error ) {
if info == nil {
if info == nil {
return obj Pod s, nil
return obj s, nil
}
}
c . Log ( "get relation pod of object: %s/%s/%s" , info . Namespace , info . Mapping . GroupVersionKind . Kind , info . Name )
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 )
versioned := asVersionedOrUnstructured ( info )
selector , ok := getSelectorFromObject ( versioned )
selector , ok := getSelectorFromObject ( versioned )
if ! ok {
if ! ok {
return obj Pod s, nil
return obj s, nil
}
}
client , _ := c . KubernetesClientSet ( )
// 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
pods , err := client . CoreV1 ( ) . Pods ( info . Namespace ) . List ( metav1 . ListOptions {
// can be used with a table printer.
LabelSelector : labels . Set ( selector ) . AsSelector ( ) . String ( ) ,
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 {
if err != nil {
return objPods , err
return obj s, err
}
}
for _ , pod := range pods . Items {
for _ , info := range infos {
vk := "v1/Pod"
vk := "v1/Pod(related)"
if ! isFoundPod ( objPods [ vk ] , pod ) {
objs [ vk ] = append ( objs [ vk ] , info . Object )
objPods [ vk ] = append ( objPods [ vk ] , pod )
}
}
return objPods , nil
}
}
func isFoundPod ( podItem [ ] v1 . Pod , pod v1 . Pod ) bool {
return objs , nil
for _ , value := range podItem {
if ( value . Namespace == pod . Namespace ) && ( value . Name == pod . Name ) {
return true
}
}
return false
}
}
func asVersionedOrUnstructured ( info * resource . Info ) runtime . Object {
func asVersionedOrUnstructured ( info * resource . Info ) runtime . Object {