github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/context/context.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package context
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  
    24  	restclient "k8s.io/client-go/rest"
    25  	"k8s.io/client-go/tools/clientcmd"
    26  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    27  
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    29  )
    30  
    31  // For testing
    32  var (
    33  	CurrentConfig = getCurrentConfig
    34  )
    35  
    36  var (
    37  	kubeConfigOnce sync.Once
    38  	kubeConfig     clientcmd.ClientConfig
    39  
    40  	configureOnce  sync.Once
    41  	kubeContext    string
    42  	kubeConfigFile string
    43  )
    44  
    45  // ConfigureKubeConfig sets an override for the current context in the k8s config.
    46  // When given, the firstCliValue always takes precedence over the yamlValue.
    47  // Changing the kube-context of a running Skaffold process is not supported, so
    48  // after the first call, the kube-context will be locked.
    49  func ConfigureKubeConfig(cliKubeConfig, cliKubeContext string) {
    50  	configureOnce.Do(func() {
    51  		kubeContext = cliKubeContext
    52  		kubeConfigFile = cliKubeConfig
    53  		if kubeContext != "" {
    54  			log.Entry(context.TODO()).Infof("Activated kube-context %q", kubeContext)
    55  		}
    56  	})
    57  }
    58  
    59  // GetDefaultRestClientConfig returns a REST client config for API calls against the Kubernetes API.
    60  // If ConfigureKubeConfig was called before, the CurrentContext will be overridden.
    61  // The kubeconfig used will be cached for the life of the skaffold process after the first call.
    62  // If the CurrentContext is empty and the resulting config is empty, this method attempts to
    63  // create a RESTClient with an in-cluster config.
    64  func GetDefaultRestClientConfig() (*restclient.Config, error) {
    65  	return getRestClientConfig(kubeContext, kubeConfigFile)
    66  }
    67  
    68  // GetRestClientConfig returns a REST client config for API calls against the Kubernetes API for the given context.
    69  func GetRestClientConfig(kubeContext string) (*restclient.Config, error) {
    70  	return getRestClientConfig(kubeContext, kubeConfigFile)
    71  }
    72  
    73  // GetClusterInfo returns the Cluster information for the given kubeContext
    74  func GetClusterInfo(kctx string) (*clientcmdapi.Cluster, error) {
    75  	rawConfig, err := getCurrentConfig()
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	c, found := rawConfig.Clusters[kctx]
    80  	if !found {
    81  		return nil, fmt.Errorf("failed to get cluster info for kubeContext: `%s`", kctx)
    82  	}
    83  	return c, nil
    84  }
    85  
    86  func getRestClientConfig(kctx string, kcfg string) (*restclient.Config, error) {
    87  	log.Entry(context.TODO()).Debugf("getting client config for kubeContext: `%s`", kctx)
    88  
    89  	rawConfig, err := getCurrentConfig()
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	clientConfig := clientcmd.NewNonInteractiveClientConfig(rawConfig, kctx, &clientcmd.ConfigOverrides{CurrentContext: kctx}, clientcmd.NewDefaultClientConfigLoadingRules())
    95  	restConfig, err := clientConfig.ClientConfig()
    96  	if kctx == "" && kcfg == "" && clientcmd.IsEmptyConfig(err) {
    97  		log.Entry(context.TODO()).Debug("no kube-context set and no kubeConfig found, attempting in-cluster config")
    98  		restConfig, err := restclient.InClusterConfig()
    99  		if err != nil {
   100  			return restConfig, fmt.Errorf("error creating REST client config in-cluster: %w", err)
   101  		}
   102  
   103  		return restConfig, nil
   104  	}
   105  	if err != nil {
   106  		return restConfig, fmt.Errorf("error creating REST client config for kubeContext %q: %w", kctx, err)
   107  	}
   108  
   109  	return restConfig, nil
   110  }
   111  
   112  // getCurrentConfig retrieves and caches the raw kubeConfig. The cache ensures that Skaffold always works with the identical kubeconfig,
   113  // even if it was changed on disk.
   114  func getCurrentConfig() (clientcmdapi.Config, error) {
   115  	kubeConfigOnce.Do(func() {
   116  		loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
   117  		loadingRules.ExplicitPath = kubeConfigFile
   118  		kubeConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{
   119  			CurrentContext: kubeContext,
   120  		})
   121  	})
   122  
   123  	cfg, err := kubeConfig.RawConfig()
   124  	if kubeContext != "" {
   125  		// RawConfig does not respect the override in kubeConfig
   126  		cfg.CurrentContext = kubeContext
   127  	}
   128  	return cfg, err
   129  }