Skip to content
This repository has been archived by the owner on Jan 19, 2023. It is now read-only.

Commit

Permalink
Merge pull request #146 from bryanl/cluster-overview-shows-empty-cust…
Browse files Browse the repository at this point in the history
…om-resources

Show cluster/namespace crds in their proper place
  • Loading branch information
bryanl authored Aug 9, 2019
2 parents d0582ce + d9c823b commit 9340d5b
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 34 deletions.
6 changes: 3 additions & 3 deletions internal/modules/clusteroverview/clusteroverview.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func (co *ClusterOverview) Navigation(ctx context.Context, namespace string, roo

nf := octant.NewNavigationFactory("", root, objectStore, navigationEntries)

entries, err := nf.Generate(ctx, "Cluster Overview", icon.ClusterOverview, "")
entries, err := nf.Generate(ctx, "Cluster Overview", icon.ClusterOverview, "", true)
if err != nil {
return nil, err
}
Expand All @@ -227,8 +227,8 @@ func (co *ClusterOverview) Generators() []octant.Generator {
return []octant.Generator{}
}

func rbacEntries(_ context.Context, prefix, _ string, _ store.Store) ([]navigation.Navigation, error) {
neh := navigation.NavigationEntriesHelper{}
func rbacEntries(_ context.Context, prefix, _ string, _ store.Store, _ bool) ([]navigation.Navigation, error) {
neh := navigation.EntriesHelper{}
neh.Add("Cluster Roles", "cluster-roles", icon.ClusterOverviewClusterRole)
neh.Add("Cluster Role Bindings", "cluster-role-bindings", icon.ClusterOverviewClusterRoleBinding)

Expand Down
16 changes: 8 additions & 8 deletions internal/modules/overview/navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ var (
}
)

func workloadEntries(_ context.Context, prefix, _ string, _ store.Store) ([]navigation.Navigation, error) {
neh := navigation.NavigationEntriesHelper{}
func workloadEntries(_ context.Context, prefix, _ string, _ store.Store, _ bool) ([]navigation.Navigation, error) {
neh := navigation.EntriesHelper{}
neh.Add("Cron Jobs", "cron-jobs", icon.OverviewCronJob)
neh.Add("Daemon Sets", "daemon-sets", icon.OverviewDaemonSet)
neh.Add("Deployments", "deployments", icon.OverviewDeployment)
Expand All @@ -38,16 +38,16 @@ func workloadEntries(_ context.Context, prefix, _ string, _ store.Store) ([]navi
return neh.Generate(prefix)
}

func discoAndLBEntries(_ context.Context, prefix, _ string, _ store.Store) ([]navigation.Navigation, error) {
neh := navigation.NavigationEntriesHelper{}
func discoAndLBEntries(_ context.Context, prefix, _ string, _ store.Store, _ bool) ([]navigation.Navigation, error) {
neh := navigation.EntriesHelper{}
neh.Add("Ingresses", "ingresses", icon.OverviewIngress)
neh.Add("Services", "services", icon.OverviewService)

return neh.Generate(prefix)
}

func configAndStorageEntries(_ context.Context, prefix, _ string, _ store.Store) ([]navigation.Navigation, error) {
neh := navigation.NavigationEntriesHelper{}
func configAndStorageEntries(_ context.Context, prefix, _ string, _ store.Store, _ bool) ([]navigation.Navigation, error) {
neh := navigation.EntriesHelper{}
neh.Add("Config Maps", "config-maps", icon.OverviewConfigMap)
neh.Add("Persistent Volume Claims", "persistent-volume-claims", icon.OverviewPersistentVolumeClaim)
neh.Add("Secrets", "secrets", icon.OverviewSecret)
Expand All @@ -56,8 +56,8 @@ func configAndStorageEntries(_ context.Context, prefix, _ string, _ store.Store)
return neh.Generate(prefix)
}

func rbacEntries(_ context.Context, prefix, _ string, _ store.Store) ([]navigation.Navigation, error) {
neh := navigation.NavigationEntriesHelper{}
func rbacEntries(_ context.Context, prefix, _ string, _ store.Store, _ bool) ([]navigation.Navigation, error) {
neh := navigation.EntriesHelper{}

neh.Add("Roles", "roles", icon.OverviewRole)
neh.Add("Role Bindings", "role-bindings", icon.OverviewRoleBinding)
Expand Down
2 changes: 1 addition & 1 deletion internal/modules/overview/overview.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func (co *Overview) Navigation(ctx context.Context, namespace, root string) ([]n

nf := octant.NewNavigationFactory(namespace, root, objectStore, navigationEntries)

entries, err := nf.Generate(ctx, "Overview", icon.Overview, "")
entries, err := nf.Generate(ctx, "Overview", icon.Overview, "", false)
if err != nil {
return nil, err
}
Expand Down
10 changes: 5 additions & 5 deletions internal/octant/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

// EntriesFunc is a function that can create navigation entries.
type EntriesFunc func(ctx context.Context, prefix, namespace string, objectStore store.Store) ([]navigation.Navigation, error)
type EntriesFunc func(ctx context.Context, prefix, namespace string, objectStore store.Store, wantsClusterScoped bool) ([]navigation.Navigation, error)

// NavigationEntries help construct navigation entries.
type NavigationEntries struct {
Expand Down Expand Up @@ -60,7 +60,7 @@ func (nf *NavigationFactory) Root() string {
}

// Generate returns navigation entries.
func (nf *NavigationFactory) Generate(ctx context.Context, title string, iconName, iconSource string) (*navigation.Navigation, error) {
func (nf *NavigationFactory) Generate(ctx context.Context, title string, iconName, iconSource string, wantsClusterScoped bool) (*navigation.Navigation, error) {
n := &navigation.Navigation{
Title: title,
Path: nf.rootPath,
Expand All @@ -80,7 +80,7 @@ func (nf *NavigationFactory) Generate(ctx context.Context, title string, iconNam

for _, name := range nf.entries.Order {
g.Go(func() error {
children, err := nf.genNode(ctx, name, nf.entries.EntriesFuncs[name])
children, err := nf.genNode(ctx, name, nf.entries.EntriesFuncs[name], wantsClusterScoped)
if err != nil {
return errors.Wrapf(err, "generate entries for %s", name)
}
Expand All @@ -105,14 +105,14 @@ func (nf *NavigationFactory) pathFor(elements ...string) string {
return path.Join(append([]string{nf.rootPath}, elements...)...)
}

func (nf *NavigationFactory) genNode(ctx context.Context, name string, childFn EntriesFunc) (*navigation.Navigation, error) {
func (nf *NavigationFactory) genNode(ctx context.Context, name string, childFn EntriesFunc, wantsClusterScoped bool) (*navigation.Navigation, error) {
node, err := navigation.New(name, nf.pathFor(nf.entries.Lookup[name]))
if err != nil {
return nil, err
}

if childFn != nil {
children, err := childFn(ctx, node.Path, nf.namespace, nf.objectStore)
children, err := childFn(ctx, node.Path, nf.namespace, nf.objectStore, wantsClusterScoped)
if err != nil {
return nil, err
}
Expand Down
28 changes: 14 additions & 14 deletions pkg/navigation/navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ type Navigation struct {
}

// New creates a Navigation.
func New(title, path string, options ...Option) (*Navigation, error) {
navigation := &Navigation{Title: title, Path: path}
func New(title, navigationPath string, options ...Option) (*Navigation, error) {
navigation := &Navigation{Title: title, Path: navigationPath}

for _, option := range options {
if err := option(navigation); err != nil {
Expand All @@ -68,7 +68,7 @@ func New(title, path string, options ...Option) (*Navigation, error) {
}

// CRDEntries generates navigation entries for CRDs.
func CRDEntries(ctx context.Context, prefix, namespace string, objectStore store.Store) ([]Navigation, error) {
func CRDEntries(ctx context.Context, prefix, namespace string, objectStore store.Store, wantsClusterScoped bool) ([]Navigation, error) {
var list []Navigation

crds, err := CustomResourceDefinitions(ctx, objectStore)
Expand All @@ -81,6 +81,11 @@ func CRDEntries(ctx context.Context, prefix, namespace string, objectStore store
})

for i := range crds {
if wantsClusterScoped && crds[i].Spec.Scope != apiextv1beta1.ClusterScoped {
continue
} else if !wantsClusterScoped && crds[i].Spec.Scope != apiextv1beta1.NamespaceScoped {
continue
}

objects, err := ListCustomResources(ctx, crds[i], namespace, objectStore, nil)
if err != nil {
Expand All @@ -106,10 +111,6 @@ func CustomResourceDefinitions(ctx context.Context, o store.Store) ([]*apiextv1b
Kind: "CustomResourceDefinition",
}

if err := o.HasAccess(ctx, key, "list"); err != nil {
return nil, nil
}

rawList, err := o.List(ctx, key)
if err != nil {
return nil, errors.Wrap(err, "listing CRDs")
Expand Down Expand Up @@ -169,14 +170,13 @@ func ListCustomResources(
apiVersion, kind := gvk.ToAPIVersionAndKind()

key := store.Key{
Namespace: namespace,
APIVersion: apiVersion,
Kind: kind,
Selector: selector,
}

if err := o.HasAccess(ctx, key, "list"); err != nil {
return nil, nil
if crd.Spec.Scope == apiextv1beta1.NamespaceScoped {
key.Namespace = namespace
}

objects, err := o.List(ctx, key)
Expand All @@ -193,20 +193,20 @@ type navConfig struct {
iconName string
}

// NavigationEntriesHelper generates navigation entries.
type NavigationEntriesHelper struct {
// EntriesHelper generates navigation entries.
type EntriesHelper struct {
navConfigs []navConfig
}

// Add adds an entry.
func (neh *NavigationEntriesHelper) Add(title, suffix, iconName string) {
func (neh *EntriesHelper) Add(title, suffix, iconName string) {
neh.navConfigs = append(neh.navConfigs, navConfig{
title: title, suffix: suffix, iconName: iconName,
})
}

// Generate generates navigation entries.
func (neh *NavigationEntriesHelper) Generate(prefix string) ([]Navigation, error) {
func (neh *EntriesHelper) Generate(prefix string) ([]Navigation, error) {
var navigations []Navigation

for _, nc := range neh.navConfigs {
Expand Down
120 changes: 118 additions & 2 deletions pkg/navigation/navigation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@ SPDX-License-Identifier: Apache-2.0
package navigation

import (
"context"
"fmt"
"path"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/vmware/octant/internal/testutil"
"github.com/vmware/octant/pkg/icon"
"github.com/vmware/octant/pkg/store"
"github.com/vmware/octant/pkg/store/fake"
)

func Test_NewNavigation(t *testing.T) {
Expand All @@ -27,8 +35,8 @@ func Test_NewNavigation(t *testing.T) {
assert.Equal(t, title, nav.Title)
}

func TestNavigationEntriesHelper(t *testing.T) {
neh := NavigationEntriesHelper{}
func TestEntriesHelper(t *testing.T) {
neh := EntriesHelper{}

neh.Add("title", "suffix", icon.OverviewService)

Expand All @@ -47,3 +55,111 @@ func TestNavigationEntriesHelper(t *testing.T) {
assert.Equal(t, expected.IconName, list[0].IconName)
assert.NotEmpty(t, list[0].IconSource)
}

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

objectStore := fake.NewMockStore(controller)
clusterScopedCRD := createCRD("cluster-scoped", "ClusterScoped", true)
namespaceScopedCRD := createCRD("namespace-scoped", "NamespaceScoped", false)

crds := testutil.ToUnstructuredList(t, clusterScopedCRD, namespaceScopedCRD)
crdKey := store.Key{
APIVersion: "apiextensions.k8s.io/v1beta1",
Kind: "CustomResourceDefinition",
}
objectStore.EXPECT().
List(gomock.Any(), crdKey).
Return(crds, nil).
AnyTimes()

clusterCR := createCR("testing", "v1", "ClusterScoped", "cluster-scoped")
clusterCRs := testutil.ToUnstructuredList(t, clusterCR)
namespaceCR := createCR("testing", "v1", "NamespaceScoped", "namespace-scoped")
namespaceCRs := testutil.ToUnstructuredList(t, namespaceCR)

crNamespaceKey := store.Key{
Namespace: "default",
APIVersion: "testing/v1",
Kind: "NamespaceScoped",
}
objectStore.EXPECT().
List(gomock.Any(), crNamespaceKey).
Return(namespaceCRs, nil).
AnyTimes()
crClusterKey := store.Key{
APIVersion: "testing/v1",
Kind: "ClusterScoped",
}
objectStore.EXPECT().
List(gomock.Any(), crClusterKey).
Return(clusterCRs, nil).
AnyTimes()

ctx := context.Background()

namespaceGot, err := CRDEntries(ctx, "/prefix", "default", objectStore, false)
require.NoError(t, err)

namespaceExpected := []Navigation{
createNavForCR(t, namespaceCR.GetName()),
}

assert.Equal(t, namespaceExpected, namespaceGot)

clusterGot, err := CRDEntries(ctx, "/prefix", "default", objectStore, true)
require.NoError(t, err)

clusterExpected := []Navigation{
createNavForCR(t, clusterCR.GetName()),
}

assert.Equal(t, clusterExpected, clusterGot)
}

func createNavForCR(t *testing.T, name string) Navigation {
nav, err := New(name, path.Join("/prefix", name), SetNavigationIcon(icon.CustomResourceDefinition))
require.NoError(t, err)

return *nav
}

func createCRD(name, kind string, isClusterScoped bool) *apiextv1beta1.CustomResourceDefinition {
scope := apiextv1beta1.ClusterScoped
if !isClusterScoped {
scope = apiextv1beta1.NamespaceScoped
}

crd := testutil.CreateCRD(name)
crd.Spec.Scope = scope
crd.Spec.Group = "testing"
crd.Spec.Names = apiextv1beta1.CustomResourceDefinitionNames{
Kind: kind,
}
// TODO fix this because Version is deprecated
crd.Spec.Version = "v1"
crd.Spec.Versions = []apiextv1beta1.CustomResourceDefinitionVersion{
{
Name: "v1",
Served: true,
Storage: true,
},
}

return crd
}

func createCR(group, version, kind, name string) *unstructured.Unstructured {
m := make(map[string]interface{})
u := &unstructured.Unstructured{Object: m}

u.SetName(name)
u.SetGroupVersionKind(schema.GroupVersionKind{
Group: group,
Version: version,
Kind: kind,
})

return u
}
2 changes: 1 addition & 1 deletion web/src/app/services/namespace/namespace.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('NamespaceService', () => {
namespaces: new BehaviorSubject<string[]>([]),
registerStreamer: (name: string, handler: Streamer) => {},
streamer: () => new BehaviorSubject(emptyNavigation),
navigation: new BehaviorSubject<Navigation>(emptyNavigation)
navigation: new BehaviorSubject<Navigation>(emptyNavigation),
};

const notifierServiceStub = {
Expand Down

0 comments on commit 9340d5b

Please sign in to comment.