Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: switch currently viewed cluster #499

Merged
merged 53 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7efb2eb
feat: switch currently viewed cluster
TristanHoladay Oct 24, 2024
be51552
wip
TristanHoladay Oct 25, 2024
432bc76
merge main and resolve
TristanHoladay Oct 25, 2024
53d5167
wip
TristanHoladay Oct 25, 2024
b5778e3
initial reactivity
TristanHoladay Oct 25, 2024
cc88845
implement switch handling
TristanHoladay Oct 25, 2024
df10ec4
wip
TristanHoladay Oct 28, 2024
161a331
add context name to displayed cluster name
TristanHoladay Oct 28, 2024
737f768
refactor client.NewForContext into just client.New
TristanHoladay Oct 28, 2024
8136952
wip
TristanHoladay Oct 28, 2024
1718ebf
adding loading cluster and error page
TristanHoladay Oct 28, 2024
0f65213
wip
TristanHoladay Oct 28, 2024
e0acd7e
Merge branch 'main' into cluster-switch
TristanHoladay Oct 29, 2024
dcab3f3
wip
TristanHoladay Oct 29, 2024
ef145af
generating docs
TristanHoladay Oct 29, 2024
0d71455
working on UI cluster menu switch test
TristanHoladay Oct 29, 2024
6995e52
Merge branch 'main' into cluster-switch
TristanHoladay Oct 29, 2024
bd2e397
ensure at least 3 seconds for loading screen to show
TristanHoladay Oct 29, 2024
84a3685
Merge branch 'main' into cluster-switch
TristanHoladay Oct 29, 2024
adde131
wip
TristanHoladay Oct 29, 2024
0de6399
refactors
TristanHoladay Oct 30, 2024
9df9702
update docs and fix test
TristanHoladay Oct 30, 2024
05e44ae
test troubleshooting
TristanHoladay Oct 30, 2024
219708f
add to error view
TristanHoladay Oct 30, 2024
9a33115
fix unit test
TristanHoladay Oct 30, 2024
4f774ea
Merge branch 'main' into cluster-switch
TristanHoladay Oct 30, 2024
ebe00dc
get cluster overview with latest cache
TristanHoladay Oct 30, 2024
f242bae
add switch cluster error test
TristanHoladay Oct 30, 2024
f791717
fix dropdown style
TristanHoladay Oct 31, 2024
2c0f950
adding tests
TristanHoladay Oct 31, 2024
2de7b7b
Merge branch 'main' into cluster-switch
TristanHoladay Oct 31, 2024
f06d974
fixing reconnection and tests
TristanHoladay Oct 31, 2024
015eb45
wip on e2e
TristanHoladay Oct 31, 2024
710cc00
e2e tests
TristanHoladay Nov 1, 2024
f0aef43
Merge branch 'main' into cluster-switch
TristanHoladay Nov 1, 2024
6d22cc6
update test exclusions from other configs
TristanHoladay Nov 1, 2024
b2b02aa
fix api test
TristanHoladay Nov 1, 2024
2279150
remove unneeded struct in test
TristanHoladay Nov 1, 2024
fb247a3
update comment and text color
TristanHoladay Nov 4, 2024
fa3588e
Merge branch 'main' into cluster-switch
TristanHoladay Nov 4, 2024
8cd5e33
merge main and resolve
TristanHoladay Nov 4, 2024
37253c6
prevent indefinite waiting in cache sync when client creates but conn…
TristanHoladay Nov 4, 2024
037e32f
merge and resolve
TristanHoladay Nov 4, 2024
8509fed
fix e2e and add test for switching to stopped cluster
TristanHoladay Nov 4, 2024
488821d
update ignores for playwright configs
TristanHoladay Nov 4, 2024
bc6fb96
wip
TristanHoladay Nov 4, 2024
a167734
dropdown refactors and cloudoffline icon
TristanHoladay Nov 5, 2024
3abcecf
Merge branch 'main' into cluster-switch
TristanHoladay Nov 5, 2024
54ee405
lint fix
TristanHoladay Nov 5, 2024
77b4244
update K8sSession even when switch fails to force selecting a differe…
TristanHoladay Nov 6, 2024
35fd0a6
update e2e test
TristanHoladay Nov 6, 2024
8349988
Merge branch 'main' into cluster-switch
TristanHoladay Nov 6, 2024
1655586
header and return updates
TristanHoladay Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/pkg/api/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,52 @@ const docTemplate = `{
}
}
},
"/api/v1/cluster": {
"post": {
"description": "Swith the currently viewed cluster",
"consumes": [
"application/json"
],
"tags": [
"cluster"
],
"parameters": [
{
"description": "example body: {",
"name": "request",
"in": "body",
"required": true,
"schema": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/client.ClusterInfo"
}
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/clusters": {
"get": {
"description": "get list of clusters with context names from local kubeconfig",
"produces": [
"application/json"
],
"tags": [
"clusters"
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/resources/cluster-ops/hpas": {
"get": {
"description": "Get HPAs",
Expand Down Expand Up @@ -3109,6 +3155,22 @@ const docTemplate = `{
}
}
}
},
"definitions": {
"client.ClusterInfo": {
"type": "object",
"properties": {
"context": {
"type": "string"
},
"name": {
"type": "string"
},
"selected": {
"type": "boolean"
}
}
}
}
}`

Expand Down
62 changes: 62 additions & 0 deletions src/pkg/api/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,52 @@
}
}
},
"/api/v1/cluster": {
"post": {
"description": "Swith the currently viewed cluster",
"consumes": [
"application/json"
],
"tags": [
"cluster"
],
"parameters": [
{
"description": "example body: {",
"name": "request",
"in": "body",
"required": true,
"schema": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/client.ClusterInfo"
}
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/clusters": {
"get": {
"description": "get list of clusters with context names from local kubeconfig",
"produces": [
"application/json"
],
"tags": [
"clusters"
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/resources/cluster-ops/hpas": {
"get": {
"description": "Get HPAs",
Expand Down Expand Up @@ -3098,5 +3144,21 @@
}
}
}
},
"definitions": {
"client.ClusterInfo": {
"type": "object",
"properties": {
"context": {
"type": "string"
},
"name": {
"type": "string"
},
"selected": {
"type": "boolean"
}
}
}
}
}
39 changes: 39 additions & 0 deletions src/pkg/api/docs/swagger.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
definitions:
client.ClusterInfo:
properties:
context:
type: string
name:
type: string
selected:
type: boolean
type: object
info:
contact: {}
paths:
Expand All @@ -10,6 +20,35 @@ paths:
description: OK
tags:
- auth
/api/v1/cluster:
post:
consumes:
- application/json
description: Swith the currently viewed cluster
parameters:
- description: 'example body: {'
in: body
name: request
required: true
schema:
additionalProperties:
$ref: '#/definitions/client.ClusterInfo'
type: object
responses:
"200":
description: OK
tags:
- cluster
/api/v1/clusters:
get:
description: get list of clusters with context names from local kubeconfig
produces:
- application/json
responses:
"200":
description: OK
tags:
- clusters
/api/v1/resources/cluster-ops/hpas:
get:
consumes:
Expand Down
20 changes: 20 additions & 0 deletions src/pkg/api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
_ "github.com/defenseunicorns/uds-runtime/src/pkg/api/docs" //nolint:staticcheck
"github.com/defenseunicorns/uds-runtime/src/pkg/api/resources"
"github.com/defenseunicorns/uds-runtime/src/pkg/api/rest"
"github.com/defenseunicorns/uds-runtime/src/pkg/k8s/client"
"github.com/defenseunicorns/uds-runtime/src/pkg/k8s/session"
)

Expand Down Expand Up @@ -935,3 +936,22 @@ func healthz(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}

// @Description get list of clusters with context names from local kubeconfig
// @Tags clusters
// @Produce json
// @Success 200
// @Router /api/v1/clusters [get]
func getClusters(ks *session.K8sSession) func(w http.ResponseWriter, r *http.Request) {
return client.ServeClusters(ks.InCluster, ks.CurrentCtx)
}

// @Description Switch the currently viewed cluster
// @Tags cluster
// @Param request body map[string]client.ClusterInfo true "example body: {"cluster": {"name": string, "context": string, "selected": bool}}"
// @Accept json
// @Success 200
// @Router /api/v1/cluster [post]
func switchCluster(ks *session.K8sSession) func(w http.ResponseWriter, r *http.Request) {
return ks.Switch()
}
8 changes: 8 additions & 0 deletions src/pkg/api/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ func Setup(assets *embed.FS) (*chi.Mux, bool, error) {
r.Get("/cluster-check", checkClusterConnection(k8sSession))
r.Route("/api/v1", func(r chi.Router) {
r.Get("/auth", authHandler)
r.Get("/clusters", withLatestSession(k8sSession, getClusters))
r.Post("/cluster", switchCluster(k8sSession))
r.Route("/monitor", func(r chi.Router) {
r.Get("/pepr/", monitor.Pepr)
r.Get("/pepr/{stream}", monitor.Pepr)
Expand Down Expand Up @@ -304,3 +306,9 @@ func withLatestCache(k8sSession *session.K8sSession, handler func(cache *resourc
handler(k8sSession.Cache)(w, r)
}
}

func withLatestSession(k8sSession *session.K8sSession, handler func(k8sSession *session.K8sSession) func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
TristanHoladay marked this conversation as resolved.
Show resolved Hide resolved
return func(w http.ResponseWriter, r *http.Request) {
handler(k8sSession)(w, r)
}
}
104 changes: 87 additions & 17 deletions src/pkg/k8s/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
package client

import (
"encoding/json"
"fmt"
"log/slog"
"net/http"

"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
TristanHoladay marked this conversation as resolved.
Show resolved Hide resolved
metricsv "k8s.io/metrics/pkg/client/clientset/versioned"
)

Expand All @@ -19,11 +23,17 @@ type Clients struct {
Config *rest.Config
}

// NewClient creates new Kubernetes cluster clients
func NewClient() (*Clients, error) {
type ClusterInfo struct {
Name string `json:"name"`
Context string `json:"context"`
Selected bool `json:"selected"`
}

// New creates new Kubernetes cluster clients with the option of overriding the default config
TristanHoladay marked this conversation as resolved.
Show resolved Hide resolved
func New(overrides *clientcmd.ConfigOverrides) (*Clients, error) {
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.NewDefaultClientConfigLoadingRules(),
&clientcmd.ConfigOverrides{}).ClientConfig()
overrides).ClientConfig()

if err != nil {
return nil, err
Expand All @@ -46,24 +56,22 @@ func NewClient() (*Clients, error) {
}, nil
}

// IsRunningInCluster checks if the application is running in cluster
func IsRunningInCluster() (bool, error) {
_, err := rest.InClusterConfig()
func rawConfig() (clientcmdapi.Config, error) {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{}).RawConfig()
}

if err == rest.ErrNotInCluster {
return false, nil
} else if err != nil {
return true, err
func Contexts() (map[string]*clientcmdapi.Context, error) {
config, err := rawConfig()
if err != nil {
return nil, err
}

return true, nil
return config.Contexts, nil
}

// Declare GetCurrentContext as a variable so it can be mocked
var GetCurrentContext = func() (string, string, error) {
// Actual implementation of GetCurrentContext
rules := clientcmd.NewDefaultClientConfigLoadingRules()
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{}).RawConfig()
// Declare CurrentContext as a variable so it can be mocked
var CurrentContext = func() (string, string, error) {
config, err := rawConfig()
if err != nil {
return "", "", err
}
Expand All @@ -74,3 +82,65 @@ var GetCurrentContext = func() (string, string, error) {
}
return contextName, context.Cluster, nil
}

func ServeClusters(inCluster bool, currentContext string) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/json; charset=utf-8")
TristanHoladay marked this conversation as resolved.
Show resolved Hide resolved
w.Header().Set("Cache-Control", "no-cache")

// running in cluster return []
if inCluster {
data, err := json.Marshal([]interface{}{})
if err != nil {
slog.Error("Failed to marshal empty data", "error", err)
http.Error(w, "JSON marshalling error", http.StatusInternalServerError)
TristanHoladay marked this conversation as resolved.
Show resolved Hide resolved
}

// Write the data to the response
if _, err := w.Write(data); err != nil {
http.Error(w, "Failed to write data", http.StatusInternalServerError)
return
}
TristanHoladay marked this conversation as resolved.
Show resolved Hide resolved
}

contexts, err := Contexts()
if err != nil {
slog.Error("Failed to get contexts", "error", err)
http.Error(w, "Failed to get contexts", http.StatusInternalServerError)
return
}

slog.Debug("ServeClusters called", "inCluster", inCluster, "currentContext", currentContext)
TristanHoladay marked this conversation as resolved.
Show resolved Hide resolved

var clusters []ClusterInfo
for ctxName, context := range contexts {
clusters = append(clusters, ClusterInfo{Name: context.Cluster, Context: ctxName, Selected: currentContext == ctxName})
}

data, err := json.Marshal(clusters)
if err != nil {
slog.Error("Failed to marshal contexts", "error", err)
http.Error(w, "JSON marshalling error", http.StatusInternalServerError)
return
}

// Write the data to the response
if _, err := w.Write(data); err != nil {
http.Error(w, "Failed to write data", http.StatusInternalServerError)
return
}
}
}

// IsRunningInCluster checks if the application is running in cluster
func IsRunningInCluster() (bool, error) {
_, err := rest.InClusterConfig()

if err == rest.ErrNotInCluster {
return false, nil
} else if err != nil {
return true, err
}

return true, nil
TristanHoladay marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading