mirror of https://github.com/portainer/portainer
fix(kubernetes): create proxied kubeclient EE-4326 (#7850)
parent
f6d6be90e4
commit
0c995ae1c8
|
@ -239,8 +239,8 @@ func initDockerClientFactory(signatureService portainer.DigitalSignatureService,
|
||||||
return docker.NewClientFactory(signatureService, reverseTunnelService)
|
return docker.NewClientFactory(signatureService, reverseTunnelService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string, dataStore dataservices.DataStore) *kubecli.ClientFactory {
|
func initKubernetesClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*kubecli.ClientFactory, error) {
|
||||||
return kubecli.NewClientFactory(signatureService, reverseTunnelService, instanceID, dataStore)
|
return kubecli.NewClientFactory(signatureService, reverseTunnelService, dataStore, instanceID, addrHTTPS, userSessionTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSnapshotService(
|
func initSnapshotService(
|
||||||
|
@ -612,7 +612,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
|
||||||
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
|
reverseTunnelService := chisel.NewService(dataStore, shutdownCtx)
|
||||||
|
|
||||||
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
|
dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService)
|
||||||
kubernetesClientFactory := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, instanceID, dataStore)
|
kubernetesClientFactory, err := initKubernetesClientFactory(digitalSignatureService, reverseTunnelService, dataStore, instanceID, *flags.AddrHTTPS, settings.UserSessionTimeout)
|
||||||
|
|
||||||
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
|
snapshotService, err := initSnapshotService(*flags.SnapshotInterval, dataStore, dockerClientFactory, kubernetesClientFactory, shutdownCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -31,6 +31,7 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
|
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
|
||||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
|
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021
|
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021
|
||||||
github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a
|
github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a
|
||||||
|
|
|
@ -352,6 +352,8 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB
|
||||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
|
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
|
||||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
|
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
|
|
@ -2,6 +2,7 @@ package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
|
@ -9,7 +10,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (handler *Handler) getKubernetesConfigMaps(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) getKubernetesConfigMaps(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
namespace, err := request.RetrieveRouteVariableValue(r, "namespace")
|
namespace, err := request.RetrieveRouteVariableValue(r, "namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -22,7 +39,7 @@ func (handler *Handler) getKubernetesConfigMaps(w http.ResponseWriter, r *http.R
|
||||||
configmaps, err := cli.GetConfigMapsAndSecrets(namespace)
|
configmaps, err := cli.GetConfigMapsAndSecrets(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to retrieve configmaps and secrets",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package kubernetes
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
portainerDsErrors "github.com/portainer/portainer/api/dataservices/errors"
|
portainerDsErrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||||
|
@ -24,7 +26,6 @@ type Handler struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
authorizationService *authorization.Service
|
authorizationService *authorization.Service
|
||||||
DataStore dataservices.DataStore
|
DataStore dataservices.DataStore
|
||||||
KubernetesClient portainer.KubeClient
|
|
||||||
KubernetesClientFactory *cli.ClientFactory
|
KubernetesClientFactory *cli.ClientFactory
|
||||||
JwtService dataservices.JWTService
|
JwtService dataservices.JWTService
|
||||||
kubeClusterAccessService kubernetes.KubeClusterAccessService
|
kubeClusterAccessService kubernetes.KubeClusterAccessService
|
||||||
|
@ -39,7 +40,6 @@ func NewHandler(bouncer *security.RequestBouncer, authorizationService *authoriz
|
||||||
JwtService: jwtService,
|
JwtService: jwtService,
|
||||||
kubeClusterAccessService: kubeClusterAccessService,
|
kubeClusterAccessService: kubeClusterAccessService,
|
||||||
KubernetesClientFactory: kubernetesClientFactory,
|
KubernetesClientFactory: kubernetesClientFactory,
|
||||||
KubernetesClient: kubernetesClient,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeRouter := h.PathPrefix("/kubernetes").Subrouter()
|
kubeRouter := h.PathPrefix("/kubernetes").Subrouter()
|
||||||
|
@ -85,13 +85,19 @@ func kubeOnlyMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, request *http.Request) {
|
||||||
endpoint, err := middlewares.FetchEndpoint(request)
|
endpoint, err := middlewares.FetchEndpoint(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httperror.WriteError(rw, http.StatusInternalServerError, "Unable to find an environment on request context", err)
|
httperror.InternalServerError(
|
||||||
|
"Unable to find an environment on request context",
|
||||||
|
err,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !endpointutils.IsKubernetesEndpoint(endpoint) {
|
if !endpointutils.IsKubernetesEndpoint(endpoint) {
|
||||||
errMessage := "environment is not a Kubernetes environment"
|
errMessage := "environment is not a Kubernetes environment"
|
||||||
httperror.WriteError(rw, http.StatusBadRequest, errMessage, errors.New(errMessage))
|
httperror.BadRequest(
|
||||||
|
errMessage,
|
||||||
|
errors.New(errMessage),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +115,7 @@ func (handler *Handler) kubeClient(next http.Handler) http.Handler {
|
||||||
"Invalid environment identifier route variable",
|
"Invalid environment identifier route variable",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
||||||
|
@ -119,6 +126,7 @@ func (handler *Handler) kubeClient(next http.Handler) http.Handler {
|
||||||
"Unable to find an environment with the specified identifier inside the database",
|
"Unable to find an environment with the specified identifier inside the database",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
httperror.WriteError(
|
httperror.WriteError(
|
||||||
w,
|
w,
|
||||||
|
@ -126,23 +134,101 @@ func (handler *Handler) kubeClient(next http.Handler) http.Handler {
|
||||||
"Unable to find an environment with the specified identifier inside the database",
|
"Unable to find an environment with the specified identifier inside the database",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if handler.KubernetesClientFactory == nil {
|
if handler.KubernetesClientFactory == nil {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
kubeCli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint)
|
// 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,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bearerToken, err := handler.JwtService.GenerateTokenForKubeconfig(tokenData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httperror.WriteError(
|
httperror.WriteError(
|
||||||
w,
|
w,
|
||||||
http.StatusInternalServerError,
|
http.StatusInternalServerError,
|
||||||
"Unable to create Kubernetes client",
|
"Unable to create JWT token",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
handler.KubernetesClient = kubeCli
|
singleEndpointList := []portainer.Endpoint{
|
||||||
|
*endpoint,
|
||||||
|
}
|
||||||
|
config, handlerErr := handler.buildConfig(
|
||||||
|
r,
|
||||||
|
tokenData,
|
||||||
|
bearerToken,
|
||||||
|
singleEndpointList,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
httperror.WriteError(
|
||||||
|
w,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"Unable to build endpoint kubeconfig",
|
||||||
|
handlerErr.Err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.Clusters) == 0 {
|
||||||
|
httperror.WriteError(
|
||||||
|
w,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"Unable build cluster kubeconfig",
|
||||||
|
errors.New("Unable build cluster kubeconfig"),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually setting the 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,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverURL.Scheme = "https"
|
||||||
|
serverURL.Host = "localhost" + handler.KubernetesClientFactory.AddrHTTPS
|
||||||
|
config.Clusters[0].Cluster.Server = serverURL.String()
|
||||||
|
|
||||||
|
yaml, err := cli.GenerateYAML(config)
|
||||||
|
if err != nil {
|
||||||
|
httperror.WriteError(
|
||||||
|
w,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"Unable to generate yaml from endpoint kubeconfig",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kubeCli, err := handler.KubernetesClientFactory.CreateKubeClientFromKubeConfig(endpoint.Name, []byte(yaml))
|
||||||
|
if err != nil {
|
||||||
|
httperror.WriteError(
|
||||||
|
w,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"Failed to create client from kubeconfig",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.KubernetesClientFactory.SetProxyKubeClient(strconv.Itoa(int(endpoint.ID)), r.Header.Get("Authorization"), kubeCli)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
|
@ -49,7 +50,13 @@ func (handler *Handler) getKubernetesIngressControllers(w http.ResponseWriter, r
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
controllers := cli.GetIngressControllers()
|
controllers, err := cli.GetIngressControllers()
|
||||||
|
if err != nil {
|
||||||
|
return httperror.InternalServerError(
|
||||||
|
"Failed to fetch ingressclasses",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses
|
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses
|
||||||
var updatedClasses []portainer.KubernetesIngressClassConfig
|
var updatedClasses []portainer.KubernetesIngressClassConfig
|
||||||
for i := range controllers {
|
for i := range controllers {
|
||||||
|
@ -129,8 +136,23 @@ func (handler *Handler) getKubernetesIngressControllersByNamespace(w http.Respon
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := handler.KubernetesClient
|
cli, ok := handler.KubernetesClientFactory.GetProxyKubeClient(
|
||||||
currentControllers := cli.GetIngressControllers()
|
strconv.Itoa(endpointID), r.Header.Get("Authorization"),
|
||||||
|
)
|
||||||
|
if !ok {
|
||||||
|
return httperror.InternalServerError(
|
||||||
|
"Failed to lookup KubeClient",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentControllers, err := cli.GetIngressControllers()
|
||||||
|
if err != nil {
|
||||||
|
return httperror.InternalServerError(
|
||||||
|
"Failed to fetch ingressclasses",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
kubernetesConfig := endpoint.Kubernetes.Configuration
|
kubernetesConfig := endpoint.Kubernetes.Configuration
|
||||||
existingClasses := kubernetesConfig.IngressClasses
|
existingClasses := kubernetesConfig.IngressClasses
|
||||||
ingressAvailabilityPerNamespace := kubernetesConfig.IngressAvailabilityPerNamespace
|
ingressAvailabilityPerNamespace := kubernetesConfig.IngressAvailabilityPerNamespace
|
||||||
|
@ -229,7 +251,13 @@ func (handler *Handler) updateKubernetesIngressControllers(w http.ResponseWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses
|
existingClasses := endpoint.Kubernetes.Configuration.IngressClasses
|
||||||
controllers := cli.GetIngressControllers()
|
controllers, err := cli.GetIngressControllers()
|
||||||
|
if err != nil {
|
||||||
|
return httperror.InternalServerError(
|
||||||
|
"Unable to get ingress controllers",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
var updatedClasses []portainer.KubernetesIngressClassConfig
|
var updatedClasses []portainer.KubernetesIngressClassConfig
|
||||||
for i := range controllers {
|
for i := range controllers {
|
||||||
controllers[i].Availability = true
|
controllers[i].Availability = true
|
||||||
|
@ -401,11 +429,28 @@ func (handler *Handler) getKubernetesIngresses(w http.ResponseWriter, r *http.Re
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ingresses, err := cli.GetIngresses(namespace)
|
ingresses, err := cli.GetIngresses(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to retrieve ingresses",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -431,11 +476,28 @@ func (handler *Handler) createKubernetesIngress(w http.ResponseWriter, r *http.R
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
err = cli.CreateIngress(namespace, payload)
|
err = cli.CreateIngress(namespace, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to retrieve the ingress",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -443,10 +505,26 @@ func (handler *Handler) createKubernetesIngress(w http.ResponseWriter, r *http.R
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) deleteKubernetesIngresses(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) deleteKubernetesIngresses(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
cli := handler.KubernetesClient
|
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.K8sIngressDeleteRequests
|
var payload models.K8sIngressDeleteRequests
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.BadRequest("Invalid request payload", err)
|
return httperror.BadRequest("Invalid request payload", err)
|
||||||
}
|
}
|
||||||
|
@ -454,7 +532,7 @@ func (handler *Handler) deleteKubernetesIngresses(w http.ResponseWriter, r *http
|
||||||
err = cli.DeleteIngresses(payload)
|
err = cli.DeleteIngresses(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to delete ingresses",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -479,11 +557,28 @@ func (handler *Handler) updateKubernetesIngress(w http.ResponseWriter, r *http.R
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
err = cli.UpdateIngress(namespace, payload)
|
err = cli.UpdateIngress(namespace, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to update the ingress",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
|
@ -10,12 +11,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (handler *Handler) getKubernetesNamespaces(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) getKubernetesNamespaces(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
namespaces, err := cli.GetNamespaces()
|
namespaces, err := cli.GetNamespaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to retrieve namespaces",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -24,10 +41,26 @@ func (handler *Handler) getKubernetesNamespaces(w http.ResponseWriter, r *http.R
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) createKubernetesNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) createKubernetesNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
cli := handler.KubernetesClient
|
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
|
var payload models.K8sNamespaceDetails
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.BadRequest(
|
return httperror.BadRequest(
|
||||||
"Invalid request payload",
|
"Invalid request payload",
|
||||||
|
@ -38,7 +71,7 @@ func (handler *Handler) createKubernetesNamespace(w http.ResponseWriter, r *http
|
||||||
err = cli.CreateNamespace(payload)
|
err = cli.CreateNamespace(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to create namespace",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -46,7 +79,23 @@ func (handler *Handler) createKubernetesNamespace(w http.ResponseWriter, r *http
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) deleteKubernetesNamespaces(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) deleteKubernetesNamespaces(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
namespace, err := request.RetrieveRouteVariableValue(r, "namespace")
|
namespace, err := request.RetrieveRouteVariableValue(r, "namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -59,7 +108,7 @@ func (handler *Handler) deleteKubernetesNamespaces(w http.ResponseWriter, r *htt
|
||||||
err = cli.DeleteNamespace(namespace)
|
err = cli.DeleteNamespace(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to delete namespace",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -68,17 +117,33 @@ func (handler *Handler) deleteKubernetesNamespaces(w http.ResponseWriter, r *htt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) updateKubernetesNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) updateKubernetesNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
cli := handler.KubernetesClient
|
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
|
var payload models.K8sNamespaceDetails
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.BadRequest("Invalid request payload", err)
|
return httperror.BadRequest("Invalid request payload", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cli.UpdateNamespace(payload)
|
err = cli.UpdateNamespace(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable to retrieve nodes limits", err)
|
return httperror.InternalServerError("Unable to update namespace", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
|
@ -18,7 +19,24 @@ func (handler *Handler) getKubernetesServices(w http.ResponseWriter, r *http.Req
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
services, err := cli.GetServices(namespace)
|
services, err := cli.GetServices(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
|
@ -48,11 +66,28 @@ func (handler *Handler) createKubernetesService(w http.ResponseWriter, r *http.R
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
err = cli.CreateService(namespace, payload)
|
err = cli.CreateService(namespace, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to create sercice",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -60,10 +95,26 @@ func (handler *Handler) createKubernetesService(w http.ResponseWriter, r *http.R
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *Handler) deleteKubernetesServices(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
func (handler *Handler) deleteKubernetesServices(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||||
cli := handler.KubernetesClient
|
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
|
var payload models.K8sServiceDeleteRequests
|
||||||
err := request.DecodeAndValidateJSONPayload(r, &payload)
|
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.BadRequest(
|
return httperror.BadRequest(
|
||||||
"Invalid request payload",
|
"Invalid request payload",
|
||||||
|
@ -74,7 +125,7 @@ func (handler *Handler) deleteKubernetesServices(w http.ResponseWriter, r *http.
|
||||||
err = cli.DeleteServices(payload)
|
err = cli.DeleteServices(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to delete service",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -99,11 +150,27 @@ func (handler *Handler) updateKubernetesService(w http.ResponseWriter, r *http.R
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := handler.KubernetesClient
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
err = cli.UpdateService(namespace, payload)
|
err = cli.UpdateService(namespace, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError(
|
return httperror.InternalServerError(
|
||||||
"Unable to retrieve nodes limits",
|
"Unable to update service",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
cmap "github.com/orcaman/concurrent-map"
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/dataservices"
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
|
@ -23,6 +25,8 @@ type (
|
||||||
signatureService portainer.DigitalSignatureService
|
signatureService portainer.DigitalSignatureService
|
||||||
instanceID string
|
instanceID string
|
||||||
endpointClients cmap.ConcurrentMap
|
endpointClients cmap.ConcurrentMap
|
||||||
|
endpointProxyClients *cache.Cache
|
||||||
|
AddrHTTPS string
|
||||||
}
|
}
|
||||||
|
|
||||||
// KubeClient represent a service used to execute Kubernetes operations
|
// KubeClient represent a service used to execute Kubernetes operations
|
||||||
|
@ -34,14 +38,24 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewClientFactory returns a new instance of a ClientFactory
|
// NewClientFactory returns a new instance of a ClientFactory
|
||||||
func NewClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, instanceID string, dataStore dataservices.DataStore) *ClientFactory {
|
func NewClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService, dataStore dataservices.DataStore, instanceID, addrHTTPS, userSessionTimeout string) (*ClientFactory, error) {
|
||||||
|
if userSessionTimeout == "" {
|
||||||
|
userSessionTimeout = portainer.DefaultUserSessionTimeout
|
||||||
|
}
|
||||||
|
timeout, err := time.ParseDuration(userSessionTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &ClientFactory{
|
return &ClientFactory{
|
||||||
dataStore: dataStore,
|
dataStore: dataStore,
|
||||||
signatureService: signatureService,
|
signatureService: signatureService,
|
||||||
reverseTunnelService: reverseTunnelService,
|
reverseTunnelService: reverseTunnelService,
|
||||||
instanceID: instanceID,
|
instanceID: instanceID,
|
||||||
endpointClients: cmap.New(),
|
endpointClients: cmap.New(),
|
||||||
}
|
endpointProxyClients: cache.New(timeout, timeout),
|
||||||
|
AddrHTTPS: addrHTTPS,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (factory *ClientFactory) GetInstanceID() (instanceID string) {
|
func (factory *ClientFactory) GetInstanceID() (instanceID string) {
|
||||||
|
@ -59,7 +73,7 @@ func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint) (porta
|
||||||
key := strconv.Itoa(int(endpoint.ID))
|
key := strconv.Itoa(int(endpoint.ID))
|
||||||
client, ok := factory.endpointClients.Get(key)
|
client, ok := factory.endpointClients.Get(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
client, err := factory.createKubeClient(endpoint)
|
client, err := factory.createCachedAdminKubeClient(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -71,7 +85,49 @@ func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint) (porta
|
||||||
return client.(portainer.KubeClient), nil
|
return client.(portainer.KubeClient), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) {
|
// GetProxyKubeClient retrieves a KubeClient from the cache. You should be
|
||||||
|
// calling SetProxyKubeClient before first. It is normally, called the
|
||||||
|
// kubernetes middleware.
|
||||||
|
func (factory *ClientFactory) GetProxyKubeClient(endpointID, token string) (portainer.KubeClient, bool) {
|
||||||
|
client, ok := factory.endpointProxyClients.Get(endpointID + "." + token)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return client.(portainer.KubeClient), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProxyKubeClient stores a kubeclient in the cache.
|
||||||
|
func (factory *ClientFactory) SetProxyKubeClient(endpointID, token string, cli portainer.KubeClient) {
|
||||||
|
factory.endpointProxyClients.Set(endpointID+"."+token, cli, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateKubeClientFromKubeConfig creates a KubeClient from a clusterID, and
|
||||||
|
// Kubernetes config.
|
||||||
|
func (factory *ClientFactory) CreateKubeClientFromKubeConfig(clusterID string, kubeConfig []byte) (portainer.KubeClient, error) {
|
||||||
|
config, err := clientcmd.NewClientConfigFromBytes([]byte(kubeConfig))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cliConfig, err := config.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cli, err := kubernetes.NewForConfig(cliConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kubecli := &KubeClient{
|
||||||
|
cli: cli,
|
||||||
|
instanceID: factory.instanceID,
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return kubecli, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (factory *ClientFactory) createCachedAdminKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) {
|
||||||
cli, err := factory.CreateClient(endpoint)
|
cli, err := factory.CreateClient(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -5,11 +5,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/portainer/portainer/api/database/models"
|
"github.com/portainer/portainer/api/database/models"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
netv1 "k8s.io/api/networking/v1"
|
netv1 "k8s.io/api/networking/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (kcl *KubeClient) GetIngressControllers() models.K8sIngressControllers {
|
func (kcl *KubeClient) GetIngressControllers() (models.K8sIngressControllers, error) {
|
||||||
var controllers []models.K8sIngressController
|
var controllers []models.K8sIngressController
|
||||||
|
|
||||||
// We know that each existing class points to a controller so we can start
|
// We know that each existing class points to a controller so we can start
|
||||||
|
@ -17,19 +18,22 @@ func (kcl *KubeClient) GetIngressControllers() models.K8sIngressControllers {
|
||||||
classClient := kcl.cli.NetworkingV1().IngressClasses()
|
classClient := kcl.cli.NetworkingV1().IngressClasses()
|
||||||
classList, err := classClient.List(context.Background(), metav1.ListOptions{})
|
classList, err := classClient.List(context.Background(), metav1.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to know which of these controllers is in use.
|
// We want to know which of these controllers is in use.
|
||||||
var ingresses []models.K8sIngressInfo
|
var ingresses []models.K8sIngressInfo
|
||||||
namespaces, err := kcl.GetNamespaces()
|
namespaces, err := kcl.GetNamespaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
for namespace := range namespaces {
|
for namespace := range namespaces {
|
||||||
t, err := kcl.GetIngresses(namespace)
|
t, err := kcl.GetIngresses(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
// User might not be able to list ingresses in system/not allowed
|
||||||
|
// namespaces.
|
||||||
|
log.Debug().Err(err).Msg("failed to list ingresses for the current user, skipped sending ingress")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
ingresses = append(ingresses, t...)
|
ingresses = append(ingresses, t...)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +62,7 @@ func (kcl *KubeClient) GetIngressControllers() models.K8sIngressControllers {
|
||||||
}
|
}
|
||||||
controllers = append(controllers, controller)
|
controllers = append(controllers, controller)
|
||||||
}
|
}
|
||||||
return controllers
|
return controllers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIngresses gets all the ingresses for a given namespace in a k8s endpoint.
|
// GetIngresses gets all the ingresses for a given namespace in a k8s endpoint.
|
||||||
|
|
|
@ -85,7 +85,7 @@ func Test_GenerateYAML(t *testing.T) {
|
||||||
t.Errorf("generateYamlConfig failed; err=%s", err)
|
t.Errorf("generateYamlConfig failed; err=%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if compareYAMLStrings(yaml, ryt.wantYAML) != 0 {
|
if compareYAMLStrings(string(yaml), ryt.wantYAML) != 0 {
|
||||||
t.Errorf("generateYamlConfig failed;\ngot=\n%s\nwant=\n%s", yaml, ryt.wantYAML)
|
t.Errorf("generateYamlConfig failed;\ngot=\n%s\nwant=\n%s", yaml, ryt.wantYAML)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,6 +25,11 @@ func getPortainerUserDefaultPolicies() []rbacv1.PolicyRule {
|
||||||
Resources: []string{"namespaces", "pods", "nodes"},
|
Resources: []string{"namespaces", "pods", "nodes"},
|
||||||
APIGroups: []string{"metrics.k8s.io"},
|
APIGroups: []string{"metrics.k8s.io"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"list"},
|
||||||
|
Resources: []string{"ingressclasses"},
|
||||||
|
APIGroups: []string{"networking.k8s.io"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ func (service *kubeClusterAccessService) GetData(hostURL string, endpointID port
|
||||||
baseURL = fmt.Sprintf("/%s/", strings.Trim(baseURL, "/"))
|
baseURL = fmt.Sprintf("/%s/", strings.Trim(baseURL, "/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().
|
log.Debug().
|
||||||
Str("host_URL", hostURL).
|
Str("host_URL", hostURL).
|
||||||
Str("HTTPS_bind_address", service.httpsBindAddr).
|
Str("HTTPS_bind_address", service.httpsBindAddr).
|
||||||
Str("base_URL", baseURL).
|
Str("base_URL", baseURL).
|
||||||
|
|
|
@ -1357,7 +1357,7 @@ type (
|
||||||
GetNamespaces() (map[string]K8sNamespaceInfo, error)
|
GetNamespaces() (map[string]K8sNamespaceInfo, error)
|
||||||
DeleteNamespace(namespace string) error
|
DeleteNamespace(namespace string) error
|
||||||
GetConfigMapsAndSecrets(namespace string) ([]models.K8sConfigMapOrSecret, error)
|
GetConfigMapsAndSecrets(namespace string) ([]models.K8sConfigMapOrSecret, error)
|
||||||
GetIngressControllers() models.K8sIngressControllers
|
GetIngressControllers() (models.K8sIngressControllers, error)
|
||||||
CreateIngress(namespace string, info models.K8sIngressInfo) error
|
CreateIngress(namespace string, info models.K8sIngressInfo) error
|
||||||
UpdateIngress(namespace string, info models.K8sIngressInfo) error
|
UpdateIngress(namespace string, info models.K8sIngressInfo) error
|
||||||
GetIngresses(namespace string) ([]models.K8sIngressInfo, error)
|
GetIngresses(namespace string) ([]models.K8sIngressInfo, error)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useRouter } from '@uirouter/react';
|
||||||
|
|
||||||
import { useEnvironmentId } from '@/portainer/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/portainer/hooks/useEnvironmentId';
|
||||||
import { useNamespaces } from '@/react/kubernetes/namespaces/queries';
|
import { useNamespaces } from '@/react/kubernetes/namespaces/queries';
|
||||||
import { Authorized } from '@/portainer/hooks/useUser';
|
import { useAuthorizations, Authorized } from '@/portainer/hooks/useUser';
|
||||||
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
|
import { confirmDeletionAsync } from '@/portainer/services/modal.service/confirm';
|
||||||
|
|
||||||
import { Datatable } from '@@/datatables';
|
import { Datatable } from '@@/datatables';
|
||||||
|
@ -55,6 +55,7 @@ export function IngressDataTable() {
|
||||||
}}
|
}}
|
||||||
getRowId={(row) => row.Name + row.Type + row.Namespace}
|
getRowId={(row) => row.Name + row.Type + row.Namespace}
|
||||||
renderTableActions={tableActions}
|
renderTableActions={tableActions}
|
||||||
|
disableSelect={useCheckboxes()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ export function IngressDataTable() {
|
||||||
</Button>
|
</Button>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
|
|
||||||
<Authorized authorizations="K8sIngressesAdd">
|
<Authorized authorizations="K8sIngressesW">
|
||||||
<Link to="kubernetes.ingresses.create" className="space-left">
|
<Link to="kubernetes.ingresses.create" className="space-left">
|
||||||
<Button
|
<Button
|
||||||
icon={Plus}
|
icon={Plus}
|
||||||
|
@ -91,7 +92,7 @@ export function IngressDataTable() {
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
<Authorized authorizations="K8sApplicationsW">
|
<Authorized authorizations="K8sIngressesW">
|
||||||
<Link to="kubernetes.deploy" className="space-left">
|
<Link to="kubernetes.deploy" className="space-left">
|
||||||
<Button icon={Plus} className="btn-wrapper">
|
<Button icon={Plus} className="btn-wrapper">
|
||||||
Create from manifest
|
Create from manifest
|
||||||
|
@ -102,6 +103,10 @@ export function IngressDataTable() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useCheckboxes() {
|
||||||
|
return !useAuthorizations(['K8sIngressesW']);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleRemoveClick(ingresses: SelectedIngress[]) {
|
async function handleRemoveClick(ingresses: SelectedIngress[]) {
|
||||||
const confirmed = await confirmDeletionAsync(
|
const confirmed = await confirmDeletionAsync(
|
||||||
'Are you sure you want to delete the selected ingresses?'
|
'Are you sure you want to delete the selected ingresses?'
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { CellProps, Column } from 'react-table';
|
import { CellProps, Column } from 'react-table';
|
||||||
|
|
||||||
|
import { Authorized } from '@/portainer/hooks/useUser';
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
|
|
||||||
import { Ingress } from '../../types';
|
import { Ingress } from '../../types';
|
||||||
|
@ -8,17 +10,22 @@ export const name: Column<Ingress> = {
|
||||||
Header: 'Name',
|
Header: 'Name',
|
||||||
accessor: 'Name',
|
accessor: 'Name',
|
||||||
Cell: ({ row }: CellProps<Ingress>) => (
|
Cell: ({ row }: CellProps<Ingress>) => (
|
||||||
<Link
|
<Authorized
|
||||||
to="kubernetes.ingresses.edit"
|
authorizations="K8sIngressesW"
|
||||||
params={{
|
childrenUnauthorized={row.original.Name}
|
||||||
uid: row.original.UID,
|
|
||||||
namespace: row.original.Namespace,
|
|
||||||
name: row.original.Name,
|
|
||||||
}}
|
|
||||||
title={row.original.Name}
|
|
||||||
>
|
>
|
||||||
{row.original.Name}
|
<Link
|
||||||
</Link>
|
to="kubernetes.ingresses.edit"
|
||||||
|
params={{
|
||||||
|
uid: row.original.UID,
|
||||||
|
namespace: row.original.Namespace,
|
||||||
|
name: row.original.Name,
|
||||||
|
}}
|
||||||
|
title={row.original.Name}
|
||||||
|
>
|
||||||
|
{row.original.Name}
|
||||||
|
</Link>
|
||||||
|
</Authorized>
|
||||||
),
|
),
|
||||||
id: 'name',
|
id: 'name',
|
||||||
disableFilters: true,
|
disableFilters: true,
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-repeat="ingress in $ctrl.applicationIngress">
|
<tr ng-repeat="ingress in $ctrl.applicationIngress">
|
||||||
<td
|
<td
|
||||||
><a ui-sref="kubernetes.ingresses.edit({ name: ingress.IngressName, namespace: $ctrl.application.ResourcePool })">{{ ingress.IngressName }}</a></td
|
><a authorization="K8sIngressesW" ui-sref="kubernetes.ingresses.edit({ name: ingress.IngressName, namespace: $ctrl.application.ResourcePool })">{{
|
||||||
|
ingress.IngressName
|
||||||
|
}}</a></td
|
||||||
>
|
>
|
||||||
<td>{{ ingress.ServiceName }}</td>
|
<td>{{ ingress.ServiceName }}</td>
|
||||||
<td>{{ ingress.Host }}</td>
|
<td>{{ ingress.Host }}</td>
|
||||||
|
|
|
@ -123,6 +123,7 @@ interface AuthorizedProps {
|
||||||
authorizations: string | string[];
|
authorizations: string | string[];
|
||||||
environmentId?: EnvironmentId;
|
environmentId?: EnvironmentId;
|
||||||
adminOnlyCE?: boolean;
|
adminOnlyCE?: boolean;
|
||||||
|
childrenUnauthorized?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Authorized({
|
export function Authorized({
|
||||||
|
@ -130,6 +131,7 @@ export function Authorized({
|
||||||
environmentId,
|
environmentId,
|
||||||
adminOnlyCE = false,
|
adminOnlyCE = false,
|
||||||
children,
|
children,
|
||||||
|
childrenUnauthorized = null,
|
||||||
}: PropsWithChildren<AuthorizedProps>) {
|
}: PropsWithChildren<AuthorizedProps>) {
|
||||||
const isAllowed = useAuthorizations(
|
const isAllowed = useAuthorizations(
|
||||||
authorizations,
|
authorizations,
|
||||||
|
@ -137,7 +139,7 @@ export function Authorized({
|
||||||
adminOnlyCE
|
adminOnlyCE
|
||||||
);
|
);
|
||||||
|
|
||||||
return isAllowed ? <>{children}</> : null;
|
return isAllowed ? <>{children}</> : <>{childrenUnauthorized}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserProviderProps {
|
interface UserProviderProps {
|
||||||
|
|
Loading…
Reference in New Issue