Unverified Commit 8aad20d5 authored by bryanl's avatar bryanl
Browse files

User can select current context

parent 1f615b07
No related merge requests found
Showing with 272 additions and 139 deletions
+272 -139
......@@ -19,19 +19,20 @@ rice-box.go
/dist
# generated fakes
internal/api/fake
internal/cluster/fake
internal/cache/fake
internal/clustereye/fake
internal/config/fake
internal/event/fake
internal/kubeconfig/fake
internal/objectstore/fake
internal/link/fake
internal/module/fake
internal/queryer/fake
internal/modules/overview/printer/fake
internal/modules/overview/resourceviewer/fake
internal/portforward/fake
/pkg/store/fake
/pkg/plugin/fake
/pkg/plugin/api/fake
......
......@@ -40,7 +40,7 @@ run-web:
generate:
@echo "-> $@"
@go generate -v ./pkg/plugin/api/proto ./pkg/plugin/dashboard ./pkg/plugin/api ./pkg/plugin ./internal/...
@go generate -v ./pkg/store ./pkg/plugin/api/proto ./pkg/plugin/dashboard ./pkg/plugin/api ./pkg/plugin ./internal/...
go-install:
$(GOINSTALL) ./vendor/github.com/GeertJohan/go.rice
......
......@@ -7,14 +7,15 @@ import (
"sync"
"time"
"github.com/heptio/developer-dash/pkg/objectstoreutil"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/heptio/developer-dash/pkg/store"
"github.com/heptio/developer-dash/pkg/plugin"
"github.com/heptio/developer-dash/pkg/plugin/api"
"github.com/heptio/developer-dash/pkg/view/component"
"github.com/heptio/developer-dash/pkg/view/flexlayout"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type stub struct {
......@@ -58,7 +59,7 @@ func (s *stub) Print(object runtime.Object) (plugin.PrintResponse, error) {
}
ctx := context.Background()
key, err := objectstoreutil.KeyFromObject(object)
key, err := store.KeyFromObject(object)
if err != nil {
return plugin.PrintResponse{}, err
}
......
......@@ -7,6 +7,8 @@ import (
"path"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/heptio/developer-dash/internal/cluster"
"github.com/heptio/developer-dash/internal/clustereye"
"github.com/heptio/developer-dash/internal/log"
......@@ -14,6 +16,9 @@ import (
"github.com/heptio/developer-dash/internal/module"
)
//go:generate mockgen -destination=./fake/mock_cluster_client.go -package=fake github.com/heptio/developer-dash/internal/api ClusterClient
//go:generate mockgen -destination=./fake/mock_service.go -package=fake github.com/heptio/developer-dash/internal/api Service
var (
// acceptedHosts are the hosts this api will answer for.
acceptedHosts = []string{
......@@ -32,7 +37,7 @@ func serveAsJSON(w http.ResponseWriter, v interface{}, logger log.Logger) {
// Service is an API service.
type Service interface {
RegisterModule(module.Module) error
Handler(ctx context.Context) *mux.Router
Handler(ctx context.Context) (*mux.Router, error)
}
type errorMessage struct {
......@@ -67,11 +72,15 @@ func RespondWithError(w http.ResponseWriter, code int, message string, logger lo
}
}
type ClusterClient interface {
NamespaceClient() (cluster.NamespaceInterface, error)
InfoClient() (cluster.InfoInterface, error)
}
// API is the API for the dashboard client
type API struct {
ctx context.Context
nsClient cluster.NamespaceInterface
infoClient cluster.InfoInterface
clusterClient ClusterClient
moduleManager module.ManagerInterface
prefix string
logger log.Logger
......@@ -80,13 +89,14 @@ type API struct {
modules []module.Module
}
var _ Service = (*API)(nil)
// New creates an instance of API.
func New(ctx context.Context, prefix string, nsClient cluster.NamespaceInterface, infoClient cluster.InfoInterface, moduleManager module.ManagerInterface, logger log.Logger) *API {
func New(ctx context.Context, prefix string, clusterClient ClusterClient, moduleManager module.ManagerInterface, logger log.Logger) *API {
return &API{
ctx: ctx,
prefix: prefix,
nsClient: nsClient,
infoClient: infoClient,
clusterClient: clusterClient,
moduleManager: moduleManager,
modulePaths: make(map[string]module.Module),
logger: logger,
......@@ -94,13 +104,23 @@ func New(ctx context.Context, prefix string, nsClient cluster.NamespaceInterface
}
// Handler returns a HTTP handler for the service.
func (a *API) Handler(ctx context.Context) *mux.Router {
func (a *API) Handler(ctx context.Context) (*mux.Router, error) {
router := mux.NewRouter()
router.Use(rebindHandler(acceptedHosts))
s := router.PathPrefix(a.prefix).Subrouter()
namespacesService := newNamespaces(a.nsClient, a.logger)
nsClient, err := a.clusterClient.NamespaceClient()
if err != nil {
return nil, errors.Wrap(err, "retrieve namespace client")
}
infoClient, err := a.clusterClient.InfoClient()
if err != nil {
return nil, errors.Wrap(err, "retrieve cluster info client")
}
namespacesService := newNamespaces(nsClient, a.logger)
s.Handle("/namespaces", namespacesService).Methods(http.MethodGet)
ans := newAPINavSections(a.modules)
......@@ -114,12 +134,12 @@ func (a *API) Handler(ctx context.Context) *mux.Router {
s.HandleFunc("/namespace", namespaceUpdateService.update).Methods(http.MethodPost)
s.HandleFunc("/namespace", namespaceUpdateService.read).Methods(http.MethodGet)
infoService := newClusterInfo(a.infoClient, a.logger)
infoService := newClusterInfo(infoClient, a.logger)
s.Handle("/cluster-info", infoService)
// Register content routes
contentService := &contentHandler{
nsClient: a.nsClient,
nsClient: nsClient,
modulePaths: a.modulePaths,
modules: a.modules,
logger: a.logger,
......@@ -127,7 +147,7 @@ func (a *API) Handler(ctx context.Context) *mux.Router {
}
if err := contentService.RegisterRoutes(ctx, s); err != nil {
a.logger.Errorf("register routers: %v", err)
a.logger.WithErr(err).Errorf("register routers")
}
s.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
......@@ -135,7 +155,7 @@ func (a *API) Handler(ctx context.Context) *mux.Router {
RespondWithError(w, http.StatusNotFound, "not found", a.logger)
})
return router
return router, nil
}
// RegisterModule registers a module with the API service.
......
......@@ -12,21 +12,21 @@ import (
"github.com/golang/mock/gomock"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
clusterfake "github.com/heptio/developer-dash/internal/cluster/fake"
apiFake "github.com/heptio/developer-dash/internal/api/fake"
clusterFake "github.com/heptio/developer-dash/internal/cluster/fake"
"github.com/heptio/developer-dash/internal/clustereye"
"github.com/heptio/developer-dash/internal/log"
"github.com/heptio/developer-dash/internal/module"
modulefake "github.com/heptio/developer-dash/internal/module/fake"
moduleFake "github.com/heptio/developer-dash/internal/module/fake"
"github.com/heptio/developer-dash/pkg/view/component"
)
type testMocks struct {
namespace *clusterfake.MockNamespaceInterface
info *clusterfake.MockInfoInterface
namespace *clusterFake.MockNamespaceInterface
info *clusterFake.MockInfoInterface
}
func TestAPI_routes(t *testing.T) {
......@@ -112,8 +112,8 @@ func TestAPI_routes(t *testing.T) {
defer controller.Finish()
mocks := &testMocks{
namespace: clusterfake.NewMockNamespaceInterface(controller),
info: clusterfake.NewMockInfoInterface(controller),
namespace: clusterFake.NewMockNamespaceInterface(controller),
info: clusterFake.NewMockInfoInterface(controller),
}
mocks.info.EXPECT().Context().Return("main-context").AnyTimes()
......@@ -123,7 +123,7 @@ func TestAPI_routes(t *testing.T) {
mocks.namespace.EXPECT().Names().Return([]string{"default"}, nil).AnyTimes()
m := modulefake.NewMockModule(controller)
m := moduleFake.NewMockModule(controller)
m.EXPECT().
Name().Return("module").AnyTimes()
m.EXPECT().
......@@ -159,15 +159,22 @@ func TestAPI_routes(t *testing.T) {
}).
AnyTimes()
manager := modulefake.NewMockManagerInterface(controller)
manager := moduleFake.NewMockManagerInterface(controller)
clusterClient := apiFake.NewMockClusterClient(controller)
clusterClient.EXPECT().NamespaceClient().Return(mocks.namespace, nil).AnyTimes()
clusterClient.EXPECT().InfoClient().Return(mocks.info, nil).AnyTimes()
ctx := context.Background()
srv := New(ctx, "/", mocks.namespace, mocks.info, manager, log.NopLogger())
srv := New(ctx, "/", clusterClient, manager, log.NopLogger())
err := srv.RegisterModule(m)
require.NoError(t, err)
ts := httptest.NewServer(srv.Handler(ctx))
handler, err := srv.Handler(ctx)
require.NoError(t, err)
ts := httptest.NewServer(handler)
defer ts.Close()
u, err := url.Parse(ts.URL)
......
......@@ -7,7 +7,6 @@ import (
"os"
"time"
"github.com/heptio/developer-dash/internal/log"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
......@@ -21,6 +20,8 @@ import (
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
"github.com/heptio/developer-dash/internal/log"
// auth plugins
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
......@@ -44,6 +45,7 @@ type ClientInterface interface {
DiscoveryClient() (discovery.DiscoveryInterface, error)
NamespaceClient() (NamespaceInterface, error)
InfoClient() (InfoInterface, error)
Close()
RESTInterface
}
......@@ -63,6 +65,8 @@ type Cluster struct {
discoveryClient discovery.DiscoveryInterface
restMapper *restmapper.DeferredDiscoveryRESTMapper
closeFn context.CancelFunc
}
var _ ClientInterface = (*Cluster)(nil)
......@@ -114,16 +118,27 @@ func newCluster(ctx context.Context, clientConfig clientcmd.ClientConfig, restCl
logger: log.From(ctx),
}
ctx, cancel := context.WithCancel(ctx)
c.closeFn = cancel
go func() {
<-ctx.Done()
logger.Debugf("removing cluster client template directory")
os.RemoveAll(dir)
logger.Infof("removing cluster client temporary directory")
if err := os.RemoveAll(dir); err != nil {
logger.WithErr(err).Errorf("closing temporary directory")
}
}()
return c, nil
}
func (c *Cluster) Close() {
if c.closeFn != nil {
c.closeFn()
}
}
func (c *Cluster) ResourceExists(gvr schema.GroupVersionResource) bool {
restMapper := c.restMapper
_, err := restMapper.KindFor(gvr)
......@@ -151,8 +166,9 @@ func (c *Cluster) Resource(gk schema.GroupKind) (schema.GroupVersionResource, er
retries++
c.logger.
With("err", err).
Debugf("Having trouble retrieving the REST mapping from your cluster at %s. Retrying.....", restConfig.Host)
WithErr(err).
With("group-kind", gk.String()).
Errorf("Having trouble retrieving the REST mapping from your cluster at %s. Retrying.....", restConfig.Host)
time.Sleep(5 * time.Second)
continue
......@@ -161,7 +177,9 @@ func (c *Cluster) Resource(gk schema.GroupKind) (schema.GroupVersionResource, er
}
return restMapping.Resource, nil
}
c.logger.Infof("Unable to retrieve the REST mapping from your cluster at %s. Full error details below.", restConfig.Host)
c.logger.
With("group-kind", gk.String()).
Errorf("Unable to retrieve the REST mapping from your cluster at %s. Full error details below.", restConfig.Host)
return schema.GroupVersionResource{}, errors.New("unable to retrieve rest mapping")
}
......@@ -223,13 +241,18 @@ func (c *Cluster) Version() (string, error) {
return fmt.Sprint(serverVersion), nil
}
// FromKubeconfig creates a Cluster from a kubeconfig.
func FromKubeconfig(ctx context.Context, kubeconfig string) (*Cluster, error) {
// FromKubeConfig creates a Cluster from a kubeconfig.
func FromKubeConfig(ctx context.Context, kubeconfig, contextName string) (*Cluster, error) {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
if kubeconfig != "" {
rules.ExplicitPath = kubeconfig
}
cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})
overrides := &clientcmd.ConfigOverrides{}
if contextName != "" {
overrides.CurrentContext = contextName
}
cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides)
config, err := cc.ClientConfig()
if err != nil {
return nil, err
......
......@@ -9,7 +9,7 @@ import (
)
func Test_FromKubeConfig(t *testing.T) {
kubeconfig := filepath.Join("testdata", "kubeconfig.yaml")
_, err := FromKubeconfig(context.TODO(), kubeconfig)
kubeConfig := filepath.Join("testdata", "kubeconfig.yaml")
_, err := FromKubeConfig(context.TODO(), kubeConfig, "")
require.NoError(t, err)
}
......@@ -9,11 +9,11 @@ import (
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"github.com/heptio/developer-dash/internal/objectstore"
"github.com/heptio/developer-dash/pkg/store"
)
// EntriesFunc is a function that can create navigation entries.
type EntriesFunc func(ctx context.Context, prefix, namespace string, objectStore objectstore.ObjectStore) ([]Navigation, error)
type EntriesFunc func(ctx context.Context, prefix, namespace string, objectStore store.Store) ([]Navigation, error)
// NavigationEntries help construct navigation entries.
type NavigationEntries struct {
......@@ -27,11 +27,11 @@ type NavigationFactory struct {
rootPath string
namespace string
entries NavigationEntries
objectStore objectstore.ObjectStore
objectStore store.Store
}
// NewNavigationFactory creates an instance of NewNavigationFactory.
func NewNavigationFactory(namespace string, root string, objectStore objectstore.ObjectStore, entries NavigationEntries) *NavigationFactory {
func NewNavigationFactory(namespace string, root string, objectStore store.Store, entries NavigationEntries) *NavigationFactory {
var rootPath = root
if namespace != "" {
rootPath = path.Join(root, "namespace", namespace, "")
......
......@@ -12,8 +12,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/heptio/developer-dash/internal/objectstore"
"github.com/heptio/developer-dash/pkg/objectstoreutil"
"github.com/heptio/developer-dash/pkg/store"
)
// Navigation is a set of navigation entries.
......@@ -29,7 +28,7 @@ func NewNavigation(title, path string) *Navigation {
}
// CRDEntries generates navigation entries for crds.
func CRDEntries(ctx context.Context, prefix, namespace string, objectStore objectstore.ObjectStore) ([]Navigation, error) {
func CRDEntries(ctx context.Context, prefix, namespace string, objectStore store.Store) ([]Navigation, error) {
var list []Navigation
crdNames, err := CustomResourceDefinitionNames(ctx, objectStore)
......@@ -59,8 +58,8 @@ func CRDEntries(ctx context.Context, prefix, namespace string, objectStore objec
}
// CustomResourceDefinitionNames returns the available custom resource definition names.
func CustomResourceDefinitionNames(ctx context.Context, o objectstore.ObjectStore) ([]string, error) {
key := objectstoreutil.Key{
func CustomResourceDefinitionNames(ctx context.Context, o store.Store) ([]string, error) {
key := store.Key{
APIVersion: "apiextensions.k8s.io/v1beta1",
Kind: "CustomResourceDefinition",
}
......@@ -90,15 +89,15 @@ func CustomResourceDefinitionNames(ctx context.Context, o objectstore.ObjectStor
}
// CustomResourceDefinition retrieves a CRD.
func CustomResourceDefinition(ctx context.Context, name string, o objectstore.ObjectStore) (*apiextv1beta1.CustomResourceDefinition, error) {
key := objectstoreutil.Key{
func CustomResourceDefinition(ctx context.Context, name string, o store.Store) (*apiextv1beta1.CustomResourceDefinition, error) {
key := store.Key{
APIVersion: "apiextensions.k8s.io/v1beta1",
Kind: "CustomResourceDefinition",
Name: name,
}
crd := &apiextv1beta1.CustomResourceDefinition{}
if err := objectstore.GetAs(ctx, o, key, crd); err != nil {
if err := store.GetObjectAs(ctx, o, key, crd); err != nil {
return nil, errors.Wrap(err, "get CRD from object store")
}
......@@ -110,7 +109,7 @@ func ListCustomResources(
ctx context.Context,
crd *apiextv1beta1.CustomResourceDefinition,
namespace string,
o objectstore.ObjectStore,
o store.Store,
selector *labels.Set) ([]*unstructured.Unstructured, error) {
if crd == nil {
return nil, errors.New("crd is nil")
......@@ -123,7 +122,7 @@ func ListCustomResources(
apiVersion, kind := gvk.ToAPIVersionAndKind()
key := objectstoreutil.Key{
key := store.Key{
Namespace: namespace,
APIVersion: apiVersion,
Kind: kind,
......
......@@ -18,9 +18,10 @@ import (
func newClusterEyeCmd() *cobra.Command {
var namespace string
var uiURL string
var kubeconfig string
var kubeConfig string
var verboseLevel int
var enableOpenCensus bool
var initialContext string
clusterEyeCmd := &cobra.Command{
Use: "clustereye",
......@@ -50,9 +51,10 @@ func newClusterEyeCmd() *cobra.Command {
go func() {
options := dash.Options{
EnableOpenCensus: enableOpenCensus,
KubeConfig: kubeconfig,
KubeConfig: kubeConfig,
Namespace: namespace,
FrontendURL: uiURL,
Context: initialContext,
}
if err := dash.Run(ctx, logger, shutdownCh, options); err != nil {
......@@ -80,10 +82,11 @@ func newClusterEyeCmd() *cobra.Command {
clusterEyeCmd.Flags().StringVar(&uiURL, "ui-url", "", "dashboard url")
clusterEyeCmd.Flags().CountVarP(&verboseLevel, "verbose", "v", "verbosity level")
clusterEyeCmd.Flags().BoolVarP(&enableOpenCensus, "enable-opencensus", "c", false, "enable open census")
clusterEyeCmd.Flags().StringVarP(&initialContext, "context", "", "", "initial context")
kubeconfig = clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
kubeConfig = clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
clusterEyeCmd.Flags().StringVar(&kubeconfig, "kubeconfig", kubeconfig, "absolute path to kubeconfig file")
clusterEyeCmd.Flags().StringVar(&kubeConfig, "kubeConfig", kubeConfig, "absolute path to kubeConfig file")
return clusterEyeCmd
}
......
......@@ -4,6 +4,7 @@ import (
"context"
"github.com/heptio/developer-dash/internal/componentcache"
"github.com/heptio/developer-dash/pkg/store"
"github.com/pkg/errors"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
......@@ -12,7 +13,6 @@ import (
"github.com/heptio/developer-dash/internal/cluster"
"github.com/heptio/developer-dash/internal/log"
"github.com/heptio/developer-dash/internal/module"
"github.com/heptio/developer-dash/internal/objectstore"
"github.com/heptio/developer-dash/internal/portforward"
"github.com/heptio/developer-dash/pkg/plugin"
)
......@@ -66,7 +66,7 @@ type Dash interface {
CRDWatcher() CRDWatcher
ObjectStore() objectstore.ObjectStore
ObjectStore() store.Store
ComponentCache() componentcache.ComponentCache
......@@ -76,51 +76,61 @@ type Dash interface {
PortForwarder() portforward.PortForwarder
KubeConfigsPaths() []string
KubeConfigPath() string
UseContext(ctx context.Context, contextName string) error
ContextName() string
Validate() error
}
// Live is a live version of dash config.
type Live struct {
clusterClient cluster.ClientInterface
crdWatcher CRDWatcher
logger log.Logger
moduleManager module.ManagerInterface
objectStore objectstore.ObjectStore
componentCache componentcache.ComponentCache
pluginManager plugin.ManagerInterface
portForwarder portforward.PortForwarder
kubeConfigPaths []string
clusterClient cluster.ClientInterface
crdWatcher CRDWatcher
logger log.Logger
moduleManager module.ManagerInterface
objectStore store.Store
componentCache componentcache.ComponentCache
pluginManager plugin.ManagerInterface
portForwarder portforward.PortForwarder
kubeConfigPath string
currentContextName string
}
var _ Dash = (*Live)(nil)
// NewLiveConfig creates an instance of Live.
func NewLiveConfig(
clusterClient cluster.ClientInterface,
crdWatcher CRDWatcher,
kubeConfigPaths []string,
kubeConfigPath string,
logger log.Logger,
moduleManager module.ManagerInterface,
objectStore objectstore.ObjectStore,
objectStore store.Store,
componentCache componentcache.ComponentCache,
pluginManager plugin.ManagerInterface,
portForwarder portforward.PortForwarder,
currentContextName string,
) *Live {
return &Live{
clusterClient: clusterClient,
crdWatcher: crdWatcher,
kubeConfigPaths: kubeConfigPaths,
logger: logger,
moduleManager: moduleManager,
objectStore: objectStore,
componentCache: componentCache,
pluginManager: pluginManager,
portForwarder: portForwarder,
l := &Live{
clusterClient: clusterClient,
crdWatcher: crdWatcher,
kubeConfigPath: kubeConfigPath,
logger: logger,
moduleManager: moduleManager,
objectStore: objectStore,
componentCache: componentCache,
pluginManager: pluginManager,
portForwarder: portForwarder,
currentContextName: currentContextName,
}
objectStore.RegisterOnUpdate(func(store store.Store) {
l.objectStore = store
})
return l
}
// ObjectPath returns the path given an object description.
......@@ -138,8 +148,8 @@ func (l *Live) CRDWatcher() CRDWatcher {
return l.crdWatcher
}
// ObjectStore returns an object store.
func (l *Live) ObjectStore() objectstore.ObjectStore {
// Store returns an object store.
func (l *Live) ObjectStore() store.Store {
return l.objectStore
}
......@@ -148,9 +158,9 @@ func (l *Live) ComponentCache() componentcache.ComponentCache {
return l.componentCache
}
// KubeConfigsPaths returns a slice of kube config paths.
func (l *Live) KubeConfigsPaths() []string {
return l.kubeConfigPaths
// KubeConfigPath returns the kube config path.
func (l *Live) KubeConfigPath() string {
return l.kubeConfigPath
}
// Logger returns a logger.
......@@ -168,6 +178,35 @@ func (l *Live) PortForwarder() portforward.PortForwarder {
return l.portForwarder
}
// UseContext switches context name.
func (l *Live) UseContext(ctx context.Context, contextName string) error {
client, err := cluster.FromKubeConfig(ctx, l.kubeConfigPath, contextName)
if err != nil {
return err
}
l.ClusterClient().Close()
l.clusterClient = client
if err := l.objectStore.UpdateClusterClient(ctx, client); err != nil {
return err
}
if err := l.moduleManager.UpdateContext(ctx, contextName); err != nil {
return err
}
l.currentContextName = contextName
l.Logger().With("new-kube-context", contextName).Infof("updated kube config context")
return nil
}
// ContextName returns the current context name
func (l *Live) ContextName() string {
return l.currentContextName
}
// Validate validates the configuration and returns an error if there is an issue.
func (l *Live) Validate() error {
if l.clusterClient == nil {
......
......@@ -13,9 +13,9 @@ import (
componentCacheFake "github.com/heptio/developer-dash/internal/componentcache/fake"
"github.com/heptio/developer-dash/internal/log"
moduleFake "github.com/heptio/developer-dash/internal/module/fake"
objectstoreFake "github.com/heptio/developer-dash/internal/objectstore/fake"
portForwardFake "github.com/heptio/developer-dash/internal/portforward/fake"
"github.com/heptio/developer-dash/internal/testutil"
objectStoreFake "github.com/heptio/developer-dash/pkg/store/fake"
pluginFake "github.com/heptio/developer-dash/pkg/plugin/fake"
)
......@@ -74,13 +74,18 @@ func TestLiveConfig(t *testing.T) {
ObjectPath(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
Return("/pod", nil)
objectStore := objectstoreFake.NewMockObjectStore(controller)
objectStore := objectStoreFake.NewMockStore(controller)
componentCache := componentCacheFake.NewMockComponentCache(controller)
pluginManager := pluginFake.NewMockManagerInterface(controller)
portForwarder := portForwardFake.NewMockPortForwarder(controller)
kubeConfigPaths := []string{"/path"}
kubeConfigPath := "/path"
config := NewLiveConfig(clusterClient, crdWatcher, kubeConfigPaths, logger, moduleManager, objectStore, componentCache, pluginManager, portForwarder)
objectStore.EXPECT().
RegisterOnUpdate(gomock.Any())
contextName := "context-name"
config := NewLiveConfig(clusterClient, crdWatcher, kubeConfigPath, logger, moduleManager, objectStore, componentCache, pluginManager, portForwarder, contextName)
assert.NoError(t, config.Validate())
assert.Equal(t, clusterClient, config.ClusterClient())
......
......@@ -12,6 +12,7 @@ import (
"time"
"github.com/heptio/developer-dash/internal/componentcache"
"github.com/heptio/developer-dash/internal/objectstore"
"github.com/heptio/developer-dash/internal/config"
"github.com/heptio/developer-dash/internal/describer"
......@@ -27,8 +28,8 @@ import (
"github.com/heptio/developer-dash/internal/module"
"github.com/heptio/developer-dash/internal/modules/localcontent"
"github.com/heptio/developer-dash/internal/modules/overview"
"github.com/heptio/developer-dash/internal/objectstore"
"github.com/heptio/developer-dash/internal/portforward"
"github.com/heptio/developer-dash/pkg/store"
"github.com/heptio/developer-dash/pkg/plugin"
"github.com/heptio/developer-dash/web"
......@@ -48,20 +49,26 @@ type Options struct {
KubeConfig string
Namespace string
FrontendURL string
Context string
}
// Run runs the dashboard.
func Run(ctx context.Context, logger log.Logger, shutdownCh chan bool, options Options) error {
ctx = log.WithLoggerContext(ctx, logger)
if options.Context != "" {
logger.With("initial-context", options.Context).Infof("Setting initial context from user flags")
}
logger.Debugf("Loading configuration: %v", options.KubeConfig)
clusterClient, err := cluster.FromKubeconfig(ctx, options.KubeConfig)
clusterClient, err := cluster.FromKubeConfig(ctx, options.KubeConfig, options.Context)
if err != nil {
return errors.Wrap(err, "failed to init cluster client")
}
if options.EnableOpenCensus {
if err := enableOpenCensus(); err != nil {
logger.Infof("Enabling OpenCensus")
return errors.Wrap(err, "enabling open census")
}
}
......@@ -78,12 +85,7 @@ func Run(ctx context.Context, logger log.Logger, shutdownCh chan bool, options O
logger.Debugf("initial namespace for dashboard is %s", options.Namespace)
infoClient, err := clusterClient.InfoClient()
if err != nil {
return errors.Wrap(err, "failed to create info client")
}
appObjectStore, err := initObjectStore(ctx, ctx.Done(), clusterClient)
appObjectStore, err := initObjectStore(ctx, clusterClient)
if err != nil {
return errors.Wrap(err, "initializing store")
}
......@@ -93,7 +95,7 @@ func Run(ctx context.Context, logger log.Logger, shutdownCh chan bool, options O
return errors.Wrap(err, "initializing component cache")
}
crdWatcher, err := describer.NewDefaultCRDWatcher(appObjectStore)
crdWatcher, err := describer.NewDefaultCRDWatcher(ctx, appObjectStore)
if err != nil {
return errors.Wrap(err, "initializing CRD watcher")
}
......@@ -118,6 +120,7 @@ func Run(ctx context.Context, logger log.Logger, shutdownCh chan bool, options O
pluginManager: pluginManager,
portForwarder: portForwarder,
kubeConfigPath: options.KubeConfig,
initialContext: options.Context,
}
moduleManager, err := initModuleManager(ctx, mo)
if err != nil {
......@@ -130,7 +133,7 @@ func Run(ctx context.Context, logger log.Logger, shutdownCh chan bool, options O
}
// Initialize the API
ah := api.New(ctx, apiPathPrefix, nsClient, infoClient, moduleManager, logger)
ah := api.New(ctx, apiPathPrefix, clusterClient, moduleManager, logger)
for _, m := range moduleManager.Modules() {
if err := ah.RegisterModule(m); err != nil {
return errors.Wrapf(err, "registering module: %v", m.Name())
......@@ -165,12 +168,12 @@ func Run(ctx context.Context, logger log.Logger, shutdownCh chan bool, options O
}
// initObjectStore initializes the cluster object store interface
func initObjectStore(ctx context.Context, stopCh <-chan struct{}, client cluster.ClientInterface) (objectstore.ObjectStore, error) {
func initObjectStore(ctx context.Context, client cluster.ClientInterface) (store.Store, error) {
if client == nil {
return nil, errors.New("nil cluster client")
}
appObjectStore, err := objectstore.NewWatch(ctx, client, stopCh)
appObjectStore, err := objectstore.NewWatch(ctx, client)
if err != nil {
return nil, errors.Wrapf(err, "creating object store for app")
......@@ -179,20 +182,21 @@ func initObjectStore(ctx context.Context, stopCh <-chan struct{}, client cluster
return appObjectStore, nil
}
func initPortForwarder(ctx context.Context, client cluster.ClientInterface, appObjectStore objectstore.ObjectStore) (portforward.PortForwarder, error) {
func initPortForwarder(ctx context.Context, client cluster.ClientInterface, appObjectStore store.Store) (portforward.PortForwarder, error) {
return portforward.Default(ctx, client, appObjectStore)
}
type moduleOptions struct {
clusterClient *cluster.Cluster
crdWatcher config.CRDWatcher
objectStore objectstore.ObjectStore
objectStore store.Store
componentCache componentcache.ComponentCache
namespace string
logger log.Logger
pluginManager *plugin.Manager
portForwarder portforward.PortForwarder
kubeConfigPath string
initialContext string
}
// initModuleManager initializes the moduleManager (and currently the modules themselves)
......@@ -205,13 +209,14 @@ func initModuleManager(ctx context.Context, options moduleOptions) (*module.Mana
c := config.NewLiveConfig(
options.clusterClient,
options.crdWatcher,
[]string{options.kubeConfigPath},
options.kubeConfigPath,
options.logger,
moduleManager,
options.objectStore,
options.componentCache,
options.pluginManager,
options.portForwarder,
options.initialContext,
)
overviewOptions := overview.Options{
......@@ -236,7 +241,7 @@ func initModuleManager(ctx context.Context, options moduleOptions) (*module.Mana
moduleManager.Register(clusterOverviewModule)
configurationOptions := configuration.Options{
DashConfig: c,
DashConfig: c,
KubeConfigPath: options.kubeConfigPath,
}
configurationModule := configuration.New(ctx, configurationOptions)
......@@ -326,7 +331,12 @@ func (d *dash) handler(ctx context.Context) (http.Handler, error) {
}
router := mux.NewRouter()
router.PathPrefix(apiPathPrefix).Handler(d.apiHandler.Handler(ctx))
apiHandler, err := d.apiHandler.Handler(ctx)
if err != nil {
return nil, err
}
router.PathPrefix(apiPathPrefix).Handler(apiHandler)
router.PathPrefix("/").Handler(handler)
allowedOrigins := handlers.AllowedOrigins([]string{"*"})
......
......@@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/heptio/developer-dash/internal/api"
apiFake "github.com/heptio/developer-dash/internal/api/fake"
clusterfake "github.com/heptio/developer-dash/internal/cluster/fake"
"github.com/heptio/developer-dash/internal/log"
modulefake "github.com/heptio/developer-dash/internal/module/fake"
......@@ -65,11 +66,15 @@ func Test_dash_Run(t *testing.T) {
nsClient := clusterfake.NewMockNamespaceInterface(controller)
nsClient.EXPECT().InitialNamespace().Return("default").AnyTimes()
infoClient := clusterfake.NewMockInfoInterface(controller)
clusterClient := apiFake.NewMockClusterClient(controller)
clusterClient.EXPECT().NamespaceClient().Return(nsClient, nil).AnyTimes()
clusterClient.EXPECT().InfoClient().Return(infoClient, nil).AnyTimes()
manager := modulefake.NewMockManagerInterface(controller)
infoClient := clusterfake.NewMockInfoInterface(controller)
service := api.New(ctx, apiPathPrefix, nsClient, infoClient, manager, log.NopLogger())
service := api.New(ctx, apiPathPrefix, clusterClient, manager, log.NopLogger())
d, err := newDash(listener, namespace, uiURL, service, log.NopLogger())
require.NoError(t, err)
......@@ -145,8 +150,12 @@ func Test_dash_routes(t *testing.T) {
infoClient := clusterfake.NewMockInfoInterface(controller)
clusterClient := apiFake.NewMockClusterClient(controller)
clusterClient.EXPECT().NamespaceClient().Return(nsClient, nil).AnyTimes()
clusterClient.EXPECT().InfoClient().Return(infoClient, nil).AnyTimes()
ctx := context.Background()
service := api.New(ctx, apiPathPrefix, nsClient, infoClient, manager, log.NopLogger())
service := api.New(ctx, apiPathPrefix, clusterClient, manager, log.NopLogger())
d, err := newDash(listener, namespace, uiURL, service, log.NopLogger())
require.NoError(t, err)
......
......@@ -3,14 +3,14 @@ package dash
import (
"context"
"github.com/heptio/developer-dash/internal/objectstore"
"github.com/heptio/developer-dash/pkg/store"
"github.com/heptio/developer-dash/internal/portforward"
"github.com/heptio/developer-dash/pkg/plugin"
"github.com/heptio/developer-dash/pkg/plugin/api"
"github.com/pkg/errors"
)
func initPlugin(ctx context.Context, portForwarder portforward.PortForwarder, appObjectStore objectstore.ObjectStore) (*plugin.Manager, error) {
func initPlugin(ctx context.Context, portForwarder portforward.PortForwarder, appObjectStore store.Store) (*plugin.Manager, error) {
service := &api.GRPCService{
ObjectStore: appObjectStore,
PortForwarder: portForwarder,
......
......@@ -15,7 +15,7 @@ import (
"github.com/heptio/developer-dash/internal/modules/overview/resourceviewer"
"github.com/heptio/developer-dash/internal/modules/overview/yamlviewer"
"github.com/heptio/developer-dash/internal/queryer"
"github.com/heptio/developer-dash/pkg/objectstoreutil"
"github.com/heptio/developer-dash/pkg/store"
"github.com/heptio/developer-dash/pkg/view/component"
)
......@@ -67,7 +67,7 @@ func (c *crd) Describe(ctx context.Context, prefix, namespace string, options Op
apiVersion, kind := gvk.ToAPIVersionAndKind()
key := objectstoreutil.Key{
key := store.Key{
Namespace: namespace,
APIVersion: apiVersion,
Kind: kind,
......
......@@ -12,8 +12,7 @@ import (
"github.com/heptio/developer-dash/internal/link"
"github.com/heptio/developer-dash/internal/modules/overview/printer"
"github.com/heptio/developer-dash/internal/objectstore"
"github.com/heptio/developer-dash/pkg/objectstoreutil"
"github.com/heptio/developer-dash/pkg/store"
"github.com/heptio/developer-dash/pkg/view/component"
)
......@@ -77,7 +76,7 @@ func ListCustomResources(
ctx context.Context,
crd *apiextv1beta1.CustomResourceDefinition,
namespace string,
o objectstore.ObjectStore,
o store.Store,
selector *labels.Set) ([]*unstructured.Unstructured, error) {
if crd == nil {
return nil, errors.New("crd is nil")
......@@ -90,7 +89,7 @@ func ListCustomResources(
apiVersion, kind := gvk.ToAPIVersionAndKind()
key := objectstoreutil.Key{
key := store.Key{
Namespace: namespace,
APIVersion: apiVersion,
Kind: kind,
......
......@@ -11,9 +11,9 @@ import (
configFake "github.com/heptio/developer-dash/internal/config/fake"
"github.com/heptio/developer-dash/internal/link"
storefake "github.com/heptio/developer-dash/internal/objectstore/fake"
"github.com/heptio/developer-dash/internal/testutil"
"github.com/heptio/developer-dash/pkg/objectstoreutil"
"github.com/heptio/developer-dash/pkg/store"
storefake "github.com/heptio/developer-dash/pkg/store/fake"
"github.com/heptio/developer-dash/pkg/view/component"
)
......@@ -21,14 +21,14 @@ func Test_crdListDescriber(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
o := storefake.NewMockObjectStore(controller)
o := storefake.NewMockStore(controller)
crd := testutil.CreateCRD("crd1")
crd.Spec.Group = "foo.example.com"
crd.Spec.Version = "v1"
crd.Spec.Names.Kind = "Name"
crdKey := objectstoreutil.Key{
crdKey := store.Key{
APIVersion: "apiextensions.k8s.io/v1beta1",
Kind: "CustomResourceDefinition",
Name: crd.Name,
......@@ -37,7 +37,7 @@ func Test_crdListDescriber(t *testing.T) {
o.EXPECT().HasAccess(gomock.Any(), "list").Return(nil)
o.EXPECT().Get(gomock.Any(), gomock.Eq(crdKey)).Return(testutil.ToUnstructured(t, crd), nil)
crKey := objectstoreutil.Key{
crKey := store.Key{
Namespace: "default",
APIVersion: "foo.example.com/v1",
Kind: "Name",
......
......@@ -14,10 +14,10 @@ import (
configFake "github.com/heptio/developer-dash/internal/config/fake"
linkFake "github.com/heptio/developer-dash/internal/link/fake"
"github.com/heptio/developer-dash/internal/modules/overview/printer"
storefake "github.com/heptio/developer-dash/internal/objectstore/fake"
"github.com/heptio/developer-dash/internal/queryer"
"github.com/heptio/developer-dash/internal/testutil"
"github.com/heptio/developer-dash/pkg/objectstoreutil"
"github.com/heptio/developer-dash/pkg/store"
storefake "github.com/heptio/developer-dash/pkg/store/fake"
pluginFake "github.com/heptio/developer-dash/pkg/plugin/fake"
"github.com/heptio/developer-dash/pkg/view/component"
)
......@@ -28,7 +28,7 @@ func Test_crd(t *testing.T) {
dashConfig := configFake.NewMockDash(controller)
objectStore := storefake.NewMockObjectStore(controller)
objectStore := storefake.NewMockStore(controller)
dashConfig.EXPECT().ObjectStore().Return(objectStore)
crdObject := testutil.CreateCRD("crd1")
......@@ -36,7 +36,7 @@ func Test_crd(t *testing.T) {
crdObject.Spec.Version = "v1"
crdObject.Spec.Names.Kind = "Name"
crdKey := objectstoreutil.Key{
crdKey := store.Key{
APIVersion: "apiextensions.k8s.io/v1beta1",
Kind: "CustomResourceDefinition",
Name: crdObject.Name,
......@@ -44,7 +44,7 @@ func Test_crd(t *testing.T) {
objectStore.EXPECT().Get(gomock.Any(), gomock.Eq(crdKey)).Return(testutil.ToUnstructured(t, crdObject), nil)
crKey := objectstoreutil.Key{
crKey := store.Key{
Namespace: "default",
APIVersion: "foo.example.com/v1",
Kind: "Name",
......
......@@ -3,6 +3,7 @@ package describer
import (
"context"
"fmt"
"sync"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
......@@ -10,30 +11,43 @@ import (
"github.com/heptio/developer-dash/internal/config"
"github.com/heptio/developer-dash/internal/log"
"github.com/heptio/developer-dash/internal/objectstore"
"github.com/heptio/developer-dash/pkg/objectstoreutil"
"github.com/heptio/developer-dash/pkg/store"
)
// DefaultCRDWatcher is the default CRD watcher.
type DefaultCRDWatcher struct {
objectStore objectstore.ObjectStore
objectStore store.Store
mu sync.Mutex
}
var _ config.CRDWatcher = (*DefaultCRDWatcher)(nil)
// NewDefaultCRDWatcher creates an instance of DefaultCRDWatcher.
func NewDefaultCRDWatcher(objectStore objectstore.ObjectStore) (*DefaultCRDWatcher, error) {
func NewDefaultCRDWatcher(ctx context.Context, objectStore store.Store) (*DefaultCRDWatcher, error) {
if objectStore == nil {
return nil, errors.New("object store is nil")
}
return &DefaultCRDWatcher{
cw := &DefaultCRDWatcher{
objectStore: objectStore,
}, nil
}
objectStore.RegisterOnUpdate(func(newObjectStore store.Store){
cw.mu.Lock()
defer cw.mu.Unlock()
cw.objectStore = newObjectStore
logger := log.From(ctx)
logger.Debugf("default crd watcher updated object store")
})
return cw, nil
}
var (
crdKey = objectstoreutil.Key{
crdKey = store.Key{
APIVersion: "apiextensions.k8s.io/v1beta1",
Kind: "CustomResourceDefinition",
}
......@@ -45,6 +59,9 @@ func (cw *DefaultCRDWatcher) Watch(ctx context.Context, watchConfig *config.CRDW
return errors.New("watch config is nil")
}
cw.mu.Lock()
defer cw.mu.Unlock()
handler := &kcache.ResourceEventHandlerFuncs{}
if watchConfig.Add != nil {
......
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