Add proxy configuration at the context level (#2675)

mine
Alexandru Placinta 2025-01-12 18:17:19 +02:00 committed by GitHub
parent 388df15dd6
commit b3bd60b67f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 90 additions and 12 deletions

View File

@ -128,11 +128,6 @@ func loadConfiguration() (*config.Config, error) {
k8sCfg := client.NewConfig(k8sFlags)
k9sCfg := config.NewConfig(k8sCfg)
var errs error
conn, err := client.InitConnection(k8sCfg)
k9sCfg.SetConnection(conn)
if err != nil {
errs = errors.Join(errs, err)
}
if err := k9sCfg.Load(config.AppConfigFile, false); err != nil {
errs = errors.Join(errs, err)
@ -142,14 +137,24 @@ func loadConfiguration() (*config.Config, error) {
log.Error().Err(err).Msgf("config refine failed")
errs = errors.Join(errs, err)
}
conn, err := client.InitConnection(k8sCfg)
if err != nil {
errs = errors.Join(errs, err)
}
// Try to access server version if that fail. Connectivity issue?
if !conn.CheckConnectivity() {
errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()))
}
if !conn.ConnectionOK() {
errs = errors.Join(errs, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()))
}
k9sCfg.SetConnection(conn)
log.Info().Msg("✅ Kubernetes connectivity")
if err := k9sCfg.Save(false); err != nil {
log.Error().Err(err).Msg("Config save")

View File

@ -286,8 +286,8 @@ func (a *APIClient) CheckConnectivity() bool {
}
}()
// Need reload to pick up any kubeconfig changes.
cfg, err := NewConfig(a.config.flags).RESTConfig()
cfg, err := a.config.RESTConfig()
if err != nil {
log.Error().Err(err).Msgf("restConfig load failed")
a.connOK = false
@ -551,9 +551,8 @@ func (a *APIClient) SwitchContext(name string) error {
a.reset()
ResetMetrics()
if !a.CheckConnectivity() {
return fmt.Errorf("unable to connect to context %q", name)
}
// Need reload to pick up any kubeconfig changes.
a.config = NewConfig(a.config.flags)
return nil
}

View File

@ -6,6 +6,8 @@ package client
import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
@ -27,6 +29,7 @@ const (
type Config struct {
flags *genericclioptions.ConfigFlags
mx sync.RWMutex
proxy func(*http.Request) (*url.URL, error)
}
// NewConfig returns a new k8s config or an error if the flags are invalid.
@ -50,7 +53,17 @@ func (c *Config) CallTimeout() time.Duration {
}
func (c *Config) RESTConfig() (*restclient.Config, error) {
return c.clientConfig().ClientConfig()
cfg, err := c.clientConfig().ClientConfig()
if err != nil {
return nil, err
}
if c.proxy != nil {
cfg.Proxy = c.proxy
}
return cfg, nil
}
// Flags returns configuration flags.
@ -179,6 +192,15 @@ func (c *Config) GetContext(n string) (*api.Context, error) {
return nil, fmt.Errorf("getcontext - invalid context specified: %q", n)
}
func (c *Config) SetProxy(proxy func(*http.Request) (*url.URL, error)) {
c.proxy = proxy
}
func (c *Config) WithProxy(proxy func(*http.Request) (*url.URL, error)) *Config {
c.SetProxy(proxy)
return c
}
// Contexts fetch all available contexts.
func (c *Config) Contexts() (map[string]*api.Context, error) {
cfg, err := c.RawConfig()

View File

@ -20,6 +20,7 @@ type Context struct {
View *View `yaml:"view"`
FeatureGates FeatureGates `yaml:"featureGates"`
PortForwardAddress string `yaml:"portForwardAddress"`
Proxy *Proxy `yaml:"proxy"`
mx sync.RWMutex
}

View File

@ -0,0 +1,6 @@
package data
// Proxy tracks a context's proxy configuration.
type Proxy struct {
Address string `yaml:"address"`
}

View File

@ -4,6 +4,8 @@
package data
import (
"net/http"
"net/url"
"os"
"github.com/derailed/k9s/internal/config/json"
@ -43,4 +45,7 @@ type KubeSettings interface {
// GetContext returns a given context configuration or err if not found.
GetContext(string) (*api.Context, error)
// SetProxy sets the proxy for the active context, if present
SetProxy(proxy func(*http.Request) (*url.URL, error))
}

View File

@ -11,6 +11,19 @@
"readOnly": {"type": "boolean"},
"skin": { "type": "string" },
"portForwardAddress": { "type": "string" },
"proxy": {
"oneOf": [
{ "type": "null" },
{
"type": "object",
"additionalProperties": false,
"properties":
{
"address": {"type": "string"}
}
}
]
},
"namespace": {
"type": "object",
"additionalProperties": false,

View File

@ -7,6 +7,8 @@ import (
"errors"
"fmt"
"io/fs"
"net/http"
"net/url"
"os"
"path/filepath"
"sync"
@ -216,6 +218,27 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) {
}
k.setActiveConfig(cfg)
if cfg.Context.Proxy != nil {
k.ks.SetProxy(func(*http.Request) (*url.URL, error) {
log.Debug().Msgf("[Proxy]: %s", cfg.Context.Proxy.Address)
return url.Parse(cfg.Context.Proxy.Address)
})
if k.conn != nil && k.conn.Config() != nil {
// We get on this branch when the user switches the context and k9s
// already has an API connection object so we just set the proxy to
// avoid recreation using client.InitConnection
k.conn.Config().SetProxy(func(*http.Request) (*url.URL, error) {
log.Debug().Msgf("[Proxy]: %s", cfg.Context.Proxy.Address)
return url.Parse(cfg.Context.Proxy.Address)
})
if !k.conn.CheckConnectivity() {
return nil, fmt.Errorf("unable to connect to context %q", n)
}
}
}
k.Validate(k.conn, k.ks)
// If the context specifies a namespace, use it!
if ns := ct.Namespace; ns != client.BlankNamespace {

View File

@ -7,6 +7,8 @@ import (
"errors"
"fmt"
"io/fs"
"net/http"
"net/url"
"os"
"strings"
@ -106,6 +108,8 @@ func (m mockKubeSettings) ContextNames() (map[string]struct{}, error) {
return mm, nil
}
func (m mockKubeSettings) SetProxy(proxy func(*http.Request) (*url.URL, error)) {}
type mockConnection struct {
ct string
}