mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-20 10:27:46 +08:00

Replace rules are not inherited by consumers of buildx as a module, and as such would default to use the v0.26.2 version. Removing the replace rules also removes various (indirect) dependencies (although brings in some new packages from k8s itself). The "azure" and "gcp" authentication packages in k8s.io/go-client are now no longer functional, so removing those imports. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
639 lines
26 KiB
Go
639 lines
26 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package clientcmd
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"unicode"
|
|
|
|
restclient "k8s.io/client-go/rest"
|
|
clientauth "k8s.io/client-go/tools/auth"
|
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
"k8s.io/klog/v2"
|
|
|
|
"github.com/imdario/mergo"
|
|
)
|
|
|
|
const (
|
|
// clusterExtensionKey is reserved in the cluster extensions list for exec plugin config.
|
|
clusterExtensionKey = "client.authentication.k8s.io/exec"
|
|
)
|
|
|
|
var (
|
|
// ClusterDefaults has the same behavior as the old EnvVar and DefaultCluster fields
|
|
// DEPRECATED will be replaced
|
|
ClusterDefaults = clientcmdapi.Cluster{Server: getDefaultServer()}
|
|
// DefaultClientConfig represents the legacy behavior of this package for defaulting
|
|
// DEPRECATED will be replace
|
|
DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{
|
|
ClusterDefaults: ClusterDefaults,
|
|
}, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
|
|
)
|
|
|
|
// getDefaultServer returns a default setting for DefaultClientConfig
|
|
// DEPRECATED
|
|
func getDefaultServer() string {
|
|
if server := os.Getenv("KUBERNETES_MASTER"); len(server) > 0 {
|
|
return server
|
|
}
|
|
return "http://localhost:8080"
|
|
}
|
|
|
|
// ClientConfig is used to make it easy to get an api server client
|
|
type ClientConfig interface {
|
|
// RawConfig returns the merged result of all overrides
|
|
RawConfig() (clientcmdapi.Config, error)
|
|
// ClientConfig returns a complete client config
|
|
ClientConfig() (*restclient.Config, error)
|
|
// Namespace returns the namespace resulting from the merged
|
|
// result of all overrides and a boolean indicating if it was
|
|
// overridden
|
|
Namespace() (string, bool, error)
|
|
// ConfigAccess returns the rules for loading/persisting the config.
|
|
ConfigAccess() ConfigAccess
|
|
}
|
|
|
|
type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
|
|
|
|
type promptedCredentials struct {
|
|
username string
|
|
password string `datapolicy:"password"`
|
|
}
|
|
|
|
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
|
|
type DirectClientConfig struct {
|
|
config clientcmdapi.Config
|
|
contextName string
|
|
overrides *ConfigOverrides
|
|
fallbackReader io.Reader
|
|
configAccess ConfigAccess
|
|
// promptedCredentials store the credentials input by the user
|
|
promptedCredentials promptedCredentials
|
|
}
|
|
|
|
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
|
|
func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
|
|
return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
|
|
}
|
|
|
|
// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
|
|
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
|
|
return &DirectClientConfig{config, contextName, overrides, nil, configAccess, promptedCredentials{}}
|
|
}
|
|
|
|
// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
|
|
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
|
|
return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
|
|
}
|
|
|
|
// NewClientConfigFromBytes takes your kubeconfig and gives you back a ClientConfig
|
|
func NewClientConfigFromBytes(configBytes []byte) (ClientConfig, error) {
|
|
config, err := Load(configBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &DirectClientConfig{*config, "", &ConfigOverrides{}, nil, nil, promptedCredentials{}}, nil
|
|
}
|
|
|
|
// RESTConfigFromKubeConfig is a convenience method to give back a restconfig from your kubeconfig bytes.
|
|
// For programmatic access, this is what you want 80% of the time
|
|
func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error) {
|
|
clientConfig, err := NewClientConfigFromBytes(configBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return clientConfig.ClientConfig()
|
|
}
|
|
|
|
func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
|
return config.config, nil
|
|
}
|
|
|
|
// ClientConfig implements ClientConfig
|
|
func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
|
// check that getAuthInfo, getContext, and getCluster do not return an error.
|
|
// Do this before checking if the current config is usable in the event that an
|
|
// AuthInfo, Context, or Cluster config with user-defined names are not found.
|
|
// This provides a user with the immediate cause for error if one is found
|
|
configAuthInfo, err := config.getAuthInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = config.getContext()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configClusterInfo, err := config.getCluster()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := config.ConfirmUsable(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clientConfig := &restclient.Config{}
|
|
clientConfig.Host = configClusterInfo.Server
|
|
if configClusterInfo.ProxyURL != "" {
|
|
u, err := parseProxyURL(configClusterInfo.ProxyURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clientConfig.Proxy = http.ProxyURL(u)
|
|
}
|
|
|
|
clientConfig.DisableCompression = configClusterInfo.DisableCompression
|
|
|
|
if config.overrides != nil && len(config.overrides.Timeout) > 0 {
|
|
timeout, err := ParseTimeout(config.overrides.Timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clientConfig.Timeout = timeout
|
|
}
|
|
|
|
if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
|
|
u.RawQuery = ""
|
|
u.Fragment = ""
|
|
clientConfig.Host = u.String()
|
|
}
|
|
if len(configAuthInfo.Impersonate) > 0 {
|
|
clientConfig.Impersonate = restclient.ImpersonationConfig{
|
|
UserName: configAuthInfo.Impersonate,
|
|
UID: configAuthInfo.ImpersonateUID,
|
|
Groups: configAuthInfo.ImpersonateGroups,
|
|
Extra: configAuthInfo.ImpersonateUserExtra,
|
|
}
|
|
}
|
|
|
|
// only try to read the auth information if we are secure
|
|
if restclient.IsConfigTransportTLS(*clientConfig) {
|
|
var err error
|
|
var persister restclient.AuthProviderConfigPersister
|
|
if config.configAccess != nil {
|
|
authInfoName, _ := config.getAuthInfoName()
|
|
persister = PersisterForUser(config.configAccess, authInfoName)
|
|
}
|
|
userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister, configClusterInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mergo.Merge(clientConfig, userAuthPartialConfig, mergo.WithOverride)
|
|
|
|
serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mergo.Merge(clientConfig, serverAuthPartialConfig, mergo.WithOverride)
|
|
}
|
|
|
|
return clientConfig, nil
|
|
}
|
|
|
|
// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
|
|
// both, so we have to split the objects and merge them separately
|
|
// we want this order of precedence for the server identification
|
|
// 1. configClusterInfo (the final result of command line flags and merged .kubeconfig files)
|
|
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
|
|
// 3. load the ~/.kubernetes_auth file as a default
|
|
func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
|
|
mergedConfig := &restclient.Config{}
|
|
|
|
// configClusterInfo holds the information identify the server provided by .kubeconfig
|
|
configClientConfig := &restclient.Config{}
|
|
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
|
|
configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
|
|
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
|
|
configClientConfig.ServerName = configClusterInfo.TLSServerName
|
|
mergo.Merge(mergedConfig, configClientConfig, mergo.WithOverride)
|
|
|
|
return mergedConfig, nil
|
|
}
|
|
|
|
// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
|
|
// both, so we have to split the objects and merge them separately
|
|
// we want this order of precedence for user identification
|
|
// 1. configAuthInfo minus auth-path (the final result of command line flags and merged .kubeconfig files)
|
|
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
|
|
// 3. if there is not enough information to identify the user, load try the ~/.kubernetes_auth file
|
|
// 4. if there is not enough information to identify the user, prompt if possible
|
|
func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
|
|
mergedConfig := &restclient.Config{}
|
|
|
|
// blindly overwrite existing values based on precedence
|
|
if len(configAuthInfo.Token) > 0 {
|
|
mergedConfig.BearerToken = configAuthInfo.Token
|
|
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
|
} else if len(configAuthInfo.TokenFile) > 0 {
|
|
tokenBytes, err := os.ReadFile(configAuthInfo.TokenFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mergedConfig.BearerToken = string(tokenBytes)
|
|
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
|
}
|
|
if len(configAuthInfo.Impersonate) > 0 {
|
|
mergedConfig.Impersonate = restclient.ImpersonationConfig{
|
|
UserName: configAuthInfo.Impersonate,
|
|
UID: configAuthInfo.ImpersonateUID,
|
|
Groups: configAuthInfo.ImpersonateGroups,
|
|
Extra: configAuthInfo.ImpersonateUserExtra,
|
|
}
|
|
}
|
|
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
|
mergedConfig.CertFile = configAuthInfo.ClientCertificate
|
|
mergedConfig.CertData = configAuthInfo.ClientCertificateData
|
|
mergedConfig.KeyFile = configAuthInfo.ClientKey
|
|
mergedConfig.KeyData = configAuthInfo.ClientKeyData
|
|
}
|
|
if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
|
|
mergedConfig.Username = configAuthInfo.Username
|
|
mergedConfig.Password = configAuthInfo.Password
|
|
}
|
|
if configAuthInfo.AuthProvider != nil {
|
|
mergedConfig.AuthProvider = configAuthInfo.AuthProvider
|
|
mergedConfig.AuthConfigPersister = persistAuthConfig
|
|
}
|
|
if configAuthInfo.Exec != nil {
|
|
mergedConfig.ExecProvider = configAuthInfo.Exec
|
|
mergedConfig.ExecProvider.InstallHint = cleanANSIEscapeCodes(mergedConfig.ExecProvider.InstallHint)
|
|
mergedConfig.ExecProvider.Config = configClusterInfo.Extensions[clusterExtensionKey]
|
|
}
|
|
|
|
// if there still isn't enough information to authenticate the user, try prompting
|
|
if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
|
|
if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
|
|
mergedConfig.Username = config.promptedCredentials.username
|
|
mergedConfig.Password = config.promptedCredentials.password
|
|
return mergedConfig, nil
|
|
}
|
|
prompter := NewPromptingAuthLoader(fallbackReader)
|
|
promptedAuthInfo, err := prompter.Prompt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
|
|
previouslyMergedConfig := mergedConfig
|
|
mergedConfig = &restclient.Config{}
|
|
mergo.Merge(mergedConfig, promptedConfig, mergo.WithOverride)
|
|
mergo.Merge(mergedConfig, previouslyMergedConfig, mergo.WithOverride)
|
|
config.promptedCredentials.username = mergedConfig.Username
|
|
config.promptedCredentials.password = mergedConfig.Password
|
|
}
|
|
|
|
return mergedConfig, nil
|
|
}
|
|
|
|
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
|
|
func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
|
|
config := &restclient.Config{}
|
|
config.Username = info.User
|
|
config.Password = info.Password
|
|
config.CertFile = info.CertFile
|
|
config.KeyFile = info.KeyFile
|
|
config.BearerToken = info.BearerToken
|
|
return config
|
|
}
|
|
|
|
func canIdentifyUser(config restclient.Config) bool {
|
|
return len(config.Username) > 0 ||
|
|
(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
|
|
len(config.BearerToken) > 0 ||
|
|
config.AuthProvider != nil ||
|
|
config.ExecProvider != nil
|
|
}
|
|
|
|
// cleanANSIEscapeCodes takes an arbitrary string and ensures that there are no
|
|
// ANSI escape sequences that could put the terminal in a weird state (e.g.,
|
|
// "\e[1m" bolds text)
|
|
func cleanANSIEscapeCodes(s string) string {
|
|
// spaceControlCharacters includes tab, new line, vertical tab, new page, and
|
|
// carriage return. These are in the unicode.Cc category, but that category also
|
|
// contains ESC (U+001B) which we don't want.
|
|
spaceControlCharacters := unicode.RangeTable{
|
|
R16: []unicode.Range16{
|
|
{Lo: 0x0009, Hi: 0x000D, Stride: 1},
|
|
},
|
|
}
|
|
|
|
// Why not make this deny-only (instead of allow-only)? Because unicode.C
|
|
// contains newline and tab characters that we want.
|
|
allowedRanges := []*unicode.RangeTable{
|
|
unicode.L,
|
|
unicode.M,
|
|
unicode.N,
|
|
unicode.P,
|
|
unicode.S,
|
|
unicode.Z,
|
|
&spaceControlCharacters,
|
|
}
|
|
builder := strings.Builder{}
|
|
for _, roon := range s {
|
|
if unicode.IsOneOf(allowedRanges, roon) {
|
|
builder.WriteRune(roon) // returns nil error, per go doc
|
|
} else {
|
|
fmt.Fprintf(&builder, "%U", roon)
|
|
}
|
|
}
|
|
return builder.String()
|
|
}
|
|
|
|
// Namespace implements ClientConfig
|
|
func (config *DirectClientConfig) Namespace() (string, bool, error) {
|
|
if config.overrides != nil && config.overrides.Context.Namespace != "" {
|
|
// In the event we have an empty config but we do have a namespace override, we should return
|
|
// the namespace override instead of having config.ConfirmUsable() return an error. This allows
|
|
// things like in-cluster clients to execute `kubectl get pods --namespace=foo` and have the
|
|
// --namespace flag honored instead of being ignored.
|
|
return config.overrides.Context.Namespace, true, nil
|
|
}
|
|
|
|
if err := config.ConfirmUsable(); err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
configContext, err := config.getContext()
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
if len(configContext.Namespace) == 0 {
|
|
return "default", false, nil
|
|
}
|
|
|
|
return configContext.Namespace, false, nil
|
|
}
|
|
|
|
// ConfigAccess implements ClientConfig
|
|
func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
|
|
return config.configAccess
|
|
}
|
|
|
|
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
|
|
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
|
|
func (config *DirectClientConfig) ConfirmUsable() error {
|
|
validationErrors := make([]error, 0)
|
|
|
|
var contextName string
|
|
if len(config.contextName) != 0 {
|
|
contextName = config.contextName
|
|
} else {
|
|
contextName = config.config.CurrentContext
|
|
}
|
|
|
|
if len(contextName) > 0 {
|
|
_, exists := config.config.Contexts[contextName]
|
|
if !exists {
|
|
validationErrors = append(validationErrors, &errContextNotFound{contextName})
|
|
}
|
|
}
|
|
|
|
authInfoName, _ := config.getAuthInfoName()
|
|
authInfo, _ := config.getAuthInfo()
|
|
validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
|
|
clusterName, _ := config.getClusterName()
|
|
cluster, _ := config.getCluster()
|
|
validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
|
|
// when direct client config is specified, and our only error is that no server is defined, we should
|
|
// return a standard "no config" error
|
|
if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
|
|
return newErrConfigurationInvalid([]error{ErrEmptyConfig})
|
|
}
|
|
return newErrConfigurationInvalid(validationErrors)
|
|
}
|
|
|
|
// getContextName returns the default, or user-set context name, and a boolean that indicates
|
|
// whether the default context name has been overwritten by a user-set flag, or left as its default value
|
|
func (config *DirectClientConfig) getContextName() (string, bool) {
|
|
if config.overrides != nil && len(config.overrides.CurrentContext) != 0 {
|
|
return config.overrides.CurrentContext, true
|
|
}
|
|
if len(config.contextName) != 0 {
|
|
return config.contextName, false
|
|
}
|
|
|
|
return config.config.CurrentContext, false
|
|
}
|
|
|
|
// getAuthInfoName returns a string containing the current authinfo name for the current context,
|
|
// and a boolean indicating whether the default authInfo name is overwritten by a user-set flag, or
|
|
// left as its default value
|
|
func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
|
|
if config.overrides != nil && len(config.overrides.Context.AuthInfo) != 0 {
|
|
return config.overrides.Context.AuthInfo, true
|
|
}
|
|
context, _ := config.getContext()
|
|
return context.AuthInfo, false
|
|
}
|
|
|
|
// getClusterName returns a string containing the default, or user-set cluster name, and a boolean
|
|
// indicating whether the default clusterName has been overwritten by a user-set flag, or left as
|
|
// its default value
|
|
func (config *DirectClientConfig) getClusterName() (string, bool) {
|
|
if config.overrides != nil && len(config.overrides.Context.Cluster) != 0 {
|
|
return config.overrides.Context.Cluster, true
|
|
}
|
|
context, _ := config.getContext()
|
|
return context.Cluster, false
|
|
}
|
|
|
|
// getContext returns the clientcmdapi.Context, or an error if a required context is not found.
|
|
func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
|
|
contexts := config.config.Contexts
|
|
contextName, required := config.getContextName()
|
|
|
|
mergedContext := clientcmdapi.NewContext()
|
|
if configContext, exists := contexts[contextName]; exists {
|
|
mergo.Merge(mergedContext, configContext, mergo.WithOverride)
|
|
} else if required {
|
|
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
|
|
}
|
|
if config.overrides != nil {
|
|
mergo.Merge(mergedContext, config.overrides.Context, mergo.WithOverride)
|
|
}
|
|
|
|
return *mergedContext, nil
|
|
}
|
|
|
|
// getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
|
|
func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
|
|
authInfos := config.config.AuthInfos
|
|
authInfoName, required := config.getAuthInfoName()
|
|
|
|
mergedAuthInfo := clientcmdapi.NewAuthInfo()
|
|
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
|
mergo.Merge(mergedAuthInfo, configAuthInfo, mergo.WithOverride)
|
|
} else if required {
|
|
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
|
|
}
|
|
if config.overrides != nil {
|
|
mergo.Merge(mergedAuthInfo, config.overrides.AuthInfo, mergo.WithOverride)
|
|
}
|
|
|
|
return *mergedAuthInfo, nil
|
|
}
|
|
|
|
// getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
|
|
func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
|
|
clusterInfos := config.config.Clusters
|
|
clusterInfoName, required := config.getClusterName()
|
|
|
|
mergedClusterInfo := clientcmdapi.NewCluster()
|
|
if config.overrides != nil {
|
|
mergo.Merge(mergedClusterInfo, config.overrides.ClusterDefaults, mergo.WithOverride)
|
|
}
|
|
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
|
mergo.Merge(mergedClusterInfo, configClusterInfo, mergo.WithOverride)
|
|
} else if required {
|
|
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
|
|
}
|
|
if config.overrides != nil {
|
|
mergo.Merge(mergedClusterInfo, config.overrides.ClusterInfo, mergo.WithOverride)
|
|
}
|
|
|
|
// * An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
|
|
// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set".
|
|
// * An override of --certificate-authority should also override TLS skip settings and CA data, otherwise existing CA data will take precedence.
|
|
if config.overrides != nil {
|
|
caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
|
|
caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
|
|
if config.overrides.ClusterInfo.InsecureSkipTLSVerify || caLen > 0 || caDataLen > 0 {
|
|
mergedClusterInfo.InsecureSkipTLSVerify = config.overrides.ClusterInfo.InsecureSkipTLSVerify
|
|
mergedClusterInfo.CertificateAuthority = config.overrides.ClusterInfo.CertificateAuthority
|
|
mergedClusterInfo.CertificateAuthorityData = config.overrides.ClusterInfo.CertificateAuthorityData
|
|
}
|
|
|
|
// if the --tls-server-name has been set in overrides, use that value.
|
|
// if the --server has been set in overrides, then use the value of --tls-server-name specified on the CLI too. This gives the property
|
|
// that setting a --server will effectively clear the KUBECONFIG value of tls-server-name if it is specified on the command line which is
|
|
// usually correct.
|
|
if config.overrides.ClusterInfo.TLSServerName != "" || config.overrides.ClusterInfo.Server != "" {
|
|
mergedClusterInfo.TLSServerName = config.overrides.ClusterInfo.TLSServerName
|
|
}
|
|
}
|
|
|
|
return *mergedClusterInfo, nil
|
|
}
|
|
|
|
// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
|
|
// Can take options overrides for flags explicitly provided to the command inside the cluster container.
|
|
type inClusterClientConfig struct {
|
|
overrides *ConfigOverrides
|
|
inClusterConfigProvider func() (*restclient.Config, error)
|
|
}
|
|
|
|
var _ ClientConfig = &inClusterClientConfig{}
|
|
|
|
func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
|
return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
|
|
}
|
|
|
|
func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
|
|
inClusterConfigProvider := config.inClusterConfigProvider
|
|
if inClusterConfigProvider == nil {
|
|
inClusterConfigProvider = restclient.InClusterConfig
|
|
}
|
|
|
|
icc, err := inClusterConfigProvider()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// in-cluster configs only takes a host, token, or CA file
|
|
// if any of them were individually provided, overwrite anything else
|
|
if config.overrides != nil {
|
|
if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
|
|
icc.Host = server
|
|
}
|
|
if len(config.overrides.AuthInfo.Token) > 0 || len(config.overrides.AuthInfo.TokenFile) > 0 {
|
|
icc.BearerToken = config.overrides.AuthInfo.Token
|
|
icc.BearerTokenFile = config.overrides.AuthInfo.TokenFile
|
|
}
|
|
if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
|
|
icc.TLSClientConfig.CAFile = certificateAuthorityFile
|
|
}
|
|
}
|
|
|
|
return icc, nil
|
|
}
|
|
|
|
func (config *inClusterClientConfig) Namespace() (string, bool, error) {
|
|
// This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
|
|
// This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
|
|
if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
|
|
return ns, false, nil
|
|
}
|
|
|
|
// Fall back to the namespace associated with the service account token, if available
|
|
if data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
|
|
if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
|
|
return ns, false, nil
|
|
}
|
|
}
|
|
|
|
return "default", false, nil
|
|
}
|
|
|
|
func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
|
|
return NewDefaultClientConfigLoadingRules()
|
|
}
|
|
|
|
// Possible returns true if loading an inside-kubernetes-cluster is possible.
|
|
func (config *inClusterClientConfig) Possible() bool {
|
|
fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
|
|
return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
|
|
os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
|
|
err == nil && !fi.IsDir()
|
|
}
|
|
|
|
// BuildConfigFromFlags is a helper function that builds configs from a master
|
|
// url or a kubeconfig filepath. These are passed in as command line flags for cluster
|
|
// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
|
|
// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
|
|
// to the default config.
|
|
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
|
|
if kubeconfigPath == "" && masterUrl == "" {
|
|
klog.Warning("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
|
|
kubeconfig, err := restclient.InClusterConfig()
|
|
if err == nil {
|
|
return kubeconfig, nil
|
|
}
|
|
klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
|
|
}
|
|
return NewNonInteractiveDeferredLoadingClientConfig(
|
|
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
|
|
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
|
|
}
|
|
|
|
// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
|
|
// url and a kubeconfigGetter.
|
|
func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
|
|
// TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
|
|
cc := NewNonInteractiveDeferredLoadingClientConfig(
|
|
&ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
|
|
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
|
|
return cc.ClientConfig()
|
|
}
|