github.com/GoogleCloudPlatform/terraformer@v0.8.18/providers/kubernetes/kubernetes_provider.go (about)

     1  // Copyright 2018 The Terraformer Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kubernetes
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"log"
    21  	"os"
    22  	"path/filepath"
    23  	"runtime"
    24  	"time"
    25  
    26  	restclient "k8s.io/client-go/rest"
    27  	"k8s.io/client-go/tools/clientcmd"
    28  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    29  
    30  	"github.com/GoogleCloudPlatform/terraformer/terraformutils"
    31  	"github.com/GoogleCloudPlatform/terraformer/terraformutils/providerwrapper"
    32  	"github.com/zclconf/go-cty/cty"
    33  
    34  	"github.com/pkg/errors"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/apimachinery/pkg/util/sets"
    37  	"k8s.io/client-go/discovery"
    38  	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // GKE support
    39  )
    40  
    41  type KubernetesProvider struct { //nolint
    42  	terraformutils.Provider
    43  	verbose string
    44  }
    45  
    46  func (p KubernetesProvider) GetResourceConnections() map[string]map[string][]string {
    47  	return map[string]map[string][]string{}
    48  }
    49  
    50  func (p KubernetesProvider) GetProviderData(arg ...string) map[string]interface{} {
    51  	return map[string]interface{}{}
    52  }
    53  
    54  func (p *KubernetesProvider) Init(args []string) error {
    55  	p.verbose = args[0]
    56  	return nil
    57  }
    58  
    59  func (p *KubernetesProvider) GetName() string {
    60  	return "kubernetes"
    61  }
    62  
    63  func (p *KubernetesProvider) InitService(serviceName string, verbose bool) error {
    64  	var isSupported bool
    65  	if _, isSupported = p.GetSupportedService()[serviceName]; !isSupported {
    66  		return errors.New("kubernetes: " + serviceName + " not supported resource")
    67  	}
    68  	p.Service = p.GetSupportedService()[serviceName]
    69  	p.Service.SetName(serviceName)
    70  	p.Service.SetVerbose(verbose)
    71  	p.Service.SetProviderName(p.GetName())
    72  	return nil
    73  }
    74  
    75  // GetSupportService return map of supported resource for Kubernetes
    76  func (p *KubernetesProvider) GetSupportedService() map[string]terraformutils.ServiceGenerator {
    77  	resources := make(map[string]terraformutils.ServiceGenerator)
    78  
    79  	config, _, err := initClientAndConfig()
    80  	if err != nil {
    81  		return resources
    82  	}
    83  
    84  	dc, err := discovery.NewDiscoveryClientForConfig(config)
    85  	if err != nil {
    86  		log.Println(err)
    87  		return resources
    88  	}
    89  
    90  	lists, err := dc.ServerPreferredResources()
    91  	if err != nil {
    92  		log.Println(err)
    93  		return resources
    94  	}
    95  	provider, err := providerwrapper.NewProviderWrapper("kubernetes", cty.Value{}, p.verbose == "true")
    96  	if err != nil {
    97  		log.Println(err)
    98  		return resources
    99  	}
   100  	resp := provider.GetSchema()
   101  	for _, list := range lists {
   102  		if len(list.APIResources) == 0 {
   103  			continue
   104  		}
   105  
   106  		gv, err := schema.ParseGroupVersion(list.GroupVersion)
   107  		if err != nil {
   108  			continue
   109  		}
   110  
   111  		for _, resource := range list.APIResources {
   112  			if len(resource.Verbs) == 0 {
   113  				continue
   114  			}
   115  
   116  			// filter to resources that support list
   117  			if len(resource.Verbs) > 0 && !sets.NewString(resource.Verbs...).Has("list") {
   118  				continue
   119  			}
   120  
   121  			// filter to resource that are supported by terraform kubernetes provider
   122  			if _, ok := resp.ResourceTypes[extractTfResourceName(resource.Kind)]; !ok {
   123  				continue
   124  			}
   125  
   126  			resources[resource.Name] = &Kind{
   127  				Group:      gv.Group,
   128  				Version:    gv.Version,
   129  				Name:       resource.Kind,
   130  				Namespaced: resource.Namespaced,
   131  			}
   132  		}
   133  	}
   134  	return resources
   135  }
   136  
   137  // InitClientAndConfig uses the KUBECONFIG environment variable to create
   138  // a new rest client and config object based on the existing kubectl config
   139  // and options passed from the plugin framework via environment variables
   140  func initClientAndConfig() (*restclient.Config, clientcmd.ClientConfig, error) { //nolint
   141  	// resolve kubeconfig location, prioritizing the --config global flag,
   142  	// then the value of the KUBECONFIG env var (if any), and defaulting
   143  	// to ~/.kube/config as a last resort.
   144  	home := os.Getenv("HOME")
   145  	if runtime.GOOS == "windows" {
   146  		home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
   147  		if home == "" {
   148  			home = os.Getenv("USERPROFILE")
   149  		}
   150  	}
   151  	kubeconfig := filepath.Join(home, ".kube", "config")
   152  
   153  	kubeconfigEnv := os.Getenv("KUBECONFIG")
   154  	if len(kubeconfigEnv) > 0 {
   155  		kubeconfig = kubeconfigEnv
   156  	}
   157  
   158  	configFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONFIG")
   159  	kubeConfigFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG")
   160  	if len(configFile) > 0 {
   161  		kubeconfig = configFile
   162  	} else if len(kubeConfigFile) > 0 {
   163  		kubeconfig = kubeConfigFile
   164  	}
   165  
   166  	if len(kubeconfig) == 0 {
   167  		return nil, nil, fmt.Errorf("error initializing config. The KUBECONFIG environment variable must be defined")
   168  	}
   169  
   170  	config, err := configFromPath(kubeconfig)
   171  	if err != nil {
   172  		return nil, nil, fmt.Errorf("error obtaining kubectl config: %v", err)
   173  	}
   174  	client, err := config.ClientConfig()
   175  	if err != nil {
   176  		return nil, nil, fmt.Errorf("the provided credentials %q could not be used: %v", kubeconfig, err)
   177  	}
   178  
   179  	err = applyGlobalOptionsToConfig(client)
   180  	if err != nil {
   181  		return nil, nil, fmt.Errorf("error processing global plugin options: %v", err)
   182  	}
   183  
   184  	return client, config, nil
   185  }
   186  
   187  func configFromPath(path string) (clientcmd.ClientConfig, error) {
   188  	rules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: path}
   189  	credentials, err := rules.Load()
   190  	if err != nil {
   191  		return nil, fmt.Errorf("the provided credentials %q could not be loaded: %v", path, err)
   192  	}
   193  
   194  	overrides := &clientcmd.ConfigOverrides{
   195  		Context: clientcmdapi.Context{
   196  			Namespace: os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_NAMESPACE"),
   197  		},
   198  	}
   199  
   200  	var cfg clientcmd.ClientConfig
   201  	context := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONTEXT")
   202  	if len(context) > 0 {
   203  		rules := clientcmd.NewDefaultClientConfigLoadingRules()
   204  		cfg = clientcmd.NewNonInteractiveClientConfig(*credentials, context, overrides, rules)
   205  	} else {
   206  		cfg = clientcmd.NewDefaultClientConfig(*credentials, overrides)
   207  	}
   208  
   209  	return cfg, nil
   210  }
   211  
   212  func applyGlobalOptionsToConfig(config *restclient.Config) error {
   213  	// impersonation config
   214  	impersonateUser := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS")
   215  	if len(impersonateUser) > 0 {
   216  		config.Impersonate.UserName = impersonateUser
   217  	}
   218  
   219  	impersonateGroup := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS_GROUP")
   220  	if len(impersonateGroup) > 0 {
   221  		impersonateGroupJSON := []string{}
   222  		err := json.Unmarshal([]byte(impersonateGroup), &impersonateGroupJSON)
   223  		if err != nil {
   224  			return errors.New(fmt.Sprintf("error parsing global option %q: %v", "--as-group", err))
   225  		}
   226  		if len(impersonateGroupJSON) > 0 {
   227  			config.Impersonate.Groups = impersonateGroupJSON
   228  		}
   229  	}
   230  
   231  	// tls config
   232  	caFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CERTIFICATE_AUTHORITY")
   233  	if len(caFile) > 0 {
   234  		config.TLSClientConfig.CAFile = caFile
   235  	}
   236  
   237  	clientCertFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_CERTIFICATE")
   238  	if len(clientCertFile) > 0 {
   239  		config.TLSClientConfig.CertFile = clientCertFile
   240  	}
   241  
   242  	clientKey := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_KEY")
   243  	if len(clientKey) > 0 {
   244  		config.TLSClientConfig.KeyFile = clientKey
   245  	}
   246  
   247  	// user / misc request config
   248  	requestTimeout := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_REQUEST_TIMEOUT")
   249  	if len(requestTimeout) > 0 {
   250  		t, err := time.ParseDuration(requestTimeout)
   251  		if err != nil {
   252  			return errors.New(fmt.Sprintf("%v", err))
   253  		}
   254  		config.Timeout = t
   255  	}
   256  
   257  	server := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_SERVER")
   258  	if len(server) > 0 {
   259  		config.ServerName = server
   260  	}
   261  
   262  	token := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_TOKEN")
   263  	if len(token) > 0 {
   264  		config.BearerToken = token
   265  	}
   266  
   267  	username := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USERNAME")
   268  	if len(username) > 0 {
   269  		config.Username = username
   270  	}
   271  
   272  	password := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_PASSWORD")
   273  	if len(password) > 0 {
   274  		config.Password = password
   275  	}
   276  
   277  	return nil
   278  }