From 148bd4d9979d01b7b70963cedb0f73bf15ce7283 Mon Sep 17 00:00:00 2001 From: Matt Hook Date: Fri, 13 Oct 2023 13:43:36 +1300 Subject: [PATCH] chore:(kubeclient): refactor kubeclient middleware and endpoints [EE-5028] (#10423) --- .../kubernetes/configmaps_and_secrets.go | 32 +--- api/http/handler/kubernetes/handler.go | 144 +++++++-------- api/http/handler/kubernetes/ingresses.go | 170 ++++-------------- api/http/handler/kubernetes/metrics.go | 120 +++---------- api/http/handler/kubernetes/namespaces.go | 143 ++++----------- api/http/handler/kubernetes/nodes_limits.go | 34 +--- api/http/handler/kubernetes/rbac.go | 23 +-- api/http/handler/kubernetes/services.go | 129 +++---------- 8 files changed, 194 insertions(+), 601 deletions(-) diff --git a/api/http/handler/kubernetes/configmaps_and_secrets.go b/api/http/handler/kubernetes/configmaps_and_secrets.go index 5774df5f4..881e1235d 100644 --- a/api/http/handler/kubernetes/configmaps_and_secrets.go +++ b/api/http/handler/kubernetes/configmaps_and_secrets.go @@ -2,7 +2,6 @@ package kubernetes import ( "net/http" - "strconv" httperror "github.com/portainer/portainer/pkg/libhttp/error" "github.com/portainer/portainer/pkg/libhttp/request" @@ -10,38 +9,19 @@ import ( ) func (handler *Handler) getKubernetesConfigMapsAndSecrets(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } - namespace, err := request.RetrieveRouteVariableValue(r, "namespace") - if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } configmaps, err := cli.GetConfigMapsAndSecrets(namespace) if err != nil { - return httperror.InternalServerError( - "Unable to retrieve configmaps and secrets", - err, - ) + return httperror.InternalServerError("Unable to retrieve configmaps and secrets", err) } return response.JSON(w, configmaps) diff --git a/api/http/handler/kubernetes/handler.go b/api/http/handler/kubernetes/handler.go index 7efa0bb6b..6025135ce 100644 --- a/api/http/handler/kubernetes/handler.go +++ b/api/http/handler/kubernetes/handler.go @@ -50,24 +50,24 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza endpointRouter := kubeRouter.PathPrefix("/{id}").Subrouter() endpointRouter.Use(middlewares.WithEndpoint(dataStore.Endpoint(), "id")) endpointRouter.Use(kubeOnlyMiddleware) - endpointRouter.Use(h.kubeClient) - - endpointRouter.PathPrefix("/nodes_limits").Handler(httperror.LoggerHandler(h.getKubernetesNodesLimits)).Methods(http.MethodGet) - endpointRouter.PathPrefix("/max_resource_limits").Handler(httperror.LoggerHandler(h.getKubernetesMaxResourceLimits)).Methods(http.MethodGet) - endpointRouter.Path("/metrics/nodes").Handler(httperror.LoggerHandler(h.getKubernetesMetricsForAllNodes)).Methods(http.MethodGet) - endpointRouter.Path("/metrics/nodes/{name}").Handler(httperror.LoggerHandler(h.getKubernetesMetricsForNode)).Methods(http.MethodGet) - endpointRouter.Path("/metrics/pods/namespace/{namespace}").Handler(httperror.LoggerHandler(h.getKubernetesMetricsForAllPods)).Methods(http.MethodGet) - endpointRouter.Path("/metrics/pods/namespace/{namespace}/{name}").Handler(httperror.LoggerHandler(h.getKubernetesMetricsForPod)).Methods(http.MethodGet) + endpointRouter.Use(h.kubeClientMiddleware) + + endpointRouter.Handle("/nodes_limits", httperror.LoggerHandler(h.getKubernetesNodesLimits)).Methods(http.MethodGet) + endpointRouter.Handle("/max_resource_limits", httperror.LoggerHandler(h.getKubernetesMaxResourceLimits)).Methods(http.MethodGet) + endpointRouter.Handle("/metrics/nodes", httperror.LoggerHandler(h.getKubernetesMetricsForAllNodes)).Methods(http.MethodGet) + endpointRouter.Handle("/metrics/nodes/{name}", httperror.LoggerHandler(h.getKubernetesMetricsForNode)).Methods(http.MethodGet) + endpointRouter.Handle("/metrics/pods/namespace/{namespace}", httperror.LoggerHandler(h.getKubernetesMetricsForAllPods)).Methods(http.MethodGet) + endpointRouter.Handle("/metrics/pods/namespace/{namespace}/{name}", httperror.LoggerHandler(h.getKubernetesMetricsForPod)).Methods(http.MethodGet) endpointRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.getKubernetesIngressControllers)).Methods(http.MethodGet) endpointRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.updateKubernetesIngressControllers)).Methods(http.MethodPut) endpointRouter.Handle("/ingresses/delete", httperror.LoggerHandler(h.deleteKubernetesIngresses)).Methods(http.MethodPost) endpointRouter.Handle("/services/delete", httperror.LoggerHandler(h.deleteKubernetesServices)).Methods(http.MethodPost) - endpointRouter.Path("/rbac_enabled").Handler(httperror.LoggerHandler(h.isRBACEnabled)).Methods(http.MethodGet) - endpointRouter.Path("/namespaces").Handler(httperror.LoggerHandler(h.createKubernetesNamespace)).Methods(http.MethodPost) - endpointRouter.Path("/namespaces").Handler(httperror.LoggerHandler(h.updateKubernetesNamespace)).Methods(http.MethodPut) - endpointRouter.Path("/namespaces").Handler(httperror.LoggerHandler(h.getKubernetesNamespaces)).Methods(http.MethodGet) - endpointRouter.Path("/namespace/{namespace}").Handler(httperror.LoggerHandler(h.deleteKubernetesNamespace)).Methods(http.MethodDelete) - endpointRouter.Path("/namespaces/{namespace}").Handler(httperror.LoggerHandler(h.getKubernetesNamespace)).Methods(http.MethodGet) + endpointRouter.Handle("/rbac_enabled", httperror.LoggerHandler(h.isRBACEnabled)).Methods(http.MethodGet) + endpointRouter.Handle("/namespaces", httperror.LoggerHandler(h.createKubernetesNamespace)).Methods(http.MethodPost) + endpointRouter.Handle("/namespaces", httperror.LoggerHandler(h.updateKubernetesNamespace)).Methods(http.MethodPut) + endpointRouter.Handle("/namespaces", httperror.LoggerHandler(h.getKubernetesNamespaces)).Methods(http.MethodGet) + endpointRouter.Handle("/namespace/{namespace}", httperror.LoggerHandler(h.deleteKubernetesNamespace)).Methods(http.MethodDelete) + endpointRouter.Handle("/namespaces/{namespace}", httperror.LoggerHandler(h.getKubernetesNamespace)).Methods(http.MethodGet) // namespaces // in the future this piece of code might be in another package (or a few different packages - namespaces/namespace?) @@ -111,94 +111,81 @@ func kubeOnlyMiddleware(next http.Handler) http.Handler { }) } -func (handler *Handler) kubeClient(next http.Handler) http.Handler { +// getProxyKubeClient gets a kubeclient for the user. It's generally what you want as it retrieves the kubeclient +// from the Authorization token of the currently logged in user. The kubeclient that is not from the proxy is actually using +// admin permissions. If you're unsure which one to use, use this. +func (h *Handler) getProxyKubeClient(r *http.Request) (*cli.KubeClient, *httperror.HandlerError) { + endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + if err != nil { + return nil, httperror.BadRequest("Invalid environment identifier route variable", err) + } + + cli, ok := h.KubernetesClientFactory.GetProxyKubeClient(strconv.Itoa(endpointID), r.Header.Get("Authorization")) + if !ok { + return nil, httperror.InternalServerError("Failed to lookup KubeClient", nil) + } + + return cli, nil +} + +func (handler *Handler) kubeClientMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if handler.KubernetesClientFactory == nil { + next.ServeHTTP(w, r) + return + } + endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") if err != nil { - httperror.WriteError( - w, - http.StatusBadRequest, - "Invalid environment identifier route variable", - err, - ) + httperror.WriteError(w, http.StatusBadRequest, "Invalid environment identifier route variable", err) return } - endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) - if handler.DataStore.IsErrObjectNotFound(err) { - httperror.WriteError( - w, - http.StatusNotFound, - "Unable to find an environment with the specified identifier inside the database", - err, - ) - return - } else if err != nil { - httperror.WriteError( - w, - http.StatusInternalServerError, - "Unable to find an environment with the specified identifier inside the database", - err, - ) + // Check if we have a kubeclient against this auth token already, otherwise generate a new one + _, ok := handler.KubernetesClientFactory.GetProxyKubeClient(strconv.Itoa(endpointID), r.Header.Get("Authorization")) + if ok { + next.ServeHTTP(w, r) return } - if handler.KubernetesClientFactory == nil { - next.ServeHTTP(w, r) + endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) + if err != nil { + if handler.DataStore.IsErrObjectNotFound(err) { + httperror.WriteError( + w, + http.StatusNotFound, + "Unable to find an environment with the specified identifier inside the database", + err, + ) + return + } + + httperror.WriteError(w, http.StatusInternalServerError, "Unable to read the environment from the database", err) return } + // Generate a proxied kubeconfig, then create a kubeclient using it. tokenData, err := security.RetrieveTokenData(r) if err != nil { - httperror.WriteError( - w, - http.StatusForbidden, - "Permission denied to access environment", - err, - ) + httperror.WriteError(w, http.StatusForbidden, "Permission denied to access environment", err) return } bearerToken, err := handler.JwtService.GenerateTokenForKubeconfig(tokenData) if err != nil { - httperror.WriteError( - w, - http.StatusInternalServerError, - "Unable to create JWT token", - err, - ) + httperror.WriteError(w, http.StatusInternalServerError, "Unable to create JWT token", err) return } - singleEndpointList := []portainer.Endpoint{ - *endpoint, - } - config := handler.buildConfig( - r, - tokenData, - bearerToken, - singleEndpointList, - true, - ) + config := handler.buildConfig(r, tokenData, bearerToken, []portainer.Endpoint{*endpoint}, true) if len(config.Clusters) == 0 { - httperror.WriteError( - w, - http.StatusInternalServerError, - "Unable build cluster kubeconfig", - errors.New("Unable build cluster kubeconfig"), - ) + httperror.WriteError(w, http.StatusInternalServerError, "Unable build cluster kubeconfig", nil) return } - // Manually setting the localhost to route - // the request to proxy server + // Manually setting serverURL to localhost to route the request to proxy server serverURL, err := url.Parse(config.Clusters[0].Cluster.Server) if err != nil { - httperror.WriteError( - w, - http.StatusInternalServerError, - "Unable parse cluster's kubeconfig server URL", - nil, - ) + httperror.WriteError(w, http.StatusInternalServerError, "Unable parse cluster's kubeconfig server URL", nil) return } serverURL.Scheme = "https" @@ -217,12 +204,7 @@ func (handler *Handler) kubeClient(next http.Handler) http.Handler { } kubeCli, err := handler.KubernetesClientFactory.CreateKubeClientFromKubeConfig(endpoint.Name, []byte(yaml)) if err != nil { - httperror.WriteError( - w, - http.StatusInternalServerError, - "Failed to create client from kubeconfig", - err, - ) + httperror.WriteError(w, http.StatusInternalServerError, "Failed to create client from kubeconfig", err) return } diff --git a/api/http/handler/kubernetes/ingresses.go b/api/http/handler/kubernetes/ingresses.go index ad3428279..2ddd5073a 100644 --- a/api/http/handler/kubernetes/ingresses.go +++ b/api/http/handler/kubernetes/ingresses.go @@ -5,6 +5,7 @@ import ( "strconv" portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/http/middlewares" models "github.com/portainer/portainer/api/http/models/kubernetes" "github.com/portainer/portainer/api/http/security" httperror "github.com/portainer/portainer/pkg/libhttp/error" @@ -396,42 +397,20 @@ func (handler *Handler) updateKubernetesIngressControllers(w http.ResponseWriter // @failure 500 "Server error" // @router /kubernetes/{id}/namespaces/{namespace}/ingresscontrollers [put] func (handler *Handler) updateKubernetesIngressControllersByNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + endpoint, err := middlewares.FetchEndpoint(r) if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) - if handler.DataStore.IsErrObjectNotFound(err) { - return httperror.NotFound( - "Unable to find an environment with the specified identifier inside the database", - err, - ) - } else if err != nil { - return httperror.InternalServerError( - "Unable to find an environment with the specified identifier inside the database", - err, - ) + return httperror.NotFound("Unable to find an environment on request context", err) } namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } var payload models.K8sIngressControllers err = request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { - return httperror.BadRequest( - "Invalid request payload", - err, - ) + return httperror.BadRequest("Invalid request payload", err) } existingClasses := endpoint.Kubernetes.Configuration.IngressClasses @@ -497,18 +476,13 @@ PayloadLoop: updatedClasses = append(updatedClasses, existingClass) } } - endpoint.Kubernetes.Configuration.IngressClasses = updatedClasses - err = handler.DataStore.Endpoint().UpdateEndpoint( - portainer.EndpointID(endpointID), - endpoint, - ) + + err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint) if err != nil { - return httperror.InternalServerError( - "Unable to update the BlockedIngressClasses inside the database", - err, - ) + return httperror.InternalServerError("Unable to update the BlockedIngressClasses inside the database", err) } + return response.Empty(w) } @@ -531,36 +505,17 @@ PayloadLoop: func (handler *Handler) getKubernetesIngresses(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } ingresses, err := cli.GetIngresses(namespace) if err != nil { - return httperror.InternalServerError( - "Unable to retrieve ingresses", - err, - ) + return httperror.InternalServerError("Unable to retrieve ingresses", err) } return response.JSON(w, ingresses) @@ -585,27 +540,13 @@ func (handler *Handler) getKubernetesIngresses(w http.ResponseWriter, r *http.Re func (handler *Handler) createKubernetesIngress(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } var payload models.K8sIngressInfo err = request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { - return httperror.BadRequest( - "Invalid request payload", - err, - ) - } - - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) + return httperror.BadRequest("Invalid request payload", err) } owner := "admin" @@ -614,23 +555,16 @@ func (handler *Handler) createKubernetesIngress(w http.ResponseWriter, r *http.R owner = tokenData.Username } - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } err = cli.CreateIngress(namespace, payload, owner) if err != nil { - return httperror.InternalServerError( - "Unable to retrieve the ingress", - err, - ) + return httperror.InternalServerError("Unable to retrieve the ingress", err) } + return response.Empty(w) } @@ -650,37 +584,22 @@ func (handler *Handler) createKubernetesIngress(w http.ResponseWriter, r *http.R // @failure 500 "Server error" // @router /kubernetes/{id}/ingresses/delete [post] func (handler *Handler) deleteKubernetesIngresses(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } var payload models.K8sIngressDeleteRequests - err = request.DecodeAndValidateJSONPayload(r, &payload) + err := request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { return httperror.BadRequest("Invalid request payload", err) } err = cli.DeleteIngresses(payload) if err != nil { - return httperror.InternalServerError( - "Unable to delete ingresses", - err, - ) + return httperror.InternalServerError("Unable to delete ingresses", err) } + return response.Empty(w) } @@ -703,45 +622,24 @@ func (handler *Handler) deleteKubernetesIngresses(w http.ResponseWriter, r *http func (handler *Handler) updateKubernetesIngress(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } var payload models.K8sIngressInfo err = request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { - return httperror.BadRequest( - "Invalid request payload", - err, - ) - } - - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) + return httperror.BadRequest("Invalid request payload", err) } - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } err = cli.UpdateIngress(namespace, payload) if err != nil { - return httperror.InternalServerError( - "Unable to update the ingress", - err, - ) + return httperror.InternalServerError("Unable to update the ingress", err) } + return response.Empty(w) } diff --git a/api/http/handler/kubernetes/metrics.go b/api/http/handler/kubernetes/metrics.go index e771f3376..bacc02d4d 100644 --- a/api/http/handler/kubernetes/metrics.go +++ b/api/http/handler/kubernetes/metrics.go @@ -3,7 +3,7 @@ package kubernetes import ( "net/http" - portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/http/middlewares" httperror "github.com/portainer/portainer/pkg/libhttp/error" "github.com/portainer/portainer/pkg/libhttp/request" "github.com/portainer/portainer/pkg/libhttp/response" @@ -26,34 +26,19 @@ import ( // @failure 500 "Server error" // @router /kubernetes/{id}/metrics/nodes [get] func (handler *Handler) getKubernetesMetricsForAllNodes(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + endpoint, err := middlewares.FetchEndpoint(r) if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) - if handler.DataStore.IsErrObjectNotFound(err) { - return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) - } else if err != nil { - return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + return httperror.InternalServerError(err.Error(), err) } cli, err := handler.KubernetesClientFactory.CreateRemoteMetricsClient(endpoint) if err != nil { - return httperror.InternalServerError( - "failed to create metrics KubeClient", - nil, - ) + return httperror.InternalServerError("failed to create metrics KubeClient", nil) } metrics, err := cli.MetricsV1beta1().NodeMetricses().List(r.Context(), v1.ListOptions{}) if err != nil { - return httperror.InternalServerError( - "Failed to fetch metrics", - err, - ) + return httperror.InternalServerError("Failed to fetch metrics", err) } return response.JSON(w, metrics) @@ -75,34 +60,19 @@ func (handler *Handler) getKubernetesMetricsForAllNodes(w http.ResponseWriter, r // @failure 500 "Server error" // @router /kubernetes/{id}/metrics/nodes/{name} [get] func (handler *Handler) getKubernetesMetricsForNode(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + endpoint, err := middlewares.FetchEndpoint(r) if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) - if handler.DataStore.IsErrObjectNotFound(err) { - return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) - } else if err != nil { - return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + return httperror.InternalServerError(err.Error(), err) } cli, err := handler.KubernetesClientFactory.CreateRemoteMetricsClient(endpoint) if err != nil { - return httperror.InternalServerError( - "failed to create metrics KubeClient", - nil, - ) + return httperror.InternalServerError("failed to create metrics KubeClient", nil) } nodeName, err := request.RetrieveRouteVariableValue(r, "name") if err != nil { - return httperror.BadRequest( - "Invalid node identifier route variable", - err, - ) + return httperror.BadRequest("Invalid node identifier route variable", err) } metrics, err := cli.MetricsV1beta1().NodeMetricses().Get( @@ -111,10 +81,7 @@ func (handler *Handler) getKubernetesMetricsForNode(w http.ResponseWriter, r *ht v1.GetOptions{}, ) if err != nil { - return httperror.InternalServerError( - "Failed to fetch metrics", - err, - ) + return httperror.InternalServerError("Failed to fetch metrics", err) } return response.JSON(w, metrics) @@ -136,42 +103,24 @@ func (handler *Handler) getKubernetesMetricsForNode(w http.ResponseWriter, r *ht // @failure 500 "Server error" // @router /kubernetes/{id}/metrics/pods/{namespace} [get] func (handler *Handler) getKubernetesMetricsForAllPods(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + endpoint, err := middlewares.FetchEndpoint(r) if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) - if handler.DataStore.IsErrObjectNotFound(err) { - return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) - } else if err != nil { - return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + return httperror.InternalServerError(err.Error(), err) } cli, err := handler.KubernetesClientFactory.CreateRemoteMetricsClient(endpoint) if err != nil { - return httperror.InternalServerError( - "failed to create metrics KubeClient", - nil, - ) + return httperror.InternalServerError("failed to create metrics KubeClient", nil) } namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } metrics, err := cli.MetricsV1beta1().PodMetricses(namespace).List(r.Context(), v1.ListOptions{}) if err != nil { - return httperror.InternalServerError( - "Failed to fetch metrics", - err, - ) + return httperror.InternalServerError("Failed to fetch metrics", err) } return response.JSON(w, metrics) @@ -194,54 +143,29 @@ func (handler *Handler) getKubernetesMetricsForAllPods(w http.ResponseWriter, r // @failure 500 "Server error" // @router /kubernetes/{id}/metrics/pods/{namespace}/{name} [get] func (handler *Handler) getKubernetesMetricsForPod(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + endpoint, err := middlewares.FetchEndpoint(r) if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) - if handler.DataStore.IsErrObjectNotFound(err) { - return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) - } else if err != nil { - return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + return httperror.InternalServerError(err.Error(), err) } cli, err := handler.KubernetesClientFactory.CreateRemoteMetricsClient(endpoint) if err != nil { - return httperror.InternalServerError( - "failed to create metrics KubeClient", - nil, - ) + return httperror.InternalServerError("failed to create metrics KubeClient", nil) } namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } podName, err := request.RetrieveRouteVariableValue(r, "name") if err != nil { - return httperror.BadRequest( - "Invalid pod identifier route variable", - err, - ) + return httperror.BadRequest("Invalid pod identifier route variable", err) } - metrics, err := cli.MetricsV1beta1().PodMetricses(namespace).Get( - r.Context(), - podName, - v1.GetOptions{}, - ) + metrics, err := cli.MetricsV1beta1().PodMetricses(namespace).Get(r.Context(), podName, v1.GetOptions{}) if err != nil { - return httperror.InternalServerError( - "Failed to fetch metrics", - err, - ) + return httperror.InternalServerError("Failed to fetch metrics", err) } return response.JSON(w, metrics) diff --git a/api/http/handler/kubernetes/namespaces.go b/api/http/handler/kubernetes/namespaces.go index 8f7ae1850..3f4150b40 100644 --- a/api/http/handler/kubernetes/namespaces.go +++ b/api/http/handler/kubernetes/namespaces.go @@ -2,7 +2,6 @@ package kubernetes import ( "net/http" - "strconv" models "github.com/portainer/portainer/api/http/models/kubernetes" httperror "github.com/portainer/portainer/pkg/libhttp/error" @@ -25,30 +24,14 @@ import ( // @failure 500 "Server error" // @router /kubernetes/{id}/namespaces [get] func (handler *Handler) getKubernetesNamespaces(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } namespaces, err := cli.GetNamespaces() if err != nil { - return httperror.InternalServerError( - "Unable to retrieve namespaces", - err, - ) + return httperror.InternalServerError("Unable to retrieve namespaces", err) } return response.JSON(w, namespaces) @@ -70,37 +53,22 @@ func (handler *Handler) getKubernetesNamespaces(w http.ResponseWriter, r *http.R // @failure 500 "Server error" // @router /kubernetes/{id}/namespaces/{namespace} [get] func (handler *Handler) getKubernetesNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + ns, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { return httperror.BadRequest( - "Invalid environment identifier route variable", + "Invalid namespace identifier route variable", err, ) } - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } - ns, err := request.RetrieveRouteVariableValue(r, "namespace") - if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) - } namespace, err := cli.GetNamespace(ns) if err != nil { - return httperror.InternalServerError( - "Unable to retrieve namespace", - err, - ) + return httperror.InternalServerError("Unable to retrieve namespace", err) } return response.JSON(w, namespace) @@ -123,39 +91,20 @@ func (handler *Handler) getKubernetesNamespace(w http.ResponseWriter, r *http.Re // @failure 500 "Server error" // @router /kubernetes/{id}/namespaces [post] func (handler *Handler) createKubernetesNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + var payload models.K8sNamespaceDetails + err := request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) + return httperror.BadRequest("Invalid request payload", err) } - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) - } - - var payload models.K8sNamespaceDetails - err = request.DecodeAndValidateJSONPayload(r, &payload) - if err != nil { - return httperror.BadRequest( - "Invalid request payload", - err, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } err = cli.CreateNamespace(payload) if err != nil { - return httperror.InternalServerError( - "Unable to create namespace", - err, - ) + return httperror.InternalServerError("Unable to create namespace", err) } return nil @@ -177,38 +126,25 @@ func (handler *Handler) createKubernetesNamespace(w http.ResponseWriter, r *http // @failure 500 "Server error" // @router /kubernetes/{id}/namespaces/{namespace} [delete] func (handler *Handler) deleteKubernetesNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + var payload models.K8sNamespaceDetails + err := request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + return httperror.BadRequest("Invalid request payload", err) } namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) + } + + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } err = cli.DeleteNamespace(namespace) if err != nil { - return httperror.InternalServerError( - "Unable to delete namespace", - err, - ) + return httperror.InternalServerError("Unable to delete namespace", err) } return nil @@ -231,30 +167,17 @@ func (handler *Handler) deleteKubernetesNamespace(w http.ResponseWriter, r *http // @failure 500 "Server error" // @router /kubernetes/{id}/namespaces/{namespace} [put] func (handler *Handler) updateKubernetesNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) - } - var payload models.K8sNamespaceDetails - err = request.DecodeAndValidateJSONPayload(r, &payload) + err := request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { return httperror.BadRequest("Invalid request payload", err) } + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr + } + err = cli.UpdateNamespace(payload) if err != nil { return httperror.InternalServerError("Unable to update namespace", err) diff --git a/api/http/handler/kubernetes/nodes_limits.go b/api/http/handler/kubernetes/nodes_limits.go index 3a5b6a639..ee2834593 100644 --- a/api/http/handler/kubernetes/nodes_limits.go +++ b/api/http/handler/kubernetes/nodes_limits.go @@ -3,13 +3,12 @@ package kubernetes import ( "net/http" - portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/http/middlewares" httperror "github.com/portainer/portainer/pkg/libhttp/error" - "github.com/portainer/portainer/pkg/libhttp/request" "github.com/portainer/portainer/pkg/libhttp/response" ) -// @id getKubernetesNodesLimits +// @id GetKubernetesNodesLimits // @summary Get CPU and memory limits of all nodes within k8s cluster // @description Get CPU and memory limits of all nodes within k8s cluster // @description **Access policy**: authenticated @@ -27,16 +26,9 @@ import ( // @failure 500 "Server error" // @router /kubernetes/{id}/nodes_limits [get] func (handler *Handler) getKubernetesNodesLimits(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + endpoint, err := middlewares.FetchEndpoint(r) if err != nil { - return httperror.BadRequest("Invalid environment identifier route variable", err) - } - - endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) - if handler.DataStore.IsErrObjectNotFound(err) { - return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) - } else if err != nil { - return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + return httperror.NotFound("Unable to find an environment on request context", err) } cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint) @@ -53,26 +45,14 @@ func (handler *Handler) getKubernetesNodesLimits(w http.ResponseWriter, r *http. } func (handler *Handler) getKubernetesMaxResourceLimits(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") + endpoint, err := middlewares.FetchEndpoint(r) if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID)) - if handler.DataStore.IsErrObjectNotFound(err) { - return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) - } else if err != nil { - return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + return httperror.NotFound("Unable to find an environment on request context", err) } cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint) if err != nil { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - err, - ) + return httperror.InternalServerError("Failed to lookup KubeClient", err) } overCommit := endpoint.Kubernetes.Configuration.EnableResourceOverCommit diff --git a/api/http/handler/kubernetes/rbac.go b/api/http/handler/kubernetes/rbac.go index 2c3b79f09..7079f6a88 100644 --- a/api/http/handler/kubernetes/rbac.go +++ b/api/http/handler/kubernetes/rbac.go @@ -2,10 +2,8 @@ package kubernetes import ( "net/http" - "strconv" httperror "github.com/portainer/portainer/pkg/libhttp/error" - "github.com/portainer/portainer/pkg/libhttp/request" "github.com/portainer/portainer/pkg/libhttp/response" ) @@ -22,26 +20,11 @@ import ( // @failure 500 "Server error" // @router /kubernetes/{id}/rbac_enabled [get] func (handler *Handler) isRBACEnabled(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - // with the endpoint id and user auth, create a kube client instance - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } - // with the kube client instance, check if RBAC is enabled isRBACEnabled, err := cli.IsRBACEnabled() if err != nil { return httperror.InternalServerError("Failed to check RBAC status", err) diff --git a/api/http/handler/kubernetes/services.go b/api/http/handler/kubernetes/services.go index c8bf12643..dae3ccd94 100644 --- a/api/http/handler/kubernetes/services.go +++ b/api/http/handler/kubernetes/services.go @@ -2,7 +2,6 @@ package kubernetes import ( "net/http" - "strconv" models "github.com/portainer/portainer/api/http/models/kubernetes" httperror "github.com/portainer/portainer/pkg/libhttp/error" @@ -29,44 +28,22 @@ import ( func (handler *Handler) getKubernetesServices(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } lookup, err := request.RetrieveBooleanQueryParameter(r, "lookupapplications", true) if err != nil { - return httperror.BadRequest( - "Invalid lookupapplications query parameter", - err, - ) + return httperror.BadRequest("Invalid lookupapplications query parameter", err) } services, err := cli.GetServices(namespace, lookup) if err != nil { - return httperror.InternalServerError( - "Unable to retrieve services", - err, - ) + return httperror.InternalServerError("Unable to retrieve services", err) } return response.JSON(w, services) @@ -91,46 +68,25 @@ func (handler *Handler) getKubernetesServices(w http.ResponseWriter, r *http.Req func (handler *Handler) createKubernetesService(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } var payload models.K8sServiceInfo err = request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { - return httperror.BadRequest( - "Invalid request payload", - err, - ) + return httperror.BadRequest("Invalid request payload", err) } - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } err = cli.CreateService(namespace, payload) if err != nil { - return httperror.InternalServerError( - "Unable to create sercice", - err, - ) + return httperror.InternalServerError("Unable to create sercice", err) } + return nil } @@ -150,26 +106,8 @@ func (handler *Handler) createKubernetesService(w http.ResponseWriter, r *http.R // @failure 500 "Server error" // @router /kubernetes/{id}/services/delete [post] func (handler *Handler) deleteKubernetesServices(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) - } - - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) - } - var payload models.K8sServiceDeleteRequests - err = request.DecodeAndValidateJSONPayload(r, &payload) + err := request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { return httperror.BadRequest( "Invalid request payload", @@ -177,6 +115,11 @@ func (handler *Handler) deleteKubernetesServices(w http.ResponseWriter, r *http. ) } + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr + } + err = cli.DeleteServices(payload) if err != nil { return httperror.InternalServerError( @@ -206,44 +149,24 @@ func (handler *Handler) deleteKubernetesServices(w http.ResponseWriter, r *http. func (handler *Handler) updateKubernetesService(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { namespace, err := request.RetrieveRouteVariableValue(r, "namespace") if err != nil { - return httperror.BadRequest( - "Invalid namespace identifier route variable", - err, - ) + return httperror.BadRequest("Invalid namespace identifier route variable", err) } var payload models.K8sServiceInfo err = request.DecodeAndValidateJSONPayload(r, &payload) if err != nil { - return httperror.BadRequest( - "Invalid request payload", - err, - ) + return httperror.BadRequest("Invalid request payload", err) } - endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return httperror.BadRequest( - "Invalid environment identifier route variable", - err, - ) + cli, handlerErr := handler.getProxyKubeClient(r) + if handlerErr != nil { + return handlerErr } - cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient( - strconv.Itoa(endpointID), r.Header.Get("Authorization"), - ) - if !ok { - return httperror.InternalServerError( - "Failed to lookup KubeClient", - nil, - ) - } err = cli.UpdateService(namespace, payload) if err != nil { - return httperror.InternalServerError( - "Unable to update service", - err, - ) + return httperror.InternalServerError("Unable to update service", err) } + return nil }