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

[WIP] allow edit and delete node, move add node modal to separate file #4095

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
28352f8
implement 'IsHelmVM' function based on presence of configmap
laverya Oct 16, 2023
525272d
wip
laverya Oct 17, 2023
0baa6fa
node metrics
laverya Oct 17, 2023
3988f8f
add node pod capacity and list of pods
laverya Oct 17, 2023
bda5f13
remove debug log
laverya Oct 17, 2023
eed5e81
implement per-node metrics endpoint with podlist
laverya Oct 17, 2023
3644f9f
make mergable
laverya Oct 17, 2023
74fab33
order
laverya Oct 17, 2023
1b7c79b
generate a node join token (#4072)
laverya Oct 18, 2023
90ec02e
change node join command API to accept a list of roles
laverya Oct 18, 2023
982bc3e
Add view node page, add new add node modal (#4065)
alicenstar Oct 18, 2023
612c9ed
update tanstack query imports
alicenstar Oct 18, 2023
70559f1
shorter embedded-cluster join commands (#4075)
laverya Oct 18, 2023
b9a45d7
update percentages, add pods, fix link, show expiry (#4077)
alicenstar Oct 19, 2023
57b7ffc
fix routing, add missing slash (#4079)
alicenstar Oct 19, 2023
58b732d
remove test data, uncomment route protection, fix redirect after lice…
alicenstar Oct 19, 2023
8f4b6e8
24, not 60-something, character join tokens (#4080)
laverya Oct 19, 2023
ccd51bd
node usage metrics not being collected is not a fatal error (#4082)
laverya Oct 19, 2023
a681da3
include pod usage metrics (#4083)
laverya Oct 19, 2023
a7ee18d
remove pause and delete columns, update styles (#4085)
alicenstar Oct 19, 2023
661acfb
include 'sudo ./' at the beginning of the node join command (#4088)
laverya Oct 19, 2023
3db8c08
format pod CPU and memory usage before returning (#4086)
laverya Oct 19, 2023
621757e
fixes: clipboard, redirect, columns styling/formatting (#4090)
alicenstar Oct 19, 2023
f8b1336
determine node roles based on their labels (#4089)
laverya Oct 19, 2023
4dbcbd3
fix vet (#4084)
laverya Oct 19, 2023
f977068
cleanup the k0s join token creation pod after completion (#4091)
laverya Oct 19, 2023
40d416a
fix redirect, add column types (#4092)
alicenstar Oct 19, 2023
a8d9b84
make labels optional (#4094)
alicenstar Oct 19, 2023
de23afc
remove the --force flag (#4097)
laverya Oct 20, 2023
e79ea30
chore: change embedded cluster config map namespace (#4100)
diamonwiggins Oct 20, 2023
86058f3
update redirect to cluster manage page, comment test data (#4096)
alicenstar Oct 20, 2023
c13c2c2
node role labels (#4093)
laverya Oct 20, 2023
f5d63d8
fix config redirect, remove unnecessary code (#4104)
alicenstar Oct 20, 2023
370359c
wip: add test data for dev, start adding edit and delete buttons
alicenstar Oct 19, 2023
809bf00
move add node modal to separate file
alicenstar Oct 20, 2023
234e379
uncomment route protection
alicenstar Oct 20, 2023
cddf784
clean up
alicenstar Oct 20, 2023
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
21 changes: 21 additions & 0 deletions migrations/tables/k0s_tokens.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
name: k0s-tokens
spec:
name: k0s_tokens
requires: []
schema:
rqlite:
strict: true
primaryKey:
- token
columns:
- name: token
type: text
constraints:
notNull: true
- name: roles
type: text
constraints:
notNull: true
11 changes: 7 additions & 4 deletions pkg/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,16 +277,16 @@ func RegisterSessionAuthRoutes(r *mux.Router, kotsStore store.Store, handler KOT

// HelmVM
r.Name("HelmVM").Path("/api/v1/helmvm").HandlerFunc(NotImplemented)
r.Name("GenerateHelmVMNodeJoinCommandSecondary").Path("/api/v1/helmvm/generate-node-join-command-secondary").Methods("POST").
HandlerFunc(middleware.EnforceAccess(policy.ClusterWrite, handler.GenerateHelmVMNodeJoinCommandSecondary))
r.Name("GenerateHelmVMNodeJoinCommandPrimary").Path("/api/v1/helmvm/generate-node-join-command-primary").Methods("POST").
HandlerFunc(middleware.EnforceAccess(policy.ClusterWrite, handler.GenerateHelmVMNodeJoinCommandPrimary))
r.Name("GenerateK0sNodeJoinCommand").Path("/api/v1/helmvm/generate-node-join-command").Methods("POST").
HandlerFunc(middleware.EnforceAccess(policy.ClusterWrite, handler.GenerateK0sNodeJoinCommand))
r.Name("DrainHelmVMNode").Path("/api/v1/helmvm/nodes/{nodeName}/drain").Methods("POST").
HandlerFunc(middleware.EnforceAccess(policy.ClusterWrite, handler.DrainHelmVMNode))
r.Name("DeleteHelmVMNode").Path("/api/v1/helmvm/nodes/{nodeName}").Methods("DELETE").
HandlerFunc(middleware.EnforceAccess(policy.ClusterWrite, handler.DeleteHelmVMNode))
r.Name("GetHelmVMNodes").Path("/api/v1/helmvm/nodes").Methods("GET").
HandlerFunc(middleware.EnforceAccess(policy.ClusterRead, handler.GetHelmVMNodes))
r.Name("GetHelmVMNode").Path("/api/v1/helmvm/node/{nodeName}").Methods("GET").
HandlerFunc(middleware.EnforceAccess(policy.ClusterRead, handler.GetHelmVMNode))

// Prometheus
r.Name("SetPrometheusAddress").Path("/api/v1/prometheus").Methods("POST").
Expand Down Expand Up @@ -355,6 +355,9 @@ func RegisterUnauthenticatedRoutes(handler *Handler, kotsStore store.Store, debu
// These handlers should be called by the application only.
loggingRouter.Path("/license/v1/license").Methods("GET").HandlerFunc(handler.GetPlatformLicenseCompatibility)
loggingRouter.Path("/api/v1/app/custom-metrics").Methods("POST").HandlerFunc(handler.GetSendCustomAppMetricsHandler(kotsStore))

// This handler requires a valid token in the query
loggingRouter.Path("/api/v1/embedded-cluster/join").Methods("GET").HandlerFunc(handler.GetK0sNodeJoinCommand)
}

func RegisterLicenseIDAuthRoutes(r *mux.Router, kotsStore store.Store, handler KOTSHandler) {
Expand Down
31 changes: 21 additions & 10 deletions pkg/handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1210,54 +1210,65 @@ var HandlerPolicyTests = map[string][]HandlerPolicyTest{
},

"HelmVM": {}, // Not implemented
"GenerateHelmVMNodeJoinCommandSecondary": {
"GenerateK0sNodeJoinCommand": {
{
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
SessionRoles: []string{rbac.ClusterAdminRoleID},
Calls: func(storeRecorder *mock_store.MockStoreMockRecorder, handlerRecorder *mock_handlers.MockKOTSHandlerMockRecorder) {
handlerRecorder.GenerateHelmVMNodeJoinCommandSecondary(gomock.Any(), gomock.Any())
handlerRecorder.GenerateK0sNodeJoinCommand(gomock.Any(), gomock.Any())
},
ExpectStatus: http.StatusOK,
},
},
"GenerateHelmVMNodeJoinCommandPrimary": {
"DrainHelmVMNode": {
{
Vars: map[string]string{"nodeName": "node-name"},
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
SessionRoles: []string{rbac.ClusterAdminRoleID},
Calls: func(storeRecorder *mock_store.MockStoreMockRecorder, handlerRecorder *mock_handlers.MockKOTSHandlerMockRecorder) {
handlerRecorder.GenerateHelmVMNodeJoinCommandPrimary(gomock.Any(), gomock.Any())
handlerRecorder.DrainHelmVMNode(gomock.Any(), gomock.Any())
},
ExpectStatus: http.StatusOK,
},
},
"DrainHelmVMNode": {
"DeleteHelmVMNode": {
{
Vars: map[string]string{"nodeName": "node-name"},
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
SessionRoles: []string{rbac.ClusterAdminRoleID},
Calls: func(storeRecorder *mock_store.MockStoreMockRecorder, handlerRecorder *mock_handlers.MockKOTSHandlerMockRecorder) {
handlerRecorder.DrainHelmVMNode(gomock.Any(), gomock.Any())
handlerRecorder.DeleteHelmVMNode(gomock.Any(), gomock.Any())
},
ExpectStatus: http.StatusOK,
},
},
"DeleteHelmVMNode": {
"GetHelmVMNodes": {
{
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
SessionRoles: []string{rbac.ClusterAdminRoleID},
Calls: func(storeRecorder *mock_store.MockStoreMockRecorder, handlerRecorder *mock_handlers.MockKOTSHandlerMockRecorder) {
handlerRecorder.GetHelmVMNodes(gomock.Any(), gomock.Any())
},
ExpectStatus: http.StatusOK,
},
},
"GetHelmVMNode": {
{
Vars: map[string]string{"nodeName": "node-name"},
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
SessionRoles: []string{rbac.ClusterAdminRoleID},
Calls: func(storeRecorder *mock_store.MockStoreMockRecorder, handlerRecorder *mock_handlers.MockKOTSHandlerMockRecorder) {
handlerRecorder.DeleteHelmVMNode(gomock.Any(), gomock.Any())
handlerRecorder.GetHelmVMNode(gomock.Any(), gomock.Any())
},
ExpectStatus: http.StatusOK,
},
},
"GetHelmVMNodes": {
"GetK0sNodeJoinCommand": {
{
Roles: []rbactypes.Role{rbac.ClusterAdminRole},
SessionRoles: []string{rbac.ClusterAdminRoleID},
Calls: func(storeRecorder *mock_store.MockStoreMockRecorder, handlerRecorder *mock_handlers.MockKOTSHandlerMockRecorder) {
handlerRecorder.GetHelmVMNodes(gomock.Any(), gomock.Any())
handlerRecorder.GetK0sNodeJoinCommand(gomock.Any(), gomock.Any())
},
ExpectStatus: http.StatusOK,
},
Expand Down
21 changes: 20 additions & 1 deletion pkg/handlers/helmvm_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"net/http"

"github.com/gorilla/mux"
"github.com/replicatedhq/kots/pkg/helmvm"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/logger"
Expand All @@ -16,11 +17,29 @@ func (h *Handler) GetHelmVMNodes(w http.ResponseWriter, r *http.Request) {
return
}

nodes, err := helmvm.GetNodes(client)
nodes, err := helmvm.GetNodes(r.Context(), client)
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
JSON(w, http.StatusOK, nodes)
}

func (h *Handler) GetHelmVMNode(w http.ResponseWriter, r *http.Request) {
client, err := k8sutil.GetClientset()
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

nodeName := mux.Vars(r)["nodeName"]
node, err := helmvm.GetNode(r.Context(), client, nodeName)
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
JSON(w, http.StatusOK, node)
}
97 changes: 79 additions & 18 deletions pkg/handlers/helmvm_node_join_command.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,116 @@
package handlers

import (
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/replicatedhq/kots/pkg/helmvm"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/store/kotsstore"
)

type GenerateHelmVMNodeJoinCommandResponse struct {
type GenerateK0sNodeJoinCommandResponse struct {
Command []string `json:"command"`
Expiry string `json:"expiry"`
}

func (h *Handler) GenerateHelmVMNodeJoinCommandSecondary(w http.ResponseWriter, r *http.Request) {
client, err := k8sutil.GetClientset()
type GetK0sNodeJoinCommandResponse struct {
ClusterID string `json:"clusterID"`
K0sJoinCommand string `json:"k0sJoinCommand"`
K0sToken string `json:"k0sToken"`
}

type GenerateHelmVMNodeJoinCommandRequest struct {
Roles []string `json:"roles"`
}

func (h *Handler) GenerateK0sNodeJoinCommand(w http.ResponseWriter, r *http.Request) {
generateHelmVMNodeJoinCommandRequest := GenerateHelmVMNodeJoinCommandRequest{}
if err := json.NewDecoder(r.Body).Decode(&generateHelmVMNodeJoinCommandRequest); err != nil {
logger.Error(fmt.Errorf("failed to decode request body: %w", err))
w.WriteHeader(http.StatusBadRequest)
return
}

store := kotsstore.StoreFromEnv()
token, err := store.SetK0sInstallCommandRoles(generateHelmVMNodeJoinCommandRequest.Roles)
if err != nil {
logger.Error(err)
logger.Error(fmt.Errorf("failed to set k0s install command roles: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

command, expiry, err := helmvm.GenerateAddNodeCommand(client, false)
client, err := k8sutil.GetClientset()
if err != nil {
logger.Error(fmt.Errorf("failed to get clientset: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}
nodeJoinCommand, err := helmvm.GenerateAddNodeCommand(r.Context(), client, token)
if err != nil {
logger.Error(err)
logger.Error(fmt.Errorf("failed to generate add node command: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}
JSON(w, http.StatusOK, GenerateHelmVMNodeJoinCommandResponse{
Command: command,
Expiry: expiry.Format(time.RFC3339),

JSON(w, http.StatusOK, GenerateK0sNodeJoinCommandResponse{
Command: []string{nodeJoinCommand},
})
}

func (h *Handler) GenerateHelmVMNodeJoinCommandPrimary(w http.ResponseWriter, r *http.Request) {
// this function relies on the token being valid for authentication
func (h *Handler) GetK0sNodeJoinCommand(w http.ResponseWriter, r *http.Request) {
// read query string, ensure that the token is valid
token := r.URL.Query().Get("token")
store := kotsstore.StoreFromEnv()
roles, err := store.GetK0sInstallCommandRoles(token)
if err != nil {
logger.Error(fmt.Errorf("failed to get k0s install command roles: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

// use roles to generate join token etc
client, err := k8sutil.GetClientset()
if err != nil {
logger.Error(err)
logger.Error(fmt.Errorf("failed to get clientset: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

k0sRole := "worker"
for _, role := range roles {
if role == "controller" {
k0sRole = "controller"
break
}
}

k0sToken, err := helmvm.GenerateAddNodeToken(r.Context(), client, k0sRole)
if err != nil {
logger.Error(fmt.Errorf("failed to generate add node token: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

k0sJoinCommand, err := helmvm.GenerateK0sJoinCommand(r.Context(), client, roles)
if err != nil {
logger.Error(fmt.Errorf("failed to generate k0s join command: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}

command, expiry, err := helmvm.GenerateAddNodeCommand(client, true)
clusterID, err := helmvm.ClusterID(client)
if err != nil {
logger.Error(err)
logger.Error(fmt.Errorf("failed to get cluster id: %w", err))
w.WriteHeader(http.StatusInternalServerError)
return
}
JSON(w, http.StatusOK, GenerateHelmVMNodeJoinCommandResponse{
Command: command,
Expiry: expiry.Format(time.RFC3339),

JSON(w, http.StatusOK, GetK0sNodeJoinCommandResponse{
ClusterID: clusterID,
K0sJoinCommand: k0sJoinCommand,
K0sToken: k0sToken,
})
}
5 changes: 3 additions & 2 deletions pkg/handlers/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ type KOTSHandler interface {
GetKurlNodes(w http.ResponseWriter, r *http.Request)

// HelmVM
GenerateHelmVMNodeJoinCommandSecondary(w http.ResponseWriter, r *http.Request)
GenerateHelmVMNodeJoinCommandPrimary(w http.ResponseWriter, r *http.Request)
GenerateK0sNodeJoinCommand(w http.ResponseWriter, r *http.Request)
DrainHelmVMNode(w http.ResponseWriter, r *http.Request)
DeleteHelmVMNode(w http.ResponseWriter, r *http.Request)
GetHelmVMNodes(w http.ResponseWriter, r *http.Request)
GetHelmVMNode(w http.ResponseWriter, r *http.Request)
GetK0sNodeJoinCommand(w http.ResponseWriter, r *http.Request)

// Prometheus
SetPrometheusAddress(w http.ResponseWriter, r *http.Request)
Expand Down
48 changes: 30 additions & 18 deletions pkg/handlers/mock/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading