Skip to content

Commit

Permalink
List additional columns in Custom Resource view
Browse files Browse the repository at this point in the history
Custom Resource Definitions may additionalPrinterColumns that are
specific to a give api version.  The CustomResourceHandler and
CustomResourceListHandler were only handling top-level
additionalPrinterColumns that apply to all columns regardless of
version.  This change handles version-specific additionalPrinterColumns.
This change addresses vmware-archive#405

Signed-off-by: Gary Smith <garysmith123@gmail.com>
  • Loading branch information
GarySmith committed Dec 12, 2019
1 parent 59e350a commit c9843e3
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 9 deletions.
71 changes: 62 additions & 9 deletions internal/printer/customresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,56 @@ import (
"github.com/vmware-tanzu/octant/pkg/view/component"
)

func getAdditionalPrinterColumnsForList(
crd *apiextv1beta1.CustomResourceDefinition,
list *unstructured.UnstructuredList) (cols []apiextv1beta1.CustomResourceColumnDefinition) {

if len(list.Items) == 0 {
return
}

apiVersion := list.Items[0].GetAPIVersion()

// Require that all items in the list be of the same api version,
// since different versions may have different additionalPrinterColumns
for _, cr := range list.Items {
if cr.GetAPIVersion() != apiVersion {
return
}
}

return getAdditionalPrinterColumns(crd, apiVersion)
}

func getAdditionalPrinterColumns(
crd *apiextv1beta1.CustomResourceDefinition,
apiVersion string) (cols []apiextv1beta1.CustomResourceColumnDefinition) {

if len(crd.Spec.AdditionalPrinterColumns) > 0 {
return crd.Spec.AdditionalPrinterColumns
}

if len(crd.Spec.Versions) > 0 {

// The apiVersion is in the format: "group/version". Extract the version
splits := strings.Split(apiVersion, "/")
if len(splits) < 2 {
return
}

version := splits[1]

// Find the corresponding version-specific columns in the CRD
for _, ver := range crd.Spec.Versions {
if version == ver.Name {
return ver.AdditionalPrinterColumns
}
}
}

return
}

// CustomResourceListHandler prints a list of custom resources with
// optional custom columns.
func CustomResourceListHandler(
Expand All @@ -30,9 +80,9 @@ func CustomResourceListHandler(
linkGenerator link.Interface,
isLoading bool) (component.Component, error) {

hasCustomColumns := len(crd.Spec.AdditionalPrinterColumns) > 0
if hasCustomColumns {
return printCustomCRDListTable(crdName, crd, list, linkGenerator, isLoading)
extraColumns := getAdditionalPrinterColumnsForList(crd, list)
if len(extraColumns) > 0 {
return printCustomCRDListTable(crdName, crd, list, linkGenerator, extraColumns, isLoading)
}

return printGenericCRDTable(crdName, list, linkGenerator, isLoading)
Expand Down Expand Up @@ -69,10 +119,11 @@ func printCustomCRDListTable(
crd *apiextv1beta1.CustomResourceDefinition,
list *unstructured.UnstructuredList,
linkGenerator link.Interface,
additionalCols []apiextv1beta1.CustomResourceColumnDefinition,
isLoading bool) (component.Component, error) {

table := component.NewTable(crdName, "We couldn't find any custom resources!", component.NewTableCols("Name", "Labels"))
for _, column := range crd.Spec.AdditionalPrinterColumns {
for _, column := range additionalCols {
name := column.Name
if octantStrings.Contains(column.Name, []string{"Name", "Labels", "Age"}) {
name = fmt.Sprintf("Resource %s", column.Name)
Expand All @@ -95,7 +146,7 @@ func printCustomCRDListTable(
row["Labels"] = component.NewLabels(cr.GetLabels())
row["Age"] = component.NewTimestamp(cr.GetCreationTimestamp().Time)

for _, column := range crd.Spec.AdditionalPrinterColumns {
for _, column := range additionalCols {
s, err := printCustomColumn(cr.Object, column)
if err != nil {
return nil, errors.Wrapf(err, "print custom column %q in CRD %q",
Expand Down Expand Up @@ -180,13 +231,14 @@ func printCustomResourceConfig(u *unstructured.Unstructured, crd *apiextv1beta1.

summary := component.NewSummary("Configuration")

if len(crd.Spec.AdditionalPrinterColumns) < 1 {
additionalCols := getAdditionalPrinterColumns(crd, u.GetAPIVersion())
if len(additionalCols) < 1 {
return summary, nil
}

sections := component.SummarySections{}

for _, column := range crd.Spec.AdditionalPrinterColumns {
for _, column := range additionalCols {
if strings.HasPrefix(column.JSONPath, ".spec") {
s, err := printCustomColumn(u.Object, column)
if err != nil {
Expand All @@ -212,13 +264,14 @@ func printCustomResourceStatus(u *unstructured.Unstructured, crd *apiextv1beta1.

summary := component.NewSummary("Status")

if len(crd.Spec.AdditionalPrinterColumns) < 1 {
additionalCols := getAdditionalPrinterColumns(crd, u.GetAPIVersion())
if len(additionalCols) < 1 {
return summary, nil
}

sections := component.SummarySections{}

for _, column := range crd.Spec.AdditionalPrinterColumns {
for _, column := range additionalCols {
if strings.HasPrefix(column.JSONPath, ".status") {
s, err := printCustomColumn(u.Object, column)
if err != nil {
Expand Down
40 changes: 40 additions & 0 deletions internal/printer/customresource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,46 @@ func Test_CustomResourceListHandler_custom_columns(t *testing.T) {
component.AssertEqual(t, expected, got)
}

func Test_CustomResourceListHandler_versioned_custom_columns(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()

tpo := newTestPrinterOptions(controller)

crd := loadCRDFromFile(t, "crd-versioned-cols.yaml")
resource := loadCRFromFile(t, "cr-versioned-cols.yaml")

assert.Equal(t, "stable.example.com/v2", resource.GetAPIVersion())

now := time.Now()
resource.SetCreationTimestamp(metav1.Time{Time: now})

tpo.PathForObject(resource, resource.GetName(), "/my-version")

labels := map[string]string{"foo": "bar"}
resource.SetLabels(labels)

list := testutil.ToUnstructuredList(t, resource)

got, err := CustomResourceListHandler(crd.Name, crd, list, tpo.link, false)
require.NoError(t, err)

expected := component.NewTableWithRows(
"versions.stable.example.com", "We couldn't find any custom resources!",
component.NewTableCols("Name", "Labels", "Spec", "Count", "Age"),
[]component.TableRow{
{
"Name": component.NewLink("", resource.GetName(), "/my-version"),
"Labels": component.NewLabels(labels),
"Spec": component.NewText("* * * * */9"),
"Count": component.NewText("1"),
"Age": component.NewTimestamp(now),
},
})

component.AssertEqual(t, expected, got)
}

func Test_printCustomResourceConfig(t *testing.T) {
cases := []struct {
name string
Expand Down
7 changes: 7 additions & 0 deletions internal/printer/testdata/cr-versioned-cols.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: "stable.example.com/v2"
kind: Version
metadata:
name: my-version
spec:
cronSpec: "* * * * */9"
replicas: 1
65 changes: 65 additions & 0 deletions internal/printer/testdata/crd-versioned-cols.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: versions.stable.example.com
spec:
group: stable.example.com
scope: Namespaced
names:
plural: versions
singular: version
kind: Version
shortNames:
- ver
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
additionalPrinterColumns:
- name: Spec
type: string
description: The cron spec defining the interval a CronJob is run
jsonPath: .spec.cronSpec
- name: Replicas
type: integer
description: The number of jobs launched by the CronJob
jsonPath: .spec.replicas
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
- name: v2
served: true
storage: false
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
replicas:
type: integer
additionalPrinterColumns:
- name: Spec
type: string
description: The cron spec defining the interval a CronJob is run
jsonPath: .spec.cronSpec
- name: Count
type: integer
description: The number of jobs launched by the CronJob
jsonPath: .spec.replicas

0 comments on commit c9843e3

Please sign in to comment.