Add proxy configuration at the context level (#2675)
parent
388df15dd6
commit
b3bd60b67f
15
cmd/root.go
15
cmd/root.go
|
|
@ -128,11 +128,6 @@ func loadConfiguration() (*config.Config, error) {
|
||||||
k8sCfg := client.NewConfig(k8sFlags)
|
k8sCfg := client.NewConfig(k8sFlags)
|
||||||
k9sCfg := config.NewConfig(k8sCfg)
|
k9sCfg := config.NewConfig(k8sCfg)
|
||||||
var errs error
|
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 {
|
if err := k9sCfg.Load(config.AppConfigFile, false); err != nil {
|
||||||
errs = errors.Join(errs, err)
|
errs = errors.Join(errs, err)
|
||||||
|
|
@ -142,14 +137,24 @@ func loadConfiguration() (*config.Config, error) {
|
||||||
log.Error().Err(err).Msgf("config refine failed")
|
log.Error().Err(err).Msgf("config refine failed")
|
||||||
errs = errors.Join(errs, err)
|
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?
|
// Try to access server version if that fail. Connectivity issue?
|
||||||
if !conn.CheckConnectivity() {
|
if !conn.CheckConnectivity() {
|
||||||
errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()))
|
errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !conn.ConnectionOK() {
|
if !conn.ConnectionOK() {
|
||||||
errs = errors.Join(errs, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()))
|
errs = errors.Join(errs, fmt.Errorf("k8s connection failed for context: %s", k9sCfg.K9s.ActiveContextName()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
k9sCfg.SetConnection(conn)
|
||||||
|
|
||||||
log.Info().Msg("✅ Kubernetes connectivity")
|
log.Info().Msg("✅ Kubernetes connectivity")
|
||||||
if err := k9sCfg.Save(false); err != nil {
|
if err := k9sCfg.Save(false); err != nil {
|
||||||
log.Error().Err(err).Msg("Config save")
|
log.Error().Err(err).Msg("Config save")
|
||||||
|
|
|
||||||
|
|
@ -286,8 +286,8 @@ func (a *APIClient) CheckConnectivity() bool {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Need reload to pick up any kubeconfig changes.
|
cfg, err := a.config.RESTConfig()
|
||||||
cfg, err := NewConfig(a.config.flags).RESTConfig()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("restConfig load failed")
|
log.Error().Err(err).Msgf("restConfig load failed")
|
||||||
a.connOK = false
|
a.connOK = false
|
||||||
|
|
@ -551,9 +551,8 @@ func (a *APIClient) SwitchContext(name string) error {
|
||||||
a.reset()
|
a.reset()
|
||||||
ResetMetrics()
|
ResetMetrics()
|
||||||
|
|
||||||
if !a.CheckConnectivity() {
|
// Need reload to pick up any kubeconfig changes.
|
||||||
return fmt.Errorf("unable to connect to context %q", name)
|
a.config = NewConfig(a.config.flags)
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ package client
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -27,6 +29,7 @@ const (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
flags *genericclioptions.ConfigFlags
|
flags *genericclioptions.ConfigFlags
|
||||||
mx sync.RWMutex
|
mx sync.RWMutex
|
||||||
|
proxy func(*http.Request) (*url.URL, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns a new k8s config or an error if the flags are invalid.
|
// 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) {
|
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.
|
// 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)
|
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.
|
// Contexts fetch all available contexts.
|
||||||
func (c *Config) Contexts() (map[string]*api.Context, error) {
|
func (c *Config) Contexts() (map[string]*api.Context, error) {
|
||||||
cfg, err := c.RawConfig()
|
cfg, err := c.RawConfig()
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type Context struct {
|
||||||
View *View `yaml:"view"`
|
View *View `yaml:"view"`
|
||||||
FeatureGates FeatureGates `yaml:"featureGates"`
|
FeatureGates FeatureGates `yaml:"featureGates"`
|
||||||
PortForwardAddress string `yaml:"portForwardAddress"`
|
PortForwardAddress string `yaml:"portForwardAddress"`
|
||||||
|
Proxy *Proxy `yaml:"proxy"`
|
||||||
mx sync.RWMutex
|
mx sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
// Proxy tracks a context's proxy configuration.
|
||||||
|
type Proxy struct {
|
||||||
|
Address string `yaml:"address"`
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config/json"
|
"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 returns a given context configuration or err if not found.
|
||||||
GetContext(string) (*api.Context, error)
|
GetContext(string) (*api.Context, error)
|
||||||
|
|
||||||
|
// SetProxy sets the proxy for the active context, if present
|
||||||
|
SetProxy(proxy func(*http.Request) (*url.URL, error))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@
|
||||||
"readOnly": {"type": "boolean"},
|
"readOnly": {"type": "boolean"},
|
||||||
"skin": { "type": "string" },
|
"skin": { "type": "string" },
|
||||||
"portForwardAddress": { "type": "string" },
|
"portForwardAddress": { "type": "string" },
|
||||||
|
"proxy": {
|
||||||
|
"oneOf": [
|
||||||
|
{ "type": "null" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties":
|
||||||
|
{
|
||||||
|
"address": {"type": "string"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"namespace": {
|
"namespace": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -216,6 +218,27 @@ func (k *K9s) ActivateContext(n string) (*data.Context, error) {
|
||||||
}
|
}
|
||||||
k.setActiveConfig(cfg)
|
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)
|
k.Validate(k.conn, k.ks)
|
||||||
// If the context specifies a namespace, use it!
|
// If the context specifies a namespace, use it!
|
||||||
if ns := ct.Namespace; ns != client.BlankNamespace {
|
if ns := ct.Namespace; ns != client.BlankNamespace {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -106,6 +108,8 @@ func (m mockKubeSettings) ContextNames() (map[string]struct{}, error) {
|
||||||
return mm, nil
|
return mm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m mockKubeSettings) SetProxy(proxy func(*http.Request) (*url.URL, error)) {}
|
||||||
|
|
||||||
type mockConnection struct {
|
type mockConnection struct {
|
||||||
ct string
|
ct string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue