Skip to content

Commit

Permalink
Prepare cli-go to accept --kubeconfig setting
Browse files Browse the repository at this point in the history
This introduces yet another global state. An alternative could be to
load the kubeconfig eagerly, when ConfigureKubeConfig is called. However,
this would require to call this setup method in all tests which make use
of the k8s rest client, which is a severe disadvantage.

Signed-off-by: Cornelius Weig <22861411+corneliusweig@users.noreply.github.com>
  • Loading branch information
corneliusweig committed Oct 22, 2019
1 parent af21e9c commit 2790ddf
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 22 deletions.
2 changes: 1 addition & 1 deletion cmd/skaffold/app/cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func createNewRunner(opts config.SkaffoldOptions) (runner.Runner, *latest.Skaffo
return nil, nil, errors.Wrap(err, "applying profiles")
}

kubectx.UseKubeContext(opts.KubeContext, config.Deploy.KubeContext)
kubectx.ConfigureKubeConfig(opts.KubeConfig, opts.KubeContext, config.Deploy.KubeContext)

if err := defaults.Set(config); err != nil {
return nil, nil, errors.Wrap(err, "setting default values")
Expand Down
34 changes: 19 additions & 15 deletions pkg/skaffold/kubernetes/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,26 @@ var (
)

var (
kubeConfigOnce sync.Once
kubeConfig clientcmd.ClientConfig
kubeContextOnce sync.Once
kubeContext string
kubeConfigOnce sync.Once
kubeConfig clientcmd.ClientConfig

configureOnce sync.Once
kubeContext string
kubeConfigFile string
)

// UseKubeContext sets an override for the current context in the k8s config.
// ConfigureKubeConfig sets an override for the current context in the k8s config.
// When given, the firstCliValue always takes precedence over the yamlValue.
// Changing the kube-context of a running Skaffold process is not supported, so
// after the first call, the kube-context will be locked.
func UseKubeContext(cliValue, yamlValue string) {
newKubeContext := yamlValue
if cliValue != "" {
newKubeContext = cliValue
func ConfigureKubeConfig(cliKubeConfig, cliKubeContext, yamlKubeContext string) {
newKubeContext := yamlKubeContext
if cliKubeContext != "" {
newKubeContext = cliKubeContext
}
kubeContextOnce.Do(func() {
configureOnce.Do(func() {
kubeContext = newKubeContext
kubeConfigFile = cliKubeConfig
if kubeContext != "" {
logrus.Infof("Activated kube-context %q", kubeContext)
}
Expand All @@ -60,23 +63,23 @@ func UseKubeContext(cliValue, yamlValue string) {
}

// GetRestClientConfig returns a REST client config for API calls against the Kubernetes API.
// If UseKubeContext was called before, the CurrentContext will be overridden.
// If ConfigureKubeConfig was called before, the CurrentContext will be overridden.
// The kubeconfig used will be cached for the life of the skaffold process after the first call.
// If the CurrentContext is empty and the resulting config is empty, this method attempts to
// create a RESTClient with an in-cluster config.
func GetRestClientConfig() (*restclient.Config, error) {
return getRestClientConfig(kubeContext)
return getRestClientConfig(kubeContext, kubeConfigFile)
}

func getRestClientConfig(kctx string) (*restclient.Config, error) {
func getRestClientConfig(kctx string, kcfg string) (*restclient.Config, error) {
logrus.Debugf("getting client config for kubeContext: `%s`", kctx)
rawConfig, err := getRawKubeConfig()
if err != nil {
return nil, err
}
clientConfig := clientcmd.NewNonInteractiveClientConfig(rawConfig, kctx, &clientcmd.ConfigOverrides{CurrentContext: kctx}, nil)
restConfig, err := clientConfig.ClientConfig()
if kctx == "" && clientcmd.IsEmptyConfig(err) {
if kctx == "" && kcfg == "" && clientcmd.IsEmptyConfig(err) {
logrus.Debug("no kube-context set and no kubeConfig found, attempting in-cluster config")
restConfig, err := restclient.InClusterConfig()
return restConfig, errors.Wrap(err, "error creating REST client config in-cluster")
Expand All @@ -85,7 +88,7 @@ func getRestClientConfig(kctx string) (*restclient.Config, error) {
return restConfig, errors.Wrapf(err, "error creating REST client config for kubeContext '%s'", kctx)
}

// getCurrentConfig retrieves the kubeconfig file. If UseKubeContext was called before, the CurrentContext will be overridden.
// getCurrentConfig retrieves the kubeconfig file. If ConfigureKubeConfig was called before, the CurrentContext will be overridden.
// The result will be cached after the first call.
func getCurrentConfig() (clientcmdapi.Config, error) {
cfg, err := getRawKubeConfig()
Expand All @@ -101,6 +104,7 @@ func getCurrentConfig() (clientcmdapi.Config, error) {
func getRawKubeConfig() (clientcmdapi.Config, error) {
kubeConfigOnce.Do(func() {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.ExplicitPath = kubeConfigFile
kubeConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{
CurrentContext: kubeContext,
})
Expand Down
28 changes: 22 additions & 6 deletions pkg/skaffold/kubernetes/context/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,13 @@ clusters:
contexts:
- context:
cluster: cluster-bar
user: user1
name: cluster-bar
current-context: cluster-foo
user: user-bar
name: context-bar
- context:
cluster: cluster-bar
user: user-baz
name: context-baz
current-context: context-baz
users:
- name: user1
user:
Expand Down Expand Up @@ -96,6 +100,17 @@ func TestCurrentContext(t *testing.T) {
t.CheckDeepEqual(clusterBarContext, config.CurrentContext)
})

testutil.Run(t, "kubeconfig CLI flag takes precedence", func(t *testutil.T) {
resetKubeConfig(t, validKubeConfig)
kubeConfig := t.TempFile("config", []byte(changedKubeConfig))

kubeConfigFile = kubeConfig
config, err := CurrentConfig()

t.CheckNoError(err)
t.CheckDeepEqual("context-baz", config.CurrentContext)
})

testutil.Run(t, "invalid context", func(t *testutil.T) {
resetKubeConfig(t, "invalid")

Expand Down Expand Up @@ -172,7 +187,7 @@ func TestGetRestClientConfig(t *testing.T) {
t.SetEnvs(map[string]string{"KUBECONFIG": "non-valid"})
resetConfig()

_, err := getRestClientConfig("")
_, err := getRestClientConfig("", "")

if err == nil {
t.Errorf("expected error outside the cluster")
Expand Down Expand Up @@ -258,7 +273,7 @@ func TestUseKubeContext(t *testing.T) {
testutil.Run(t, test.name, func(t *testutil.T) {
kubeContext = ""
for _, inv := range test.invocations {
UseKubeContext(inv.cliValue, inv.yamlValue)
ConfigureKubeConfig("", inv.cliValue, inv.yamlValue)
}

t.CheckDeepEqual(test.expected, kubeContext)
Expand All @@ -270,12 +285,13 @@ func TestUseKubeContext(t *testing.T) {
// resetConfig is used by tests
func resetConfig() {
kubeConfigOnce = sync.Once{}
kubeContextOnce = sync.Once{}
configureOnce = sync.Once{}
}

func resetKubeConfig(t *testutil.T, content string) {
kubeConfig := t.TempFile("config", []byte(content))
t.SetEnvs(map[string]string{"KUBECONFIG": kubeConfig})
kubeContext = ""
kubeConfigFile = ""
resetConfig()
}

0 comments on commit 2790ddf

Please sign in to comment.