diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b7801afd..04b839a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan - [#236](https://github.com/kobsio/kobs/pull/236): [core] Improve filtering in select components for various plugins. - [#260](https://github.com/kobsio/kobs/pull/260): [opsgenie] Adjust permission handling and add actions for incidents. - [#262](https://github.com/kobsio/kobs/pull/262): [core] Rework variables handling in dashboards. +- [#263](https://github.com/kobsio/kobs/pull/263): [core] :warning: _Breaking change:_ :warning: Refactor `cluster` and `clusters` package. ## [v0.7.0](https://github.com/kobsio/kobs/releases/tag/v0.7.0) (2021-11-19) diff --git a/cmd/kobs/config/config_test.go b/cmd/kobs/config/config_test.go new file mode 100644 index 000000000..940bd3d26 --- /dev/null +++ b/cmd/kobs/config/config_test.go @@ -0,0 +1,27 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLoad(t *testing.T) { + t.Run("load config", func(t *testing.T) { + config, err := Load("mocks/config_valid.yaml") + require.NoError(t, err) + require.NotEmpty(t, config) + }) + + t.Run("load config failed: file does not exists", func(t *testing.T) { + config, err := Load("mocks/config.yaml") + require.Error(t, err) + require.Empty(t, config) + }) + + t.Run("load config failed: invalid config", func(t *testing.T) { + config, err := Load("mocks/config_invalid.yaml") + require.Error(t, err) + require.Empty(t, config) + }) +} diff --git a/cmd/kobs/config/mocks/config_invalid.yaml b/cmd/kobs/config/mocks/config_invalid.yaml new file mode 100644 index 000000000..c43381caa --- /dev/null +++ b/cmd/kobs/config/mocks/config_invalid.yaml @@ -0,0 +1,14 @@ +clusters: + - providers: + - provider: kubeconfig + kubeconfig: + path: ${HOME}/.kube/config + +plugins: + resources: + forbidden: + - secrets + + applications: + topologyCacheDuration: 1m + teamsCacheDuration: 1m diff --git a/cmd/kobs/config/mocks/config_valid.yaml b/cmd/kobs/config/mocks/config_valid.yaml new file mode 100644 index 000000000..5c0b18382 --- /dev/null +++ b/cmd/kobs/config/mocks/config_valid.yaml @@ -0,0 +1,14 @@ +clusters: + providers: + - provider: kubeconfig + kubeconfig: + path: ${HOME}/.kube/config + +plugins: + resources: + forbidden: + - secrets + + applications: + topologyCacheDuration: 1m + teamsCacheDuration: 1m diff --git a/cmd/kobs/kobs.go b/cmd/kobs/kobs.go index 4c451e1e1..fe8a709c2 100644 --- a/cmd/kobs/kobs.go +++ b/cmd/kobs/kobs.go @@ -116,18 +116,18 @@ func main() { // repository. // The loaded clusters and the router for the plugins is then passed to the api package, so we can access all the // plugin api routes via the kobs api. - loadedClusters, err := clusters.Load(cfg.Clusters) + clustersClient, err := clusters.NewClient(cfg.Clusters) if err != nil { log.Fatal(nil, "Could not load clusters", zap.Error(err)) } - pluginsRouter := plugins.Register(loadedClusters, cfg.Plugins) + pluginsRouter := plugins.Register(clustersClient, cfg.Plugins) // Initialize each component and start it in it's own goroutine, so that the main goroutine is only used as listener // for terminal signals, to initialize the graceful shutdown of the components. // The appServer is the kobs application server, which serves the React frontend and the health endpoint. The // metrics server is used to serve the kobs metrics. - apiServer, err := api.New(loadedClusters, pluginsRouter, isDevelopment) + apiServer, err := api.New(clustersClient, pluginsRouter, isDevelopment) if err != nil { log.Fatal(nil, "Could not create API server", zap.Error(err)) } diff --git a/cmd/kobs/plugins/plugins.go b/cmd/kobs/plugins/plugins.go index 559f38b2b..dcb646487 100644 --- a/cmd/kobs/plugins/plugins.go +++ b/cmd/kobs/plugins/plugins.go @@ -72,7 +72,7 @@ func (router *Router) getPlugins(w http.ResponseWriter, r *http.Request) { } // Register is used to register all api routes for plugins. -func Register(clusters *clusters.Clusters, config Config) chi.Router { +func Register(clustersClient clusters.Client, config Config) chi.Router { router := Router{ chi.NewRouter(), &plugin.Plugins{}, @@ -81,25 +81,25 @@ func Register(clusters *clusters.Clusters, config Config) chi.Router { router.Get("/", router.getPlugins) // Initialize all plugins - resourcesRouter := resources.Register(clusters, router.plugins, config.Resources) - applicationsRouter := applications.Register(clusters, router.plugins, config.Applications) - teamsRouter := teams.Register(clusters, router.plugins, config.Teams) - usersRouter := users.Register(clusters, router.plugins, config.Users) - dashboardsRouter := dashboards.Register(clusters, router.plugins, config.Dashboards) - prometheusRouter, prometheusInstances := prometheus.Register(clusters, router.plugins, config.Prometheus) + resourcesRouter := resources.Register(clustersClient, router.plugins, config.Resources) + applicationsRouter := applications.Register(clustersClient, router.plugins, config.Applications) + teamsRouter := teams.Register(clustersClient, router.plugins, config.Teams) + usersRouter := users.Register(clustersClient, router.plugins, config.Users) + dashboardsRouter := dashboards.Register(clustersClient, router.plugins, config.Dashboards) + prometheusRouter, prometheusInstances := prometheus.Register(router.plugins, config.Prometheus) elasticsearchRouter := elasticsearch.Register(router.plugins, config.Elasticsearch) - klogsRouter, klogsInstances := klogs.Register(clusters, router.plugins, config.Klogs) - jaegerRouter := jaeger.Register(clusters, router.plugins, config.Jaeger) - kialiRouter := kiali.Register(clusters, router.plugins, config.Kiali) - istioRouter := istio.Register(clusters, router.plugins, config.Istio, prometheusInstances, klogsInstances) + klogsRouter, klogsInstances := klogs.Register(router.plugins, config.Klogs) + jaegerRouter := jaeger.Register(router.plugins, config.Jaeger) + kialiRouter := kiali.Register(router.plugins, config.Kiali) + istioRouter := istio.Register(router.plugins, config.Istio, prometheusInstances, klogsInstances) grafanaRouter := grafana.Register(router.plugins, config.Grafana) - harborRouter := harbor.Register(clusters, router.plugins, config.Harbor) - fluxRouter := flux.Register(clusters, router.plugins, config.Flux) + harborRouter := harbor.Register(router.plugins, config.Harbor) + fluxRouter := flux.Register(clustersClient, router.plugins, config.Flux) opsgenieRouter := opsgenie.Register(router.plugins, config.Opsgenie) - sonarqubeRouter := sonarqube.Register(clusters, router.plugins, config.Sonarqube) - techdocsRouter := techdocs.Register(clusters, router.plugins, config.TechDocs) - azureRouter := azure.Register(clusters, router.plugins, config.Azure) - sqlRouter := sql.Register(clusters, router.plugins, config.SQL) + sonarqubeRouter := sonarqube.Register(router.plugins, config.Sonarqube) + techdocsRouter := techdocs.Register(router.plugins, config.TechDocs) + azureRouter := azure.Register(router.plugins, config.Azure) + sqlRouter := sql.Register(router.plugins, config.SQL) markdownRouter := markdown.Register(router.plugins, config.Markdown) rssRouter := rss.Register(router.plugins, config.RSS) diff --git a/cmd/kobs/plugins/plugins_test.go b/cmd/kobs/plugins/plugins_test.go new file mode 100644 index 000000000..48a0456d0 --- /dev/null +++ b/cmd/kobs/plugins/plugins_test.go @@ -0,0 +1,47 @@ +package plugins + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/kobsio/kobs/pkg/api/plugins/plugin" + + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/require" +) + +func TestGetPlugins(t *testing.T) { + router := Router{ + chi.NewRouter(), + &plugin.Plugins{ + { + Name: "applications", + DisplayName: "Applications", + Description: "Monitor your Kubernetes workloads.", + Home: true, + Type: "applications", + }, + { + Name: "resources", + DisplayName: "Resources", + Description: "View and edit Kubernetes resources.", + Type: "resources", + }, + }, + } + router.Get("/plugins", router.getPlugins) + + req, _ := http.NewRequest(http.MethodGet, "/plugins", nil) + w := httptest.NewRecorder() + + router.getPlugins(w, req) + + require.Equal(t, http.StatusOK, w.Code) + require.Equal(t, "[{\"name\":\"applications\",\"displayName\":\"Applications\",\"description\":\"Monitor your Kubernetes workloads.\",\"home\":true,\"type\":\"applications\",\"options\":null},{\"name\":\"resources\",\"displayName\":\"Resources\",\"description\":\"View and edit Kubernetes resources.\",\"home\":false,\"type\":\"resources\",\"options\":null}]\n", string(w.Body.Bytes())) +} + +func TestRegister(t *testing.T) { + router := Register(nil, Config{}) + require.NotEmpty(t, router) +} diff --git a/docs/contributing/develop-a-plugin.md b/docs/contributing/develop-a-plugin.md index 35afb5b55..13d18f2f3 100644 --- a/docs/contributing/develop-a-plugin.md +++ b/docs/contributing/develop-a-plugin.md @@ -47,12 +47,12 @@ Each plugin must export `chi.Router` router interface, so that the router can be ```go type Router struct { *chi.Mux - clusters *clusters.Clusters + clustersClient clusters.Client config Config } ``` -With the `Router` struct you can then create your APIs as follows, where you have access to the `clusters`, `config`, etc. +With the `Router` struct you can then create your APIs as follows, where you have access to the `clustersClient`, `config`, etc. ```go func (router *Router) getName(w http.ResponseWriter, r *http.Request) {} @@ -63,7 +63,7 @@ Finally your plugin should export a `Register` function, which returns the `chi. You have to add an entry to the `plugins` slice for each instance of your plugin, so that the React UI is aware of the plugin. Then you can create your `router` object, which will then be mounted under the before specified `Route`. ```go -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(clustersClient clusters.Client, plugins *plugin.Plugins, config Config) chi.Router { plugins.Append(plugin.Plugin{ Name: config.Name, DisplayName: config.DisplayName, @@ -73,7 +73,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, + clustersClient, config, } @@ -114,7 +114,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi type Router struct { *chi.Mux - clusters *clusters.Clusters + clustersClient clusters.Client config Config } @@ -137,7 +137,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi } // Register returns a new router which can be used in the router for the kobs rest api. - func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { + func Register(clustersClient clusters.Client, plugins *plugin.Plugins, config Config) chi.Router { plugins.Append(plugin.Plugin{ Name: config.Name, DisplayName: config.DisplayName, @@ -147,7 +147,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, + clustersClient, config, } @@ -172,6 +172,7 @@ export interface IPluginComponent { page?: React.FunctionComponent; panel: React.FunctionComponent; preview?: React.FunctionComponent; + variables?: (variable: IDashboardVariableValues, variables: IDashboardVariableValues[], times: IPluginTimes) => Promise; } ``` diff --git a/docs/contributing/using-the-kobsio-app.md b/docs/contributing/using-the-kobsio-app.md index a2fa8b4cd..a017df737 100644 --- a/docs/contributing/using-the-kobsio-app.md +++ b/docs/contributing/using-the-kobsio-app.md @@ -73,7 +73,7 @@ func (router *Router) getPlugins(w http.ResponseWriter, r *http.Request) { } // Register is used to register all api routes for plugins. -func Register(clusters *clusters.Clusters, config Config) chi.Router { +func Register(clustersClient clusters.Client, config Config) chi.Router { router := Router{ chi.NewRouter(), &plugin.Plugins{}, @@ -82,8 +82,8 @@ func Register(clusters *clusters.Clusters, config Config) chi.Router { router.Get("/", router.getPlugins) // Register all plugins - router.Mount(resources.Route, resources.Register(clusters, router.plugins, config.Resources)) - router.Mount(helloworld.Route, helloworld.Register(clusters, router.plugins, config.HelloWorld)) + router.Mount(resources.Route, resources.Register(clustersClient, router.plugins, config.Resources)) + router.Mount(helloworld.Route, helloworld.Register(clustersClient, router.plugins, config.HelloWorld)) + router.Mount(mynewplugin.Route, mynewplugin.Register(clusters, router.plugins, config.MyNewPlugin)) return router diff --git a/pkg/api/api.go b/pkg/api/api.go index 4e3af3d23..16811cd2a 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -72,7 +72,7 @@ func (s *Server) Stop() { // We exclude the health check from all middlewares, because the health check just returns 200. Therefore we do not need // our defined middlewares like request id, metrics, auth or loggin. This also makes it easier to analyze the logs in a // Kubernetes cluster where the health check is called every x seconds, because we generate less logs. -func New(loadedClusters *clusters.Clusters, pluginsRouter chi.Router, isDevelopment bool) (*Server, error) { +func New(clustersClient clusters.Client, pluginsRouter chi.Router, isDevelopment bool) (*Server, error) { router := chi.NewRouter() if isDevelopment { @@ -92,12 +92,12 @@ func New(loadedClusters *clusters.Clusters, pluginsRouter chi.Router, isDevelopm r.Use(middleware.Recoverer) r.Use(middleware.URLFormat) r.Use(metrics.Metrics) - r.Use(auth.Handler(loadedClusters)) + r.Use(auth.Handler(clustersClient)) r.Use(httplog.Logger) r.Use(render.SetContentType(render.ContentTypeJSON)) r.Get("/user", auth.UserHandler) - r.Mount("/clusters", clusters.NewRouter(loadedClusters)) + r.Mount("/clusters", clusters.NewRouter(clustersClient)) r.Mount("/plugins", pluginsRouter) }) diff --git a/pkg/api/clusters/cluster/cluster.go b/pkg/api/clusters/cluster/cluster.go index 265af6e52..dfbd4ff52 100644 --- a/pkg/api/clusters/cluster/cluster.go +++ b/pkg/api/clusters/cluster/cluster.go @@ -34,15 +34,42 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" - "sigs.k8s.io/controller-runtime/pkg/client" + controllerRuntimeClient "sigs.k8s.io/controller-runtime/pkg/client" ) var ( slugifyRe = regexp.MustCompile("[^a-z0-9]+") ) -// Cluster is a Kubernetes cluster. It contains all required fields to interact with the cluster and it's services. -type Cluster struct { +// Client is the interface to interact with an Kubernetes cluster. +type Client interface { + GetName() string + GetCRDs() []CRD + GetClient(schema *apiruntime.Scheme) (controllerRuntimeClient.Client, error) + GetNamespaces(ctx context.Context, cacheDuration time.Duration) ([]string, error) + GetResources(ctx context.Context, namespace, name, path, resource, paramName, param string) ([]byte, error) + DeleteResource(ctx context.Context, namespace, name, path, resource string, body []byte) error + PatchResource(ctx context.Context, namespace, name, path, resource string, body []byte) error + CreateResource(ctx context.Context, namespace, name, path, resource, subResource string, body []byte) error + GetLogs(ctx context.Context, namespace, name, container, regex string, since, tail int64, previous bool) (string, error) + StreamLogs(ctx context.Context, conn *websocket.Conn, namespace, name, container string, since, tail int64, follow bool) error + GetTerminal(conn *websocket.Conn, namespace, name, container, shell string) error + CopyFileFromPod(w http.ResponseWriter, namespace, name, container, srcPath string) error + CopyFileToPod(namespace, name, container string, srcFile multipart.File, destPath string) error + GetApplications(ctx context.Context, namespace string) ([]application.ApplicationSpec, error) + GetApplication(ctx context.Context, namespace, name string) (*application.ApplicationSpec, error) + GetTeams(ctx context.Context, namespace string) ([]team.TeamSpec, error) + GetTeam(ctx context.Context, namespace, name string) (*team.TeamSpec, error) + GetDashboards(ctx context.Context, namespace string) ([]dashboard.DashboardSpec, error) + GetDashboard(ctx context.Context, namespace, name string) (*dashboard.DashboardSpec, error) + GetUsers(ctx context.Context, namespace string) ([]user.UserSpec, error) + GetUser(ctx context.Context, namespace, name string) (*user.UserSpec, error) + loadCRDs() +} + +// client implements the Client interface. It contains all required fields and methods to interact with an Kubernetes +// cluster. +type client struct { cache Cache config *rest.Config clientset *kubernetes.Clientset @@ -85,18 +112,18 @@ type Cache struct { } // GetName returns the name of the cluster. -func (c *Cluster) GetName() string { +func (c *client) GetName() string { return c.name } // GetCRDs returns all CRDs of the cluster. -func (c *Cluster) GetCRDs() []CRD { +func (c *client) GetCRDs() []CRD { return c.crds } // GetClient returns a new client to perform CRUD operations on Kubernetes objects. -func (c *Cluster) GetClient(schema *apiruntime.Scheme) (client.Client, error) { - return client.New(c.config, client.Options{ +func (c *client) GetClient(schema *apiruntime.Scheme) (controllerRuntimeClient.Client, error) { + return controllerRuntimeClient.New(c.config, controllerRuntimeClient.Options{ Scheme: schema, }) } @@ -104,7 +131,7 @@ func (c *Cluster) GetClient(schema *apiruntime.Scheme) (client.Client, error) { // GetNamespaces returns all namespaces for the cluster. To reduce the latency and the number of API calls, we are // "caching" the namespaces. This means that if a new namespace is created in a cluster, this namespaces is only shown // after the configured cache duration. -func (c *Cluster) GetNamespaces(ctx context.Context, cacheDuration time.Duration) ([]string, error) { +func (c *client) GetNamespaces(ctx context.Context, cacheDuration time.Duration) ([]string, error) { log.Debug(ctx, "Last namespace fetch.", zap.Time("lastFetch", c.cache.namespacesLastFetch)) if c.cache.namespacesLastFetch.After(time.Now().Add(-1 * cacheDuration)) { @@ -133,7 +160,7 @@ func (c *Cluster) GetNamespaces(ctx context.Context, cacheDuration time.Duration // GetResources returns a list for the given resource in the given namespace. The resource is identified by the // Kubernetes API path and the resource. The name is optional and can be used to get a single resource, instead of a // list of resources. -func (c *Cluster) GetResources(ctx context.Context, namespace, name, path, resource, paramName, param string) ([]byte, error) { +func (c *client) GetResources(ctx context.Context, namespace, name, path, resource, paramName, param string) ([]byte, error) { if name != "" { if namespace != "" { res, err := c.clientset.RESTClient().Get().AbsPath(path).Namespace(namespace).Resource(resource).Name(name).DoRaw(ctx) @@ -165,7 +192,7 @@ func (c *Cluster) GetResources(ctx context.Context, namespace, name, path, resou // DeleteResource can be used to delete the given resource. The resource is identified by the Kubernetes API path and // the name of the resource. -func (c *Cluster) DeleteResource(ctx context.Context, namespace, name, path, resource string, body []byte) error { +func (c *client) DeleteResource(ctx context.Context, namespace, name, path, resource string, body []byte) error { _, err := c.clientset.RESTClient().Delete().AbsPath(path).Namespace(namespace).Resource(resource).Name(name).Body(body).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not delete resources.", zap.Error(err), zap.String("cluster", c.name), zap.String("namespace", namespace), zap.String("path", path), zap.String("resource", resource)) @@ -177,7 +204,7 @@ func (c *Cluster) DeleteResource(ctx context.Context, namespace, name, path, res // PatchResource can be used to edit the given resource. The resource is identified by the Kubernetes API path and the // name of the resource. -func (c *Cluster) PatchResource(ctx context.Context, namespace, name, path, resource string, body []byte) error { +func (c *client) PatchResource(ctx context.Context, namespace, name, path, resource string, body []byte) error { _, err := c.clientset.RESTClient().Patch(types.JSONPatchType).AbsPath(path).Namespace(namespace).Resource(resource).Name(name).Body(body).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not patch resources.", zap.Error(err), zap.String("cluster", c.name), zap.String("namespace", namespace), zap.String("path", path), zap.String("resource", resource)) @@ -189,7 +216,7 @@ func (c *Cluster) PatchResource(ctx context.Context, namespace, name, path, reso // CreateResource can be used to create the given resource. The resource is identified by the Kubernetes API path and the // name of the resource. -func (c *Cluster) CreateResource(ctx context.Context, namespace, name, path, resource, subResource string, body []byte) error { +func (c *client) CreateResource(ctx context.Context, namespace, name, path, resource, subResource string, body []byte) error { if name != "" && subResource != "" { _, err := c.clientset.RESTClient().Put().AbsPath(path).Namespace(namespace).Name(name).Resource(resource).SubResource(subResource).Body(body).DoRaw(ctx) if err != nil { @@ -212,7 +239,7 @@ func (c *Cluster) CreateResource(ctx context.Context, namespace, name, path, res // GetLogs returns the logs for a Container. The Container is identified by the namespace and pod name and the container // name. Is is also possible to set the time since when the logs should be received and with the previous flag the logs // for the last container can be received. -func (c *Cluster) GetLogs(ctx context.Context, namespace, name, container, regex string, since, tail int64, previous bool) (string, error) { +func (c *client) GetLogs(ctx context.Context, namespace, name, container, regex string, since, tail int64, previous bool) (string, error) { options := &corev1.PodLogOptions{ Container: container, SinceSeconds: &since, @@ -254,7 +281,7 @@ func (c *Cluster) GetLogs(ctx context.Context, namespace, name, container, regex // StreamLogs can be used to stream the logs of the selected Container. For that we are using the passed in WebSocket // connection an write each line returned by the Kubernetes API to this connection. -func (c *Cluster) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace, name, container string, since, tail int64, follow bool) error { +func (c *client) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace, name, container string, since, tail int64, follow bool) error { options := &corev1.PodLogOptions{ Container: container, SinceSeconds: &since, @@ -302,7 +329,7 @@ func (c *Cluster) StreamLogs(ctx context.Context, conn *websocket.Conn, namespac } // GetTerminal starts a new terminal session via the given WebSocket connection. -func (c *Cluster) GetTerminal(conn *websocket.Conn, namespace, name, container, shell string) error { +func (c *client) GetTerminal(conn *websocket.Conn, namespace, name, container, shell string) error { reqURL, err := url.Parse(fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/exec?container=%s&command=%s&stdin=true&stdout=true&stderr=true&tty=true", c.config.Host, namespace, name, container, shell)) if err != nil { return err @@ -322,7 +349,7 @@ func (c *Cluster) GetTerminal(conn *websocket.Conn, namespace, name, container, } // CopyFileFromPod creates the request URL for downloading a file from the specified container. -func (c *Cluster) CopyFileFromPod(w http.ResponseWriter, namespace, name, container, srcPath string) error { +func (c *client) CopyFileFromPod(w http.ResponseWriter, namespace, name, container, srcPath string) error { command := fmt.Sprintf("&command=tar&command=cf&command=-&command=%s", srcPath) reqURL, err := url.Parse(fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/exec?container=%s&stdin=true&stdout=true&stderr=true&tty=false%s", c.config.Host, namespace, name, container, command)) if err != nil { @@ -333,7 +360,7 @@ func (c *Cluster) CopyFileFromPod(w http.ResponseWriter, namespace, name, contai } // CopyFileToPod creates the request URL for uploading a file to the specified container. -func (c *Cluster) CopyFileToPod(namespace, name, container string, srcFile multipart.File, destPath string) error { +func (c *client) CopyFileToPod(namespace, name, container string, srcFile multipart.File, destPath string) error { command := fmt.Sprintf("&command=cp&command=/dev/stdin&command=%s", destPath) reqURL, err := url.Parse(fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/exec?container=%s&stdin=true&stdout=true&stderr=true&tty=false%s", c.config.Host, namespace, name, container, command)) if err != nil { @@ -345,7 +372,7 @@ func (c *Cluster) CopyFileToPod(namespace, name, container string, srcFile multi // GetApplications returns a list of applications gor the given namespace. It also adds the cluster, namespace and // application name to the Application CR, so that this information must not be specified by the user in the CR. -func (c *Cluster) GetApplications(ctx context.Context, namespace string) ([]application.ApplicationSpec, error) { +func (c *client) GetApplications(ctx context.Context, namespace string) ([]application.ApplicationSpec, error) { applicationsList, err := c.applicationClientset.KobsV1beta1().Applications(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, err @@ -368,7 +395,7 @@ func (c *Cluster) GetApplications(ctx context.Context, namespace string) ([]appl // GetApplication returns a application for the given namespace and name. After the application is retrieved we replace, // the cluster, namespace and name in the spec of the Application CR. This is needed, so that the user doesn't have to, // provide these fields. -func (c *Cluster) GetApplication(ctx context.Context, namespace, name string) (*application.ApplicationSpec, error) { +func (c *client) GetApplication(ctx context.Context, namespace, name string) (*application.ApplicationSpec, error) { applicationCR, err := c.applicationClientset.KobsV1beta1().Applications(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, err @@ -384,7 +411,7 @@ func (c *Cluster) GetApplication(ctx context.Context, namespace, name string) (* // GetTeams returns a list of teams gor the given namespace. It also adds the cluster, namespace and team name to the // Team CR, so that this information must not be specified by the user in the CR. -func (c *Cluster) GetTeams(ctx context.Context, namespace string) ([]team.TeamSpec, error) { +func (c *client) GetTeams(ctx context.Context, namespace string) ([]team.TeamSpec, error) { teamsList, err := c.teamClientset.KobsV1beta1().Teams(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, err @@ -407,7 +434,7 @@ func (c *Cluster) GetTeams(ctx context.Context, namespace string) ([]team.TeamSp // GetTeam returns a team for the given namespace and name. After the team is retrieved we replace, the cluster, // namespace and name in the spec of the Team CR. This is needed, so that the user doesn't have to, provide these // fields. -func (c *Cluster) GetTeam(ctx context.Context, namespace, name string) (*team.TeamSpec, error) { +func (c *client) GetTeam(ctx context.Context, namespace, name string) (*team.TeamSpec, error) { teamCR, err := c.teamClientset.KobsV1beta1().Teams(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, err @@ -423,7 +450,7 @@ func (c *Cluster) GetTeam(ctx context.Context, namespace, name string) (*team.Te // GetDashboards returns a list of dashboards gor the given namespace. It also adds the cluster, namespace and dashboard // name to the Dashboard CR, so that this information must not be specified by the user in the CR. -func (c *Cluster) GetDashboards(ctx context.Context, namespace string) ([]dashboard.DashboardSpec, error) { +func (c *client) GetDashboards(ctx context.Context, namespace string) ([]dashboard.DashboardSpec, error) { dashboardsList, err := c.dashboardClientset.KobsV1beta1().Dashboards(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, err @@ -447,7 +474,7 @@ func (c *Cluster) GetDashboards(ctx context.Context, namespace string) ([]dashbo // GetDashboard returns a dashboard for the given namespace and name. After the dashboard is retrieved we replace, // the cluster, namespace and name in the spec of the Dashboard CR. This is needed, so that the user doesn't have to, // provide these fields. -func (c *Cluster) GetDashboard(ctx context.Context, namespace, name string) (*dashboard.DashboardSpec, error) { +func (c *client) GetDashboard(ctx context.Context, namespace, name string) (*dashboard.DashboardSpec, error) { dashboardCR, err := c.dashboardClientset.KobsV1beta1().Dashboards(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, err @@ -464,7 +491,7 @@ func (c *Cluster) GetDashboard(ctx context.Context, namespace, name string) (*da // GetUsers returns a list of users for the given namespace. It also adds the cluster, namespace and user name to the // User CR, so that this information must not be specified by the user in the CR. -func (c *Cluster) GetUsers(ctx context.Context, namespace string) ([]user.UserSpec, error) { +func (c *client) GetUsers(ctx context.Context, namespace string) ([]user.UserSpec, error) { usersList, err := c.userClientset.KobsV1beta1().Users(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, err @@ -487,7 +514,7 @@ func (c *Cluster) GetUsers(ctx context.Context, namespace string) ([]user.UserSp // GetUser returns a user for the given namespace and name. After the user is retrieved we replace, the cluster, // namespace and name in the spec of the User CR. This is needed, so that the user doesn't have to, provide these // fields. -func (c *Cluster) GetUser(ctx context.Context, namespace, name string) (*user.UserSpec, error) { +func (c *client) GetUser(ctx context.Context, namespace, name string) (*user.UserSpec, error) { userCR, err := c.userClientset.KobsV1beta1().Users(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, err @@ -504,7 +531,7 @@ func (c *Cluster) GetUser(ctx context.Context, namespace, name string) (*user.Us // loadCRDs retrieves all CRDs from the Kubernetes API of this cluster. Then the CRDs are transformed into our internal // CRD format and saved within the cluster. Since this function is only called once after a cluster was loaded, we call // it in a endless loop until it succeeds. -func (c *Cluster) loadCRDs() { +func (c *client) loadCRDs() { offset := 30 for { @@ -564,10 +591,10 @@ func (c *Cluster) loadCRDs() { } } -// NewCluster returns a new cluster. Each cluster must have a unique name and a client to make requests against the -// Kubernetes API server of this cluster. When a cluster was successfully created we call the loadCRDs function to get -// all CRDs for this cluster. -func NewCluster(name string, restConfig *rest.Config) (*Cluster, error) { +// NewClient returns a new client to interact with a Kubernetes cluster. Each cluster must have a unique name and the +// actual Kubernetes clients to make requests against the Kubernetes API server. When a client was successfully created +// we call the loadCRDs function to get all CRDs in the Kubernetes cluster. +func NewClient(name string, restConfig *rest.Config) (Client, error) { clientset, err := kubernetes.NewForConfig(restConfig) if err != nil { log.Error(nil, "Could not create Kubernetes clientset.", zap.Error(err)) @@ -600,7 +627,7 @@ func NewCluster(name string, restConfig *rest.Config) (*Cluster, error) { name = strings.Trim(slugifyRe.ReplaceAllString(strings.ToLower(name), "-"), "-") - c := &Cluster{ + c := &client{ config: restConfig, clientset: clientset, applicationClientset: applicationClientset, diff --git a/pkg/api/clusters/cluster/cluster_mock.go b/pkg/api/clusters/cluster/cluster_mock.go new file mode 100644 index 000000000..0a10512fd --- /dev/null +++ b/pkg/api/clusters/cluster/cluster_mock.go @@ -0,0 +1,441 @@ +// Code generated by mockery v2.9.4. DO NOT EDIT. + +package cluster + +import ( + context "context" + + controllerRuntimeClient "sigs.k8s.io/controller-runtime/pkg/client" + + dashboardv1beta1 "github.com/kobsio/kobs/pkg/api/apis/dashboard/v1beta1" + + http "net/http" + + mock "github.com/stretchr/testify/mock" + + multipart "mime/multipart" + + runtime "k8s.io/apimachinery/pkg/runtime" + + teamv1beta1 "github.com/kobsio/kobs/pkg/api/apis/team/v1beta1" + + time "time" + + userv1beta1 "github.com/kobsio/kobs/pkg/api/apis/user/v1beta1" + + v1beta1 "github.com/kobsio/kobs/pkg/api/apis/application/v1beta1" + + websocket "github.com/gorilla/websocket" +) + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +// CopyFileFromPod provides a mock function with given fields: w, namespace, name, container, srcPath +func (_m *MockClient) CopyFileFromPod(w http.ResponseWriter, namespace string, name string, container string, srcPath string) error { + ret := _m.Called(w, namespace, name, container, srcPath) + + var r0 error + if rf, ok := ret.Get(0).(func(http.ResponseWriter, string, string, string, string) error); ok { + r0 = rf(w, namespace, name, container, srcPath) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CopyFileToPod provides a mock function with given fields: namespace, name, container, srcFile, destPath +func (_m *MockClient) CopyFileToPod(namespace string, name string, container string, srcFile multipart.File, destPath string) error { + ret := _m.Called(namespace, name, container, srcFile, destPath) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, string, multipart.File, string) error); ok { + r0 = rf(namespace, name, container, srcFile, destPath) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreateResource provides a mock function with given fields: ctx, namespace, name, path, resource, subResource, body +func (_m *MockClient) CreateResource(ctx context.Context, namespace string, name string, path string, resource string, subResource string, body []byte) error { + ret := _m.Called(ctx, namespace, name, path, resource, subResource, body) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, []byte) error); ok { + r0 = rf(ctx, namespace, name, path, resource, subResource, body) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteResource provides a mock function with given fields: ctx, namespace, name, path, resource, body +func (_m *MockClient) DeleteResource(ctx context.Context, namespace string, name string, path string, resource string, body []byte) error { + ret := _m.Called(ctx, namespace, name, path, resource, body) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, []byte) error); ok { + r0 = rf(ctx, namespace, name, path, resource, body) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetApplication provides a mock function with given fields: ctx, namespace, name +func (_m *MockClient) GetApplication(ctx context.Context, namespace string, name string) (*v1beta1.ApplicationSpec, error) { + ret := _m.Called(ctx, namespace, name) + + var r0 *v1beta1.ApplicationSpec + if rf, ok := ret.Get(0).(func(context.Context, string, string) *v1beta1.ApplicationSpec); ok { + r0 = rf(ctx, namespace, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1beta1.ApplicationSpec) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, namespace, name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetApplications provides a mock function with given fields: ctx, namespace +func (_m *MockClient) GetApplications(ctx context.Context, namespace string) ([]v1beta1.ApplicationSpec, error) { + ret := _m.Called(ctx, namespace) + + var r0 []v1beta1.ApplicationSpec + if rf, ok := ret.Get(0).(func(context.Context, string) []v1beta1.ApplicationSpec); ok { + r0 = rf(ctx, namespace) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]v1beta1.ApplicationSpec) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, namespace) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetCRDs provides a mock function with given fields: +func (_m *MockClient) GetCRDs() []CRD { + ret := _m.Called() + + var r0 []CRD + if rf, ok := ret.Get(0).(func() []CRD); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]CRD) + } + } + + return r0 +} + +// GetClient provides a mock function with given fields: schema +func (_m *MockClient) GetClient(schema *runtime.Scheme) (controllerRuntimeClient.Client, error) { + ret := _m.Called(schema) + + var r0 controllerRuntimeClient.Client + if rf, ok := ret.Get(0).(func(*runtime.Scheme) controllerRuntimeClient.Client); ok { + r0 = rf(schema) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(controllerRuntimeClient.Client) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*runtime.Scheme) error); ok { + r1 = rf(schema) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetDashboard provides a mock function with given fields: ctx, namespace, name +func (_m *MockClient) GetDashboard(ctx context.Context, namespace string, name string) (*dashboardv1beta1.DashboardSpec, error) { + ret := _m.Called(ctx, namespace, name) + + var r0 *dashboardv1beta1.DashboardSpec + if rf, ok := ret.Get(0).(func(context.Context, string, string) *dashboardv1beta1.DashboardSpec); ok { + r0 = rf(ctx, namespace, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*dashboardv1beta1.DashboardSpec) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, namespace, name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetDashboards provides a mock function with given fields: ctx, namespace +func (_m *MockClient) GetDashboards(ctx context.Context, namespace string) ([]dashboardv1beta1.DashboardSpec, error) { + ret := _m.Called(ctx, namespace) + + var r0 []dashboardv1beta1.DashboardSpec + if rf, ok := ret.Get(0).(func(context.Context, string) []dashboardv1beta1.DashboardSpec); ok { + r0 = rf(ctx, namespace) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]dashboardv1beta1.DashboardSpec) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, namespace) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLogs provides a mock function with given fields: ctx, namespace, name, container, regex, since, tail, previous +func (_m *MockClient) GetLogs(ctx context.Context, namespace string, name string, container string, regex string, since int64, tail int64, previous bool) (string, error) { + ret := _m.Called(ctx, namespace, name, container, regex, since, tail, previous) + + var r0 string + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, int64, int64, bool) string); ok { + r0 = rf(ctx, namespace, name, container, regex, since, tail, previous) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, int64, int64, bool) error); ok { + r1 = rf(ctx, namespace, name, container, regex, since, tail, previous) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetName provides a mock function with given fields: +func (_m *MockClient) GetName() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetNamespaces provides a mock function with given fields: ctx, cacheDuration +func (_m *MockClient) GetNamespaces(ctx context.Context, cacheDuration time.Duration) ([]string, error) { + ret := _m.Called(ctx, cacheDuration) + + var r0 []string + if rf, ok := ret.Get(0).(func(context.Context, time.Duration) []string); ok { + r0 = rf(ctx, cacheDuration) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, time.Duration) error); ok { + r1 = rf(ctx, cacheDuration) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetResources provides a mock function with given fields: ctx, namespace, name, path, resource, paramName, param +func (_m *MockClient) GetResources(ctx context.Context, namespace string, name string, path string, resource string, paramName string, param string) ([]byte, error) { + ret := _m.Called(ctx, namespace, name, path, resource, paramName, param) + + var r0 []byte + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string) []byte); ok { + r0 = rf(ctx, namespace, name, path, resource, paramName, param) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string, string, string) error); ok { + r1 = rf(ctx, namespace, name, path, resource, paramName, param) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTeam provides a mock function with given fields: ctx, namespace, name +func (_m *MockClient) GetTeam(ctx context.Context, namespace string, name string) (*teamv1beta1.TeamSpec, error) { + ret := _m.Called(ctx, namespace, name) + + var r0 *teamv1beta1.TeamSpec + if rf, ok := ret.Get(0).(func(context.Context, string, string) *teamv1beta1.TeamSpec); ok { + r0 = rf(ctx, namespace, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*teamv1beta1.TeamSpec) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, namespace, name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTeams provides a mock function with given fields: ctx, namespace +func (_m *MockClient) GetTeams(ctx context.Context, namespace string) ([]teamv1beta1.TeamSpec, error) { + ret := _m.Called(ctx, namespace) + + var r0 []teamv1beta1.TeamSpec + if rf, ok := ret.Get(0).(func(context.Context, string) []teamv1beta1.TeamSpec); ok { + r0 = rf(ctx, namespace) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]teamv1beta1.TeamSpec) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, namespace) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTerminal provides a mock function with given fields: conn, namespace, name, container, shell +func (_m *MockClient) GetTerminal(conn *websocket.Conn, namespace string, name string, container string, shell string) error { + ret := _m.Called(conn, namespace, name, container, shell) + + var r0 error + if rf, ok := ret.Get(0).(func(*websocket.Conn, string, string, string, string) error); ok { + r0 = rf(conn, namespace, name, container, shell) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetUser provides a mock function with given fields: ctx, namespace, name +func (_m *MockClient) GetUser(ctx context.Context, namespace string, name string) (*userv1beta1.UserSpec, error) { + ret := _m.Called(ctx, namespace, name) + + var r0 *userv1beta1.UserSpec + if rf, ok := ret.Get(0).(func(context.Context, string, string) *userv1beta1.UserSpec); ok { + r0 = rf(ctx, namespace, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.UserSpec) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, namespace, name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUsers provides a mock function with given fields: ctx, namespace +func (_m *MockClient) GetUsers(ctx context.Context, namespace string) ([]userv1beta1.UserSpec, error) { + ret := _m.Called(ctx, namespace) + + var r0 []userv1beta1.UserSpec + if rf, ok := ret.Get(0).(func(context.Context, string) []userv1beta1.UserSpec); ok { + r0 = rf(ctx, namespace) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]userv1beta1.UserSpec) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, namespace) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PatchResource provides a mock function with given fields: ctx, namespace, name, path, resource, body +func (_m *MockClient) PatchResource(ctx context.Context, namespace string, name string, path string, resource string, body []byte) error { + ret := _m.Called(ctx, namespace, name, path, resource, body) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, []byte) error); ok { + r0 = rf(ctx, namespace, name, path, resource, body) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// StreamLogs provides a mock function with given fields: ctx, conn, namespace, name, container, since, tail, follow +func (_m *MockClient) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace string, name string, container string, since int64, tail int64, follow bool) error { + ret := _m.Called(ctx, conn, namespace, name, container, since, tail, follow) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *websocket.Conn, string, string, string, int64, int64, bool) error); ok { + r0 = rf(ctx, conn, namespace, name, container, since, tail, follow) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// loadCRDs provides a mock function with given fields: +func (_m *MockClient) loadCRDs() { + _m.Called() +} diff --git a/pkg/api/clusters/clusters.go b/pkg/api/clusters/clusters.go index 5c3f67db1..c9de80963 100644 --- a/pkg/api/clusters/clusters.go +++ b/pkg/api/clusters/clusters.go @@ -34,15 +34,26 @@ type Config struct { Providers []provider.Config `json:"providers"` } -// TODO -// Clusters contains all fields and methods to interact with the configured Kubernetes clusters. It must implement the -// Clusters service from the protocol buffers definition. -type Clusters struct { - Clusters []*cluster.Cluster +// Client is the interface with all the methods to interact with all loaded Kubernetes clusters. +type Client interface { + GetClusters() []cluster.Client + GetCluster(name string) cluster.Client } -func (c *Clusters) GetCluster(name string) *cluster.Cluster { - for _, cl := range c.Clusters { +// client implements the Client interface and is used to interact with multiple Kubernetes clusters. For that it +// contains a list of all cluster clients. +type client struct { + clusters []cluster.Client +} + +// GetClusters returns all loaded Kubernetes clusters. +func (c *client) GetClusters() []cluster.Client { + return c.clusters +} + +// GetCluster returns a cluster by it's name. +func (c *client) GetCluster(name string) cluster.Client { + for _, cl := range c.clusters { if cl.GetName() == name { return cl } @@ -51,26 +62,26 @@ func (c *Clusters) GetCluster(name string) *cluster.Cluster { return nil } -// Load loads all clusters for the given configuration. +// NewClient loads all clusters for the given configuration. // The clusters can be retrieved from different providers. Currently we are supporting incluster configuration and // kubeconfig files. In the future it is planning to directly support GKE, EKS, AKS, etc. -func Load(config Config) (*Clusters, error) { - var clusters []*cluster.Cluster +func NewClient(config Config) (Client, error) { + var tmpClusters []cluster.Client for _, p := range config.Providers { - providerClusters, err := provider.GetClusters(&p) + providerClusters, err := provider.New(&p).GetClusters() if err != nil { return nil, err } if providerClusters != nil { - clusters = append(clusters, providerClusters...) + tmpClusters = append(tmpClusters, providerClusters...) } } - cs := &Clusters{ - Clusters: clusters, + client := &client{ + clusters: tmpClusters, } - return cs, nil + return client, nil } diff --git a/pkg/api/clusters/clusters_test.go b/pkg/api/clusters/clusters_test.go new file mode 100644 index 000000000..bb1a05127 --- /dev/null +++ b/pkg/api/clusters/clusters_test.go @@ -0,0 +1,62 @@ +package clusters + +import ( + "testing" + + "github.com/kobsio/kobs/pkg/api/clusters/cluster" + "github.com/kobsio/kobs/pkg/api/clusters/provider" + + "github.com/stretchr/testify/require" +) + +func TestGetClusters(t *testing.T) { + c := client{ + clusters: []cluster.Client{}, + } + + clusters := c.GetClusters() + require.Empty(t, clusters) +} + +func TestGetCluster(t *testing.T) { + mockClusterClient := &cluster.MockClient{} + mockClusterClient.On("GetName").Return("testname") + + c := client{clusters: []cluster.Client{mockClusterClient}} + + t.Run("name found", func(t *testing.T) { + clusters := c.GetCluster("testname") + require.NotEmpty(t, clusters) + }) + + t.Run("name not found", func(t *testing.T) { + clusters := c.GetCluster("testname1") + require.Empty(t, clusters) + }) +} + +func TestLoad(t *testing.T) { + t.Run("invalid config", func(t *testing.T) { + c, err := NewClient(Config{ + Providers: []provider.Config{ + { + Provider: provider.INCLUSTER, + }, + }, + }) + require.Error(t, err) + require.Empty(t, c) + }) + + t.Run("valid config", func(t *testing.T) { + c, err := NewClient(Config{ + Providers: []provider.Config{ + { + Provider: provider.KUBECONFIG, + }, + }, + }) + require.NoError(t, err) + require.Empty(t, c) + }) +} diff --git a/pkg/api/clusters/provider/incluster/incluster.go b/pkg/api/clusters/provider/incluster/incluster.go index 38e3bdd83..2ccbc573e 100644 --- a/pkg/api/clusters/provider/incluster/incluster.go +++ b/pkg/api/clusters/provider/incluster/incluster.go @@ -8,26 +8,46 @@ import ( "k8s.io/client-go/rest" ) +// Define the getInClusterConfig and getNewCluster, so that we can overwrite this in our tests. +var getInClusterConfig = rest.InClusterConfig +var getNewCluster = cluster.NewClient + // Config is the configuration for the InCluster provider. type Config struct { Name string `json:"name"` } +// Client is the interface, which must be implemented by the incluster client. +type Client interface { + GetCluster() ([]cluster.Client, error) +} + +type client struct { + config *Config +} + // GetCluster returns the cluster, where kobs is running in via the incluster configuration. For the selection of the // cluster via a name, the user has to provide this name. -func GetCluster(config *Config) ([]*cluster.Cluster, error) { - log.Debug(nil, "Load incluster config.", zap.String("name", config.Name)) +func (c *client) GetCluster() ([]cluster.Client, error) { + log.Debug(nil, "Load incluster config.", zap.String("name", c.config.Name)) - restConfig, err := rest.InClusterConfig() + restConfig, err := getInClusterConfig() if err != nil { log.Error(nil, "Could not create rest config.", zap.Error(err)) return nil, err } - c, err := cluster.NewCluster(config.Name, restConfig) + cl, err := getNewCluster(c.config.Name, restConfig) if err != nil { return nil, err } - return []*cluster.Cluster{c}, nil + return []cluster.Client{cl}, nil +} + +// New returns a new incluster client, with the given configuration. +func New(config *Config) Client { + return &client{ + config: config, + } } diff --git a/pkg/api/clusters/provider/incluster/incluster_test.go b/pkg/api/clusters/provider/incluster/incluster_test.go new file mode 100644 index 000000000..f7ec16454 --- /dev/null +++ b/pkg/api/clusters/provider/incluster/incluster_test.go @@ -0,0 +1,63 @@ +package incluster + +import ( + "fmt" + "testing" + + "github.com/kobsio/kobs/pkg/api/clusters/cluster" + + "github.com/stretchr/testify/require" + "k8s.io/client-go/rest" +) + +func getInClusterConfigError() (*rest.Config, error) { + return nil, fmt.Errorf("could not create incluster config") +} + +func getInClusterConfigSuccess() (*rest.Config, error) { + return nil, nil +} + +func getNewClusterTestError(name string, restConfig *rest.Config) (cluster.Client, error) { + return nil, fmt.Errorf("could not create cluster") +} + +func getNewClusterTestSuccess(name string, restConfig *rest.Config) (cluster.Client, error) { + return &cluster.MockClient{}, nil +} + +func TestGetClusters(t *testing.T) { + t.Run("could not create in cluster config", func(t *testing.T) { + getInClusterConfig = getInClusterConfigError + + client := New(&Config{Name: "kobs"}) + clusters, err := client.GetCluster() + require.Error(t, err) + require.Empty(t, clusters) + }) + + t.Run("new cluster fails", func(t *testing.T) { + getInClusterConfig = getInClusterConfigSuccess + getNewCluster = getNewClusterTestError + + client := New(&Config{Name: "kobs"}) + clusters, err := client.GetCluster() + require.Error(t, err) + require.Empty(t, clusters) + }) + + t.Run("new cluster", func(t *testing.T) { + getInClusterConfig = getInClusterConfigSuccess + getNewCluster = getNewClusterTestSuccess + + client := New(&Config{Name: "kobs"}) + clusters, err := client.GetCluster() + require.NoError(t, err) + require.NotEmpty(t, clusters) + }) +} + +func TestNew(t *testing.T) { + client := New(&Config{}) + require.NotEmpty(t, client) +} diff --git a/pkg/api/clusters/provider/kubeconfig/kubeconfig.go b/pkg/api/clusters/provider/kubeconfig/kubeconfig.go index abccb7d7a..ad3a2dc4b 100644 --- a/pkg/api/clusters/provider/kubeconfig/kubeconfig.go +++ b/pkg/api/clusters/provider/kubeconfig/kubeconfig.go @@ -9,27 +9,46 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) +// getRawConfigFunc is the function to get the raw cluster configuration from the kuebconfig under the given path. This +// function is then called via the "getRawConfig" variable, so that we can overwrite it in our tests. +func getRawConfigFunc(path string) (clientcmdapi.Config, error) { + clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: path}, + &clientcmd.ConfigOverrides{}, + ) + + return clientConfig.RawConfig() +} + +// Set the getRawConfig and getNewCluster functions, so that we can overwrite them in our tests. +var getRawConfig = getRawConfigFunc +var getNewCluster = cluster.NewClient + // Config is the configuration for the Kubeconfig provider. type Config struct { Path string `json:"path"` } +// Client is the interface, which must be implemented by the kubeconfig client. +type Client interface { + GetClusters() ([]cluster.Client, error) +} + +type client struct { + config *Config +} + // GetClusters returns all clusters from a given Kubeconfig file. For that the user have to provide the path to the // Kubeconfig file. -func GetClusters(config *Config) ([]*cluster.Cluster, error) { - log.Debug(nil, "Load Kubeconfig file.", zap.String("path", config.Path)) +func (c *client) GetClusters() ([]cluster.Client, error) { + log.Debug(nil, "Load Kubeconfig file.", zap.String("path", c.config.Path)) - clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - &clientcmd.ClientConfigLoadingRules{ExplicitPath: config.Path}, - &clientcmd.ConfigOverrides{}, - ) - - raw, err := clientConfig.RawConfig() + raw, err := getRawConfig(c.config.Path) if err != nil { return nil, err } - var clusters []*cluster.Cluster + var clusters []cluster.Client for name, context := range raw.Contexts { if _, ok := raw.Clusters[context.Cluster]; ok { @@ -51,7 +70,7 @@ func GetClusters(config *Config) ([]*cluster.Cluster, error) { return nil, err } - c, err := cluster.NewCluster(name, restConfig) + c, err := getNewCluster(name, restConfig) if err != nil { return nil, err } @@ -67,3 +86,10 @@ func GetClusters(config *Config) ([]*cluster.Cluster, error) { return clusters, nil } + +// New returns a new kubeconfig client, with the given configuration. +func New(config *Config) Client { + return &client{ + config: config, + } +} diff --git a/pkg/api/clusters/provider/kubeconfig/kubeconfig_test.go b/pkg/api/clusters/provider/kubeconfig/kubeconfig_test.go new file mode 100644 index 000000000..15d724dee --- /dev/null +++ b/pkg/api/clusters/provider/kubeconfig/kubeconfig_test.go @@ -0,0 +1,100 @@ +package kubeconfig + +import ( + "fmt" + "testing" + + "github.com/kobsio/kobs/pkg/api/clusters/cluster" + + "github.com/stretchr/testify/require" + "k8s.io/client-go/rest" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +func getRawConfigTest(path string) (clientcmdapi.Config, error) { + if path == "" { + return clientcmdapi.Config{}, fmt.Errorf("empty path") + } + + if path == "kubeconfig_missing_cluster.yaml" { + return clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{"admin": {Token: "token"}}, + Contexts: map[string]*clientcmdapi.Context{"kobs": {Cluster: "kobs", AuthInfo: "admin"}}, + }, nil + } + + if path == "kubeconfig_missing_authinfo.yaml" { + return clientcmdapi.Config{ + Clusters: map[string]*clientcmdapi.Cluster{"kobs": {Server: "https://kubernetes.kobs.io", CertificateAuthorityData: []byte(`certificate-authority-data`)}}, + Contexts: map[string]*clientcmdapi.Context{"kobs": {Cluster: "kobs", AuthInfo: "admin"}}, + }, nil + } + + return clientcmdapi.Config{ + Clusters: map[string]*clientcmdapi.Cluster{"kobs": {Server: "https://kubernetes.kobs.io", CertificateAuthorityData: []byte(`certificate-authority-data`)}}, + AuthInfos: map[string]*clientcmdapi.AuthInfo{"admin": {Token: "token"}}, + Contexts: map[string]*clientcmdapi.Context{"kobs": {Cluster: "kobs", AuthInfo: "admin"}}, + }, nil +} + +func getNewClusterTestError(name string, restConfig *rest.Config) (cluster.Client, error) { + return nil, fmt.Errorf("could not create cluster") +} + +func getNewClusterTestSuccess(name string, restConfig *rest.Config) (cluster.Client, error) { + return &cluster.MockClient{}, nil +} + +func TestGetRawConfigFunc(t *testing.T) { + raw, err := getRawConfig("") + require.NoError(t, err) + require.NotEmpty(t, raw) +} + +func TestGetClusters(t *testing.T) { + getRawConfig = getRawConfigTest + + t.Run("get raw config fails", func(t *testing.T) { + client := New(&Config{Path: ""}) + clusters, err := client.GetClusters() + require.Error(t, err) + require.Empty(t, clusters) + }) + + t.Run("kubeconfig missing cluster", func(t *testing.T) { + client := New(&Config{Path: "kubeconfig_missing_cluster.yaml"}) + clusters, err := client.GetClusters() + require.NoError(t, err) + require.Empty(t, clusters) + }) + + t.Run("kubeconfig missing authinfo", func(t *testing.T) { + client := New(&Config{Path: "kubeconfig_missing_authinfo.yaml"}) + clusters, err := client.GetClusters() + require.NoError(t, err) + require.Empty(t, clusters) + }) + + t.Run("new cluster fails", func(t *testing.T) { + getNewCluster = getNewClusterTestError + + client := New(&Config{Path: "kubeconfig.yaml"}) + clusters, err := client.GetClusters() + require.Error(t, err) + require.Empty(t, clusters) + }) + + t.Run("new cluster", func(t *testing.T) { + getNewCluster = getNewClusterTestSuccess + + client := New(&Config{Path: "kubeconfig.yaml"}) + clusters, err := client.GetClusters() + require.NoError(t, err) + require.NotEmpty(t, clusters) + }) +} + +func TestNew(t *testing.T) { + client := New(&Config{}) + require.NotEmpty(t, client) +} diff --git a/pkg/api/clusters/provider/provider.go b/pkg/api/clusters/provider/provider.go index d978ab1eb..e182e8497 100644 --- a/pkg/api/clusters/provider/provider.go +++ b/pkg/api/clusters/provider/provider.go @@ -30,17 +30,33 @@ type Config struct { Kubeconfig kubeconfig.Config `json:"kubeconfig"` } +// Provider is the interface, which must be implemented by our cluster provider. +type Provider interface { + GetClusters() ([]cluster.Client, error) +} + +type provider struct { + config *Config +} + // GetClusters returns all clusters for the given provider. When the provider field doesn't match our custom Type, we // only log a warning instead of throwing an error. This allows kobs to start also, when one provided provider is // invalid. -func GetClusters(config *Config) ([]*cluster.Cluster, error) { - switch config.Provider { +func (p *provider) GetClusters() ([]cluster.Client, error) { + switch p.config.Provider { case INCLUSTER: - return incluster.GetCluster(&config.InCluster) + return incluster.New(&p.config.InCluster).GetCluster() case KUBECONFIG: - return kubeconfig.GetClusters(&config.Kubeconfig) + return kubeconfig.New(&p.config.Kubeconfig).GetClusters() default: - log.Warn(nil, "Invalid provider.", zap.String("provider", string(config.Provider))) + log.Warn(nil, "Invalid provider.", zap.String("provider", string(p.config.Provider))) return nil, nil } } + +// New returns a new provider, which can be used to load all Kubernetes clusters with the given configuration. +func New(config *Config) Provider { + return &provider{ + config: config, + } +} diff --git a/pkg/api/clusters/provider/provider_test.go b/pkg/api/clusters/provider/provider_test.go new file mode 100644 index 000000000..291e51b2d --- /dev/null +++ b/pkg/api/clusters/provider/provider_test.go @@ -0,0 +1,35 @@ +package provider + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetClusters(t *testing.T) { + t.Run("incluster", func(t *testing.T) { + p := provider{config: &Config{Provider: INCLUSTER}} + clusters, err := p.GetClusters() + require.Error(t, err) + require.Empty(t, clusters) + }) + + t.Run("kubeconfig", func(t *testing.T) { + p := provider{config: &Config{Provider: KUBECONFIG}} + clusters, err := p.GetClusters() + require.NoError(t, err) + require.Empty(t, clusters) + }) + + t.Run("", func(t *testing.T) { + p := provider{config: &Config{Provider: ""}} + clusters, err := p.GetClusters() + require.NoError(t, err) + require.Empty(t, clusters) + }) +} + +func TestNew(t *testing.T) { + client := New(&Config{}) + require.NotEmpty(t, client) +} diff --git a/pkg/api/clusters/router.go b/pkg/api/clusters/router.go index 171c0b417..4bf27c154 100644 --- a/pkg/api/clusters/router.go +++ b/pkg/api/clusters/router.go @@ -17,7 +17,7 @@ import ( // Kubernetes API of a cluster. type Router struct { *chi.Mux - clusters *Clusters + clustersClient Client } // GetClusters returns all loaded Kubernetes clusters. @@ -27,7 +27,7 @@ type Router struct { func (router *Router) getClusters(w http.ResponseWriter, r *http.Request) { var clusterNames []string - for _, cluster := range router.clusters.Clusters { + for _, cluster := range router.clustersClient.GetClusters() { clusterNames = append(clusterNames, cluster.GetName()) } @@ -50,7 +50,7 @@ func (router *Router) getNamespaces(w http.ResponseWriter, r *http.Request) { var namespaces []string for _, clusterName := range clusterNames { - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -95,7 +95,7 @@ func (router *Router) getCRDs(w http.ResponseWriter, r *http.Request) { var crds []cluster.CRD - for _, cluster := range router.clusters.Clusters { + for _, cluster := range router.clustersClient.GetClusters() { crds = append(crds, cluster.GetCRDs()...) } @@ -113,10 +113,10 @@ func (router *Router) getCRDs(w http.ResponseWriter, r *http.Request) { } // NewRouter return a new router with all the cluster routes. -func NewRouter(clusters *Clusters) chi.Router { +func NewRouter(clustersClient Client) chi.Router { router := Router{ chi.NewRouter(), - clusters, + clustersClient, } router.Get("/", router.getClusters) diff --git a/pkg/api/middleware/auth/auth.go b/pkg/api/middleware/auth/auth.go index 8ce50f33a..82d0dc716 100644 --- a/pkg/api/middleware/auth/auth.go +++ b/pkg/api/middleware/auth/auth.go @@ -24,7 +24,7 @@ type Auth struct { headerTeams string sessionToken string sessionInterval time.Duration - clusters *clusters.Clusters + clustersClient clusters.Client } func containsTeam(teamID string, teamIDs []string) bool { @@ -43,7 +43,7 @@ func (a *Auth) getUser(ctx context.Context, userID string, teamIDs []string) (au var users []user.UserSpec var teams []team.TeamSpec - for _, c := range a.clusters.Clusters { + for _, c := range a.clustersClient.GetClusters() { tmpUsers, err := c.GetUsers(ctx, "") if err != nil { return authContextUser, err @@ -207,13 +207,13 @@ func (a *Auth) Handler(next http.Handler) http.Handler { } // New returns a new authentication and authorization object. -func New(enabled bool, headerUser, headerTeams, sessionToken string, sessionInterval time.Duration, clusters *clusters.Clusters) *Auth { +func New(enabled bool, headerUser, headerTeams, sessionToken string, sessionInterval time.Duration, clustersClient clusters.Client) *Auth { return &Auth{ enabled: enabled, headerUser: headerUser, headerTeams: headerTeams, sessionToken: sessionToken, sessionInterval: sessionInterval, - clusters: clusters, + clustersClient: clustersClient, } } diff --git a/pkg/api/middleware/auth/handler.go b/pkg/api/middleware/auth/handler.go index 69360445a..9e1dd6877 100644 --- a/pkg/api/middleware/auth/handler.go +++ b/pkg/api/middleware/auth/handler.go @@ -53,8 +53,8 @@ func init() { } // Handler creates a new Auth handler with the options defined via the above flags. -func Handler(clusters *clusters.Clusters) func(next http.Handler) http.Handler { - a := New(flagEnabled, flagHeaderUser, flagHeaderTeams, flagSessionToken, flagSessionInterval, clusters) +func Handler(clustersClient clusters.Client) func(next http.Handler) http.Handler { + a := New(flagEnabled, flagHeaderUser, flagHeaderTeams, flagSessionToken, flagSessionInterval, clustersClient) return a.Handler } diff --git a/plugins/applications/applications.go b/plugins/applications/applications.go index b4787e195..502820d0e 100644 --- a/plugins/applications/applications.go +++ b/plugins/applications/applications.go @@ -30,10 +30,10 @@ type Config struct { // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters - config Config - topology topology.Cache - teams teams.Cache + clustersClient clusters.Client + config Config + topology topology.Cache + teams teams.Cache } // getApplications returns a list of applications. This api endpoint supports multiple options to get applications. So @@ -67,7 +67,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { } if router.teams.Teams == nil { - ts := teams.Get(r.Context(), router.clusters) + ts := teams.Get(r.Context(), router.clustersClient) if ts != nil { router.teams.LastFetch = time.Now() router.teams.Teams = ts @@ -84,7 +84,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { } go func() { - ts := teams.Get(r.Context(), router.clusters) + ts := teams.Get(r.Context(), router.clustersClient) if ts != nil { log.Debug(r.Context(), "Get applications result.", zap.String("team", "get teams in background"), zap.Int("teamsCount", len(ts))) router.teams.LastFetch = time.Now() @@ -104,7 +104,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { var applications []application.ApplicationSpec for _, clusterName := range clusterNames { - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -155,7 +155,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { } if router.topology.Topology == nil || router.topology.Topology.Nodes == nil { - topo := topology.Get(r.Context(), router.clusters) + topo := topology.Get(r.Context(), router.clustersClient) if topo != nil && topo.Nodes != nil { router.topology.LastFetch = time.Now() router.topology.Topology = topo @@ -172,7 +172,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { } go func() { - topo := topology.Get(context.Background(), router.clusters) + topo := topology.Get(context.Background(), router.clustersClient) if topo != nil && topo.Nodes != nil { log.Debug(r.Context(), "Get applications result.", zap.String("topology", "get topology in background"), zap.Int("edges", len(topo.Edges)), zap.Int("nodes", len(topo.Nodes))) router.topology.LastFetch = time.Now() @@ -199,7 +199,7 @@ func (router *Router) getApplication(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get application parameters.", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name)) - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -217,7 +217,7 @@ func (router *Router) getApplication(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(clustersClient clusters.Client, plugins *plugin.Plugins, config Config) chi.Router { plugins.Append(plugin.Plugin{ Name: "applications", DisplayName: "Applications", @@ -244,7 +244,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, + clustersClient, config, topology, teams, diff --git a/plugins/applications/pkg/teams/teams.go b/plugins/applications/pkg/teams/teams.go index 9481b2e77..ddb9c56b2 100644 --- a/plugins/applications/pkg/teams/teams.go +++ b/plugins/applications/pkg/teams/teams.go @@ -25,14 +25,14 @@ type Team struct { } // Get returns a list of teams. For that we are looping through all clusters and getting all the Team CRs from each of -// the clusters. After that we are transforming each Team CR into our internal Teams structure, by just keeping the +// the cluster. After that we are transforming each Team CR into our internal Teams structure, by just keeping the // cluster, namespace and name of each team. In the following we have to loop again to each cluster, to retrieve all the // applications. The we are are going through each team and application to check if the application contains a reference // for the team. If this is the case we are adding the application to the team. -func Get(ctx context.Context, clusters *clusters.Clusters) []Team { +func Get(ctx context.Context, clustersClient clusters.Client) []Team { var cachedTeams []Team - for _, c := range clusters.Clusters { + for _, c := range clustersClient.GetClusters() { teams, err := c.GetTeams(ctx, "") if err != nil { continue @@ -47,7 +47,7 @@ func Get(ctx context.Context, clusters *clusters.Clusters) []Team { } } - for _, c := range clusters.Clusters { + for _, c := range clustersClient.GetClusters() { applications, err := c.GetApplications(ctx, "") if err != nil { continue diff --git a/plugins/applications/pkg/topology/topology.go b/plugins/applications/pkg/topology/topology.go index 92cdb42ce..fb12b2949 100644 --- a/plugins/applications/pkg/topology/topology.go +++ b/plugins/applications/pkg/topology/topology.go @@ -62,11 +62,11 @@ type EdgeData struct { // through all clusters and get all applications for all clusters. Then we are going through all the applications and // and add them as node in the topology grahp. The defined dependencies for all applications are added as edges in the // topology. -func Get(ctx context.Context, clusters *clusters.Clusters) *Topology { +func Get(ctx context.Context, clustersClient clusters.Client) *Topology { var edges []Edge var nodes []Node - for _, c := range clusters.Clusters { + for _, c := range clustersClient.GetClusters() { applications, err := c.GetApplications(ctx, "") if err != nil { continue diff --git a/plugins/azure/azure.go b/plugins/azure/azure.go index 1b7865a4b..3256c767c 100644 --- a/plugins/azure/azure.go +++ b/plugins/azure/azure.go @@ -1,7 +1,6 @@ package azure import ( - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" "github.com/kobsio/kobs/plugins/azure/pkg/instance" @@ -21,7 +20,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -36,7 +34,7 @@ func (router *Router) getInstance(name string) *instance.Instance { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(plugins *plugin.Plugins, config Config) chi.Router { var instances []*instance.Instance for _, cfg := range config { @@ -57,7 +55,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/dashboards/dashboards.go b/plugins/dashboards/dashboards.go index ab5ffd42d..361222cf6 100644 --- a/plugins/dashboards/dashboards.go +++ b/plugins/dashboards/dashboards.go @@ -25,8 +25,8 @@ type Config struct{} // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters - config Config + clustersClient clusters.Client + config Config } // getDashboardsRequest is the structure of the request body for a getDashboards call. It contains some defaults and a @@ -52,7 +52,7 @@ func (router *Router) getAllDashboards(w http.ResponseWriter, r *http.Request) { var dashboards []dashboard.DashboardSpec - for _, cluster := range router.clusters.Clusters { + for _, cluster := range router.clustersClient.GetClusters() { dashboard, err := cluster.GetDashboards(r.Context(), "") if err != nil { log.Error(r.Context(), "Could not get dashboards.", zap.Error(err)) @@ -107,7 +107,7 @@ func (router *Router) getDashboards(w http.ResponseWriter, r *http.Request) { Rows: reference.Inline.Rows, }) } else { - cluster := router.clusters.GetCluster(reference.Cluster) + cluster := router.clustersClient.GetCluster(reference.Cluster) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", reference.Cluster)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -153,7 +153,7 @@ func (router *Router) getDashboard(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get dashboard request data.", zap.String("cluster", data.Cluster), zap.String("namespace", data.Namespace), zap.String("name", data.Name), zap.Any("placeholders", data.Placeholders)) - cluster := router.clusters.GetCluster(data.Cluster) + cluster := router.clustersClient.GetCluster(data.Cluster) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", data.Cluster)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -186,7 +186,7 @@ func (router *Router) getDashboard(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(clustersClient clusters.Client, plugins *plugin.Plugins, config Config) chi.Router { plugins.Append(plugin.Plugin{ Name: "dashboards", DisplayName: "Dashboards", @@ -196,7 +196,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, + clustersClient, config, } diff --git a/plugins/flux/flux.go b/plugins/flux/flux.go index 28c3c4222..f3fae9742 100644 --- a/plugins/flux/flux.go +++ b/plugins/flux/flux.go @@ -23,8 +23,8 @@ type Config struct{} // Router implements the router for the flux plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters - config Config + clustersClient clusters.Client + config Config } func (router *Router) sync(w http.ResponseWriter, r *http.Request) { @@ -35,7 +35,7 @@ func (router *Router) sync(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Sync resource.", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name), zap.String("resource", resource)) - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -72,7 +72,7 @@ func (router *Router) sync(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(clustersClient clusters.Client, plugins *plugin.Plugins, config Config) chi.Router { plugins.Append(plugin.Plugin{ Name: "flux", DisplayName: "Flux", @@ -82,7 +82,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, + clustersClient, config, } diff --git a/plugins/flux/pkg/sync/sync.go b/plugins/flux/pkg/sync/sync.go index c7a6a9c43..c05a91eba 100644 --- a/plugins/flux/pkg/sync/sync.go +++ b/plugins/flux/pkg/sync/sync.go @@ -34,8 +34,8 @@ func doReconcileAnnotations(annotations map[string]string) { // Kustomization can be used to sync a Flux Kustomization. For that the cluster, namespace and name for the resource // must be provided. -func Kustomization(ctx context.Context, cluster *cluster.Cluster, namespace, name string) error { - client, err := cluster.GetClient(createScheme()) +func Kustomization(ctx context.Context, clustersClient cluster.Client, namespace, name string) error { + client, err := clustersClient.GetClient(createScheme()) if err != nil { return fmt.Errorf("could not get client: %w", err) } @@ -61,8 +61,8 @@ func Kustomization(ctx context.Context, cluster *cluster.Cluster, namespace, nam // HelmRelease can be used to sync a Flux HelmRelease. For that the cluster, namespace and name for the resource must be // provided. -func HelmRelease(ctx context.Context, cluster *cluster.Cluster, namespace, name string) error { - client, err := cluster.GetClient(createScheme()) +func HelmRelease(ctx context.Context, clustersClient cluster.Client, namespace, name string) error { + client, err := clustersClient.GetClient(createScheme()) if err != nil { return fmt.Errorf("could not get client: %w", err) } diff --git a/plugins/harbor/harbor.go b/plugins/harbor/harbor.go index a5609f455..93bbddf5d 100644 --- a/plugins/harbor/harbor.go +++ b/plugins/harbor/harbor.go @@ -3,7 +3,6 @@ package harbor import ( "net/http" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -25,7 +24,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -192,7 +190,7 @@ func (router *Router) getBuildHistory(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(plugins *plugin.Plugins, config Config) chi.Router { var instances []*instance.Instance for _, cfg := range config { @@ -218,7 +216,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/istio/istio.go b/plugins/istio/istio.go index 7f02b4e44..f48d0d8f5 100644 --- a/plugins/istio/istio.go +++ b/plugins/istio/istio.go @@ -4,7 +4,6 @@ import ( "net/http" "strconv" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -28,7 +27,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -395,7 +393,7 @@ func (router *Router) getTopDetails(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config, prometheusInstances []*prometheusInstance.Instance, klogsInstances []*klogsInstance.Instance) chi.Router { +func Register(plugins *plugin.Plugins, config Config, prometheusInstances []*prometheusInstance.Instance, klogsInstances []*klogsInstance.Instance) chi.Router { var instances []*instance.Instance for _, cfg := range config { @@ -422,7 +420,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/jaeger/jaeger.go b/plugins/jaeger/jaeger.go index 4be2c57dd..ee1a46e32 100644 --- a/plugins/jaeger/jaeger.go +++ b/plugins/jaeger/jaeger.go @@ -4,7 +4,6 @@ import ( "net/http" "strconv" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -24,7 +23,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -151,7 +149,7 @@ func (router *Router) getTrace(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(plugins *plugin.Plugins, config Config) chi.Router { var instances []*instance.Instance for _, cfg := range config { @@ -177,7 +175,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/kiali/kiali.go b/plugins/kiali/kiali.go index d6fe4670d..b9d689e0e 100644 --- a/plugins/kiali/kiali.go +++ b/plugins/kiali/kiali.go @@ -4,7 +4,6 @@ import ( "net/http" "strconv" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -24,7 +23,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -131,7 +129,7 @@ func (router *Router) getMetrics(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(plugins *plugin.Plugins, config Config) chi.Router { var instances []*instance.Instance for _, cfg := range config { @@ -152,7 +150,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/klogs/klogs.go b/plugins/klogs/klogs.go index e9227e77e..b56fd578a 100644 --- a/plugins/klogs/klogs.go +++ b/plugins/klogs/klogs.go @@ -6,7 +6,6 @@ import ( "strconv" "time" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -26,7 +25,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -216,7 +214,7 @@ func (router *Router) getAggregation(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) (chi.Router, []*instance.Instance) { +func Register(plugins *plugin.Plugins, config Config) (chi.Router, []*instance.Instance) { var instances []*instance.Instance for _, cfg := range config { @@ -237,7 +235,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/prometheus/prometheus.go b/plugins/prometheus/prometheus.go index 47e41a4ce..aa0f39e7d 100644 --- a/plugins/prometheus/prometheus.go +++ b/plugins/prometheus/prometheus.go @@ -4,7 +4,6 @@ import ( "encoding/json" "net/http" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -24,7 +23,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -190,7 +188,7 @@ func (router *Router) getLabels(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) (chi.Router, []*instance.Instance) { +func Register(plugins *plugin.Plugins, config Config) (chi.Router, []*instance.Instance) { var instances []*instance.Instance for _, cfg := range config { @@ -211,7 +209,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/resources/resources.go b/plugins/resources/resources.go index 1f43f4b24..0b1b79ea0 100644 --- a/plugins/resources/resources.go +++ b/plugins/resources/resources.go @@ -54,8 +54,8 @@ type WebSocket struct { // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters - config Config + clustersClient clusters.Client + config Config } // isForbidden checks if the requested resource was specified in the forbidden resources list. This can be used to use @@ -95,7 +95,7 @@ func (router *Router) getResources(w http.ResponseWriter, r *http.Request) { // Loop through all the given cluster names and get for each provided name the cluster interface. After that we // check if the resource was provided via the forbidden resources list. for _, clusterName := range clusterNames { - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -201,7 +201,7 @@ func (router *Router) deleteResource(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -260,7 +260,7 @@ func (router *Router) patchResource(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -315,7 +315,7 @@ func (router *Router) createResource(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -360,7 +360,7 @@ func (router *Router) getLogs(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get logs parameters.", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name), zap.String("container", container), zap.String("regex", regex), zap.String("since", since), zap.String("previous", previous), zap.String("follow", follow)) - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -543,7 +543,7 @@ func (router *Router) getTerminal(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) msg, _ := json.Marshal(terminal.Message{ @@ -579,7 +579,7 @@ func (router *Router) getFile(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get file parameters", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name), zap.String("container", container), zap.String("srcPath", srcPath)) - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -612,7 +612,7 @@ func (router *Router) postFile(w http.ResponseWriter, r *http.Request) { } defer f.Close() - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -632,7 +632,7 @@ func (router *Router) postFile(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(clustersClient clusters.Client, plugins *plugin.Plugins, config Config) chi.Router { var options map[string]interface{} options = make(map[string]interface{}) options["webSocketAddress"] = config.WebSocket.Address @@ -648,7 +648,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, + clustersClient, config, } diff --git a/plugins/sonarqube/sonarqube.go b/plugins/sonarqube/sonarqube.go index 02de97b59..d9b2feb94 100644 --- a/plugins/sonarqube/sonarqube.go +++ b/plugins/sonarqube/sonarqube.go @@ -3,7 +3,6 @@ package sonarqube import ( "net/http" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -25,7 +24,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -89,7 +87,7 @@ func (router *Router) getProjectMeasures(w http.ResponseWriter, r *http.Request) } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(plugins *plugin.Plugins, config Config) chi.Router { var instances []*instance.Instance for _, cfg := range config { @@ -115,7 +113,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/sql/sql.go b/plugins/sql/sql.go index a9486b729..f98bae14d 100644 --- a/plugins/sql/sql.go +++ b/plugins/sql/sql.go @@ -3,7 +3,6 @@ package sql import ( "net/http" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -23,7 +22,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -69,7 +67,7 @@ func (router *Router) getQueryResults(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(plugins *plugin.Plugins, config Config) chi.Router { var instances []*instance.Instance for _, cfg := range config { @@ -90,7 +88,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/teams/teams.go b/plugins/teams/teams.go index 28ff2e3e5..a77ac604a 100644 --- a/plugins/teams/teams.go +++ b/plugins/teams/teams.go @@ -23,8 +23,8 @@ type Config struct{} // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters - config Config + clustersClient clusters.Client + config Config } // getTeams returns a list of teams for all clusters and namespaces. We always return all teams for all clusters and @@ -34,7 +34,7 @@ func (router *Router) getTeams(w http.ResponseWriter, r *http.Request) { var teams []team.TeamSpec - for _, cluster := range router.clusters.Clusters { + for _, cluster := range router.clustersClient.GetClusters() { team, err := cluster.GetTeams(r.Context(), "") if err != nil { log.Error(r.Context(), "Could not get teams.", zap.Error(err)) @@ -59,7 +59,7 @@ func (router *Router) getTeam(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get team parameters.", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name)) - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -76,7 +76,7 @@ func (router *Router) getTeam(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(clustersClient clusters.Client, plugins *plugin.Plugins, config Config) chi.Router { plugins.Append(plugin.Plugin{ Name: "teams", DisplayName: "Teams", @@ -87,7 +87,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, + clustersClient, config, } diff --git a/plugins/techdocs/techdocs.go b/plugins/techdocs/techdocs.go index aa7ad2b7d..d16c6a605 100644 --- a/plugins/techdocs/techdocs.go +++ b/plugins/techdocs/techdocs.go @@ -5,7 +5,6 @@ import ( "net/http" "path/filepath" - "github.com/kobsio/kobs/pkg/api/clusters" "github.com/kobsio/kobs/pkg/api/middleware/errresponse" "github.com/kobsio/kobs/pkg/api/plugins/plugin" "github.com/kobsio/kobs/pkg/log" @@ -27,7 +26,6 @@ type Config []instance.Config // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters instances []*instance.Instance } @@ -138,7 +136,7 @@ func (router *Router) getFile(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(plugins *plugin.Plugins, config Config) chi.Router { var instances []*instance.Instance for _, cfg := range config { @@ -159,7 +157,6 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, instances, } diff --git a/plugins/users/users.go b/plugins/users/users.go index 602e711f2..df31e92c2 100644 --- a/plugins/users/users.go +++ b/plugins/users/users.go @@ -25,8 +25,8 @@ type Config struct{} // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - clusters *clusters.Clusters - config Config + clustersClient clusters.Client + config Config } type getTeamsData struct { @@ -60,7 +60,7 @@ func (router *Router) getUsers(w http.ResponseWriter, r *http.Request) { var users []user.UserSpec - for _, cluster := range router.clusters.Clusters { + for _, cluster := range router.clustersClient.GetClusters() { user, err := cluster.GetUsers(r.Context(), "") if err != nil { log.Error(r.Context(), "Could not get users.") @@ -85,7 +85,7 @@ func (router *Router) getUser(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get User parameters.", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name)) - cluster := router.clusters.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -131,7 +131,7 @@ func (router *Router) getTeams(w http.ResponseWriter, r *http.Request) { c = team.Namespace } - cluster := router.clusters.GetCluster(c) + cluster := router.clustersClient.GetCluster(c) if cluster == nil { log.Error(r.Context(), "Invalid cluster name.", zap.String("cluster", c)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -162,7 +162,7 @@ func (router *Router) getTeam(w http.ResponseWriter, r *http.Request) { var users []user.UserSpec var filteredUsers []user.UserSpec - for _, cluster := range router.clusters.Clusters { + for _, cluster := range router.clustersClient.GetClusters() { user, err := cluster.GetUsers(r.Context(), "") if err != nil { log.Error(r.Context(), "Could not get users.") @@ -183,7 +183,7 @@ func (router *Router) getTeam(w http.ResponseWriter, r *http.Request) { } // Register returns a new router which can be used in the router for the kobs rest api. -func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Config) chi.Router { +func Register(clustersClient clusters.Client, plugins *plugin.Plugins, config Config) chi.Router { plugins.Append(plugin.Plugin{ Name: "users", DisplayName: "Users", @@ -194,7 +194,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi router := Router{ chi.NewRouter(), - clusters, + clustersClient, config, }