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

fix(perf): experimental namespace lookup #531

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
github.com/mitchellh/mapstructure v1.5.0
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
github.com/perimeterx/marshmallow v1.1.5
github.com/pkg/errors v0.9.1
github.com/portainer/portainer v0.6.1-0.20230901222702-8cc5e0796c4a
github.com/rs/zerolog v1.29.0
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
Expand Down Expand Up @@ -269,6 +271,8 @@ github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down Expand Up @@ -318,6 +322,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/wI2L/jsondiff v0.2.0 h1:dE00WemBa1uCjrzQUUTE/17I6m5qAaN0EMFOg2Ynr/k=
github.com/wI2L/jsondiff v0.2.0/go.mod h1:axTcwtBkY4TsKuV+RgoMhHyHKKFRI6nnjRLi8LLYQnA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
3 changes: 3 additions & 0 deletions http/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/portainer/agent/http/proxy"
"github.com/portainer/agent/http/security"
kubecli "github.com/portainer/agent/kubernetes"
"github.com/rs/zerolog/log"
)

// Handler is the main handler of the application.
Expand Down Expand Up @@ -100,6 +101,8 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, request *http.Request) {
}
rw.Header().Set(agent.HTTPResponseAgentPlatform, strconv.Itoa(int(agentPlatformIdentifier)))

log.Debug().Msgf("Handling request: %s %s", request.Method, request.URL.Path)

switch {
case strings.HasPrefix(request.URL.Path, "/v1"):
h.ServeHTTPV1(rw, request)
Expand Down
7 changes: 7 additions & 0 deletions http/handler/handlerv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ package handler
import (
"net/http"
"strings"

"github.com/rs/zerolog/log"
)

// ServeHTTPV2 is the HTTP router for all v2 api requests.
func (h *Handler) ServeHTTPV2(rw http.ResponseWriter, request *http.Request) {

//log.Debug().Msgf("Request: %s %s", request.Method, request.URL.Path)

switch {
case strings.HasPrefix(request.URL.Path, "/v2/ping"):
http.StripPrefix("/v2", h.pingHandler).ServeHTTP(rw, request)
Expand All @@ -21,8 +26,10 @@ func (h *Handler) ServeHTTPV2(rw http.ResponseWriter, request *http.Request) {
case strings.HasPrefix(request.URL.Path, "/v2/websocket"):
http.StripPrefix("/v2", h.webSocketHandler).ServeHTTP(rw, request)
case strings.HasPrefix(request.URL.Path, "/v2/kubernetes"):
log.Debug().Msgf("Got it: %s %s", request.Method, request.URL.Path)
http.StripPrefix("/v2", h.kubernetesHandler).ServeHTTP(rw, request)
case strings.HasPrefix(request.URL.Path, "/"):
log.Debug().Msgf("default: %s %s", request.Method, request.URL.Path)
h.dockerProxyHandler.ServeHTTP(rw, request)
}
}
8 changes: 8 additions & 0 deletions http/handler/kubernetes/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,13 @@ func NewHandler(notaryService *security.NotaryService, kubernetesDeployer *exec.
h.Handle("/kubernetes/stack",
notaryService.DigitalSignatureVerification(httperror.LoggerHandler(h.kubernetesDeploy))).Methods(http.MethodPost)

h.Handle("/kubernetes/namespaces",
notaryService.DigitalSignatureVerification(httperror.LoggerHandler(h.kubernetesGetNamespaces))).Methods(http.MethodGet)
h.Handle("/kubernetes/namespaces/{namespace}",
notaryService.DigitalSignatureVerification(httperror.LoggerHandler(h.kubernetesGetNamespaces))).Methods(http.MethodGet)

h.Handle("/kubernetes/namespaces/{namespace}/{configmaps|secrets}",
notaryService.DigitalSignatureVerification(httperror.LoggerHandler(h.kubernetesGetConfigMaps))).Methods(http.MethodGet)

return h
}
111 changes: 111 additions & 0 deletions http/handler/kubernetes/kubernetes_configmaps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package kubernetes

import (
"context"
"net/http"
"path"
"strings"

"github.com/portainer/agent"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/rs/zerolog/log"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

// type (
// FilteredNamespaceResponse struct {
// Kind string `json:"kind"`
// Name string `json:"name"`
// MetaData struct {
// Name string `json:"name"`
// CreationTimestamp string `json:"creationTimestamp"`
// } `json:"metadata"`
// }

// FilteredNamespacesResponse struct {
// APIVersion string `json:"apiVersion"`
// Items []struct {
// Metadata struct {
// CreationTimestamp string `json:"creationTimestamp"`
// Labels struct {
// Kubernetes_io_metadata_name string `json:"kubernetes.io/metadata.name"`
// } `json:"labels"`
// Name string `json:"name"`
// ResourceVersion string `json:"resourceVersion"`
// UID string `json:"uid"`
// } `json:"metadata"`
// } `json:"items"`
// Kind string `json:"kind"`
// Metadata struct {
// ResourceVersion string `json:"resourceVersion"`
// } `json:"metadata"`
// }
// )

func (handler *Handler) kubernetesGetConfigMaps(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError {
log.Debug().Msgf("GetNamespaces Handler: Request: %s %s", r.Method, r.URL.Path)

config, err := rest.InClusterConfig()
if err != nil {
return httperror.InternalServerError("Unable to read service account token file", err)
}

token := r.Header.Get(agent.HTTPKubernetesSATokenHeaderName)
if len(token) == 0 {
config.BearerToken = token
}

// adjust the API path to match the Kubernetes API
api := path.Join("/api/v1/", strings.TrimPrefix(r.URL.Path, "/kubernetes"))
if len(r.URL.RawQuery) > 0 {
api = api + "?" + r.URL.RawQuery
}

log.Debug().Msgf("New API path: %s", api)

// Create an HTTP client from the Kubernetes configuration
clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
return httperror.InternalServerError("Unable to create HTTP client", err)
}

restClient := clientSet.RESTClient()

// Create an HTTP request using the client
req := restClient.Get().RequestURI(api)

// Send the HTTP request
resp, err := req.DoRaw(context.Background())
if err != nil {
panic(err)
}

return filteredConfigMaps(rw, resp)
}

func filteredConfigMaps(rw http.ResponseWriter, data []byte) *httperror.HandlerError {
// var namespacesResponse []FilteredNamespaceResponse
// err := json.Unmarshal([]byte(data), &namespacesResponse)
// if err != nil {
// return httperror.InternalServerError("Unable to unmarshal response", err)
// }

// v := struct {
// Kind string `json:"kind"`
// }{}

// result, err := marshmallow.Unmarshal(data, &v)
// if err != nil {
// return httperror.InternalServerError("Unable to unmarshal response", err)
// }

// if v.Kind == "NamespaceList" {
// result["items"] = []FilteredNamespaceResponse{}
// }

//return response.JSON(rw, result)
rw.Header().Set("Content-Type", "application/json")
rw.Write(data)
return nil
}
111 changes: 111 additions & 0 deletions http/handler/kubernetes/kubernetes_namespaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package kubernetes

import (
"context"
"net/http"
"path"
"strings"

"github.com/portainer/agent"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/rs/zerolog/log"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

// type (
// FilteredNamespaceResponse struct {
// Kind string `json:"kind"`
// Name string `json:"name"`
// MetaData struct {
// Name string `json:"name"`
// CreationTimestamp string `json:"creationTimestamp"`
// } `json:"metadata"`
// }

// FilteredNamespacesResponse struct {
// APIVersion string `json:"apiVersion"`
// Items []struct {
// Metadata struct {
// CreationTimestamp string `json:"creationTimestamp"`
// Labels struct {
// Kubernetes_io_metadata_name string `json:"kubernetes.io/metadata.name"`
// } `json:"labels"`
// Name string `json:"name"`
// ResourceVersion string `json:"resourceVersion"`
// UID string `json:"uid"`
// } `json:"metadata"`
// } `json:"items"`
// Kind string `json:"kind"`
// Metadata struct {
// ResourceVersion string `json:"resourceVersion"`
// } `json:"metadata"`
// }
// )

func (handler *Handler) kubernetesGetNamespaces(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError {
log.Debug().Msgf("GetNamespaces Handler: Request: %s %s", r.Method, r.URL.Path)

config, err := rest.InClusterConfig()
if err != nil {
return httperror.InternalServerError("Unable to read service account token file", err)
}

token := r.Header.Get(agent.HTTPKubernetesSATokenHeaderName)
if len(token) == 0 {
config.BearerToken = token
}

// adjust the API path to match the Kubernetes API
api := path.Join("/api/v1/", strings.TrimPrefix(r.URL.Path, "/kubernetes"))
if len(r.URL.RawQuery) > 0 {
api = api + "?" + r.URL.RawQuery
}

log.Debug().Msgf("New API path: %s", api)

// Create an HTTP client from the Kubernetes configuration
clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
return httperror.InternalServerError("Unable to create HTTP client", err)
}

restClient := clientSet.RESTClient()

// Create an HTTP request using the client
req := restClient.Get().RequestURI(api)

// Send the HTTP request
resp, err := req.DoRaw(context.Background())
if err != nil {
panic(err)
}

return filteredNamespaces(rw, resp)
}

func filteredNamespaces(rw http.ResponseWriter, data []byte) *httperror.HandlerError {
// var namespacesResponse []FilteredNamespaceResponse
// err := json.Unmarshal([]byte(data), &namespacesResponse)
// if err != nil {
// return httperror.InternalServerError("Unable to unmarshal response", err)
// }

// v := struct {
// Kind string `json:"kind"`
// }{}

// result, err := marshmallow.Unmarshal(data, &v)
// if err != nil {
// return httperror.InternalServerError("Unable to unmarshal response", err)
// }

// if v.Kind == "NamespaceList" {
// result["items"] = []FilteredNamespaceResponse{}
// }

//return response.JSON(rw, result)
rw.Header().Set("Content-Type", "application/json")
rw.Write(data)
return nil
}