feat(kubernetes): Gateway api client included in kubeclient [C9S-244] (#2884)

This commit is contained in:
Dakota Walsh
2026-06-18 14:37:42 +12:00
committed by GitHub
parent 0dfd27f08c
commit f5d0b3d849
5 changed files with 59 additions and 22 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ import (
func (service *service) upgradeKubernetes(environment *portainer.Endpoint, licenseKey, version string) error {
ctx := context.TODO()
kubeCLI, err := service.kubernetesClientFactory.CreateClient(environment)
kubeCLI, _, err := service.kubernetesClientFactory.CreateClient(environment)
if err != nil {
return errors.WithMessage(err, "failed to get kubernetes client")
}
+46 -12
View File
@@ -20,6 +20,7 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
metricsv "k8s.io/metrics/pkg/client/clientset/versioned"
gatewaycliv1 "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/typed/apis/v1"
)
const (
@@ -42,6 +43,7 @@ type (
// KubeClient represent a service used to execute Kubernetes operations
KubeClient struct {
cli kubernetes.Interface
gatewayCLI gatewaycliv1.GatewayV1Interface
instanceID string
mu sync.Mutex
isKubeAdmin bool
@@ -105,8 +107,10 @@ func (factory *ClientFactory) GetAddrHTTPS() string {
return factory.AddrHTTPS
}
// GetPrivilegedKubeClient checks if an existing client is already registered for the environment(endpoint) and returns it if one is found.
// If no client is registered, it will create a new client, register it, and returns it.
// GetPrivilegedKubeClient checks if an existing client is already registered
// for the environment(endpoint) and returns it if one is found.
//
// If no client is registered, it will create a new client, register it, and return it.
func (factory *ClientFactory) GetPrivilegedKubeClient(endpoint *portainer.Endpoint) (*KubeClient, error) {
key := strconv.Itoa(int(endpoint.ID))
pcl, ok := factory.endpointProxyClients.Get(key)
@@ -123,8 +127,11 @@ func (factory *ClientFactory) GetPrivilegedKubeClient(endpoint *portainer.Endpoi
return kcl, nil
}
// GetPrivilegedUserKubeClient checks if an existing admin client is already registered for the environment(endpoint) and user and returns it if one is found.
// If no client is registered, it will create a new client, register it, and returns it.
// GetPrivilegedUserKubeClient checks if an existing admin client is already
// registered for the environment(endpoint) and user and returns it if one is
// found.
//
// If no client is registered, it will create a new client, register it, and return it.
func (factory *ClientFactory) GetPrivilegedUserKubeClient(endpoint *portainer.Endpoint, userID portainer.UserID) (*KubeClient, error) {
key := strconv.Itoa(int(endpoint.ID)) + ".admin." + strconv.Itoa(int(userID))
pcl, ok := factory.endpointProxyClients.Get(key)
@@ -174,13 +181,24 @@ func (factory *ClientFactory) CreateKubeClientFromKubeConfig(clusterID string, k
clientConfig.QPS = defaultKubeClientQPS
clientConfig.Burst = defaultKubeClientBurst
cli, err := kubernetes.NewForConfig(clientConfig)
httpClient, err := rest.HTTPClientFor(clientConfig)
if err != nil {
return nil, fmt.Errorf("failed to create a new clientset for the given config: %w", err)
return nil, fmt.Errorf("failed to create http client for the given config: %w", err)
}
cli, err := kubernetes.NewForConfigAndClient(clientConfig, httpClient)
if err != nil {
return nil, fmt.Errorf("failed to create clientset for the given config: %w", err)
}
gatewayCLI, err := gatewaycliv1.NewForConfigAndClient(clientConfig, httpClient)
if err != nil {
return nil, fmt.Errorf("failed to create gateway clientset for the given config: %w", err)
}
return &KubeClient{
cli: cli,
gatewayCLI: gatewayCLI,
instanceID: factory.instanceID,
isKubeAdmin: IsKubeAdmin,
nonAdminNamespaces: NonAdminNamespaces,
@@ -188,29 +206,45 @@ func (factory *ClientFactory) CreateKubeClientFromKubeConfig(clusterID string, k
}
func (factory *ClientFactory) createCachedPrivilegedKubeClient(endpoint *portainer.Endpoint) (*KubeClient, error) {
cli, err := factory.CreateClient(endpoint)
cli, gatewayCLI, err := factory.CreateClient(endpoint)
if err != nil {
return nil, err
}
return &KubeClient{
cli: cli,
gatewayCLI: gatewayCLI,
instanceID: factory.instanceID,
isKubeAdmin: true,
}, nil
}
// CreateClient returns a pointer to a new Clientset instance.
func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, error) {
// CreateClient returns a pointer to a new Kubernetes Core Clientset instance.
func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint) (*kubernetes.Clientset, *gatewaycliv1.GatewayV1Client, error) {
switch endpoint.Type {
case portainer.KubernetesLocalEnvironment, portainer.AgentOnKubernetesEnvironment, portainer.EdgeAgentOnKubernetesEnvironment:
c, err := factory.CreateConfig(endpoint)
if err != nil {
return nil, err
return nil, nil, err
}
return kubernetes.NewForConfig(c)
httpClient, err := rest.HTTPClientFor(c)
if err != nil {
return nil, nil, fmt.Errorf("failed to create http client for the given config: %w", err)
}
cli, err := kubernetes.NewForConfigAndClient(c, httpClient)
if err != nil {
return nil, nil, fmt.Errorf("failed to create clientset for the given config: %w", err)
}
gatewayCLI, err := gatewaycliv1.NewForConfigAndClient(c, httpClient)
if err != nil {
return nil, nil, fmt.Errorf("failed to create gateway clientset for the given config: %w", err)
}
return cli, gatewayCLI, nil
}
return nil, errors.New("unsupported environment type")
return nil, nil, errors.New("unsupported environment type")
}
// CreateConfig returns a pointer to a new kubeconfig ready to create a client.
+1 -1
View File
@@ -19,7 +19,7 @@ func NewSnapshotter(clientFactory *cli.ClientFactory) *Snapshotter {
// CreateSnapshot creates a snapshot of a specific Kubernetes environment(endpoint)
func (snapshotter *Snapshotter) CreateSnapshot(endpoint *portainer.Endpoint) (*portainer.KubernetesSnapshot, error) {
client, err := snapshotter.clientFactory.CreateClient(endpoint)
client, _, err := snapshotter.clientFactory.CreateClient(endpoint)
if err != nil {
return nil, err
}
+3 -2
View File
@@ -72,6 +72,7 @@ require (
k8s.io/kubectl v0.35.1
k8s.io/metrics v0.35.1
oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/gateway-api v1.5.1
)
require (
@@ -364,13 +365,13 @@ require (
k8s.io/component-helpers v0.35.1 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect
sigs.k8s.io/controller-runtime v0.23.1 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/kustomize/api v0.21.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
tags.cncf.io/container-device-interface v1.1.0 // indirect
)
+8 -6
View File
@@ -757,8 +757,8 @@ github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.148.0 h1:CiTjQE/Hh5xK2t56ogrDK4nl0+tJPNmASCs4zEYZ/xU=
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.148.0/go.mod h1:WUFkzTiOpt7EYyL67gv1GOf3RD8qKWGtin3lY9LYzW4=
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.148.0 h1:1TLg6YrS3Au6F7xw3ws2Njbwj13IMqPplvGFi+18fWs=
@@ -1268,12 +1268,14 @@ k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg=
k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo=
k8s.io/metrics v0.35.1 h1:MUcrUcWlq81XiripkydzCGsY9zQawDXfP9IICNNcVVw=
k8s.io/metrics v0.35.1/go.mod h1:9x7xWOAOiWzHA0vaqLgSE4PXF3vyT5ts5XIbx8OSjiI=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE=
sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
sigs.k8s.io/gateway-api v1.5.1 h1:RqVRIlkhLhUO8wOHKTLnTJA6o/1un4po4/6M1nRzdd0=
sigs.k8s.io/gateway-api v1.5.1/go.mod h1:GvCETiaMAlLym5CovLxGjS0NysqFk3+Yuq3/rh6QL2o=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=
@@ -1282,8 +1284,8 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
tags.cncf.io/container-device-interface v1.1.0 h1:RnxNhxF1JOu6CJUVpetTYvrXHdxw9j9jFYgZpI+anSY=