providers/kubernetes/kubernetes_provider.go (211 lines of code) (raw):
// Copyright 2018 The Terraformer 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 kubernetes
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"time"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"github.com/GoogleCloudPlatform/terraformer/terraformutils/providerwrapper"
"github.com/zclconf/go-cty/cty"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/discovery"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // GKE support
)
type KubernetesProvider struct { //nolint
terraformutils.Provider
verbose string
}
func (p KubernetesProvider) GetResourceConnections() map[string]map[string][]string {
return map[string]map[string][]string{}
}
func (p KubernetesProvider) GetProviderData(arg ...string) map[string]interface{} {
return map[string]interface{}{}
}
func (p *KubernetesProvider) Init(args []string) error {
p.verbose = args[0]
return nil
}
func (p *KubernetesProvider) GetName() string {
return "kubernetes"
}
func (p *KubernetesProvider) InitService(serviceName string, verbose bool) error {
var isSupported bool
if _, isSupported = p.GetSupportedService()[serviceName]; !isSupported {
return errors.New("kubernetes: " + serviceName + " not supported resource")
}
p.Service = p.GetSupportedService()[serviceName]
p.Service.SetName(serviceName)
p.Service.SetVerbose(verbose)
p.Service.SetProviderName(p.GetName())
return nil
}
// GetSupportService return map of supported resource for Kubernetes
func (p *KubernetesProvider) GetSupportedService() map[string]terraformutils.ServiceGenerator {
resources := make(map[string]terraformutils.ServiceGenerator)
config, _, err := initClientAndConfig()
if err != nil {
return resources
}
dc, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
log.Println(err)
return resources
}
lists, err := dc.ServerPreferredResources()
if err != nil {
log.Println(err)
return resources
}
provider, err := providerwrapper.NewProviderWrapper("kubernetes", cty.Value{}, p.verbose == "true")
if err != nil {
log.Println(err)
return resources
}
resp := provider.GetSchema()
for _, list := range lists {
if len(list.APIResources) == 0 {
continue
}
gv, err := schema.ParseGroupVersion(list.GroupVersion)
if err != nil {
continue
}
for _, resource := range list.APIResources {
if len(resource.Verbs) == 0 {
continue
}
// filter to resources that support list
if len(resource.Verbs) > 0 && !sets.NewString(resource.Verbs...).Has("list") {
continue
}
// filter to resource that are supported by terraform kubernetes provider
if _, ok := resp.ResourceTypes[extractTfResourceName(resource.Kind)]; !ok {
continue
}
resources[resource.Name] = &Kind{
Group: gv.Group,
Version: gv.Version,
Name: resource.Kind,
Namespaced: resource.Namespaced,
}
}
}
return resources
}
// InitClientAndConfig uses the KUBECONFIG environment variable to create
// a new rest client and config object based on the existing kubectl config
// and options passed from the plugin framework via environment variables
func initClientAndConfig() (*restclient.Config, clientcmd.ClientConfig, error) { //nolint
// resolve kubeconfig location, prioritizing the --config global flag,
// then the value of the KUBECONFIG env var (if any), and defaulting
// to ~/.kube/config as a last resort.
home := os.Getenv("HOME")
if runtime.GOOS == "windows" {
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
}
kubeconfig := filepath.Join(home, ".kube", "config")
kubeconfigEnv := os.Getenv("KUBECONFIG")
if len(kubeconfigEnv) > 0 {
kubeconfig = kubeconfigEnv
}
configFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONFIG")
kubeConfigFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_KUBECONFIG")
if len(configFile) > 0 {
kubeconfig = configFile
} else if len(kubeConfigFile) > 0 {
kubeconfig = kubeConfigFile
}
if len(kubeconfig) == 0 {
return nil, nil, fmt.Errorf("error initializing config. The KUBECONFIG environment variable must be defined")
}
config, err := configFromPath(kubeconfig)
if err != nil {
return nil, nil, fmt.Errorf("error obtaining kubectl config: %v", err)
}
client, err := config.ClientConfig()
if err != nil {
return nil, nil, fmt.Errorf("the provided credentials %q could not be used: %v", kubeconfig, err)
}
err = applyGlobalOptionsToConfig(client)
if err != nil {
return nil, nil, fmt.Errorf("error processing global plugin options: %v", err)
}
return client, config, nil
}
func configFromPath(path string) (clientcmd.ClientConfig, error) {
rules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: path}
credentials, err := rules.Load()
if err != nil {
return nil, fmt.Errorf("the provided credentials %q could not be loaded: %v", path, err)
}
overrides := &clientcmd.ConfigOverrides{
Context: clientcmdapi.Context{
Namespace: os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_NAMESPACE"),
},
}
var cfg clientcmd.ClientConfig
context := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CONTEXT")
if len(context) > 0 {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
cfg = clientcmd.NewNonInteractiveClientConfig(*credentials, context, overrides, rules)
} else {
cfg = clientcmd.NewDefaultClientConfig(*credentials, overrides)
}
return cfg, nil
}
func applyGlobalOptionsToConfig(config *restclient.Config) error {
// impersonation config
impersonateUser := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS")
if len(impersonateUser) > 0 {
config.Impersonate.UserName = impersonateUser
}
impersonateGroup := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_AS_GROUP")
if len(impersonateGroup) > 0 {
impersonateGroupJSON := []string{}
err := json.Unmarshal([]byte(impersonateGroup), &impersonateGroupJSON)
if err != nil {
return errors.New(fmt.Sprintf("error parsing global option %q: %v", "--as-group", err))
}
if len(impersonateGroupJSON) > 0 {
config.Impersonate.Groups = impersonateGroupJSON
}
}
// tls config
caFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CERTIFICATE_AUTHORITY")
if len(caFile) > 0 {
config.TLSClientConfig.CAFile = caFile
}
clientCertFile := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_CERTIFICATE")
if len(clientCertFile) > 0 {
config.TLSClientConfig.CertFile = clientCertFile
}
clientKey := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_CLIENT_KEY")
if len(clientKey) > 0 {
config.TLSClientConfig.KeyFile = clientKey
}
// user / misc request config
requestTimeout := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_REQUEST_TIMEOUT")
if len(requestTimeout) > 0 {
t, err := time.ParseDuration(requestTimeout)
if err != nil {
return errors.New(fmt.Sprintf("%v", err))
}
config.Timeout = t
}
server := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_SERVER")
if len(server) > 0 {
config.ServerName = server
}
token := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_TOKEN")
if len(token) > 0 {
config.BearerToken = token
}
username := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_USERNAME")
if len(username) > 0 {
config.Username = username
}
password := os.Getenv("KUBECTL_PLUGINS_GLOBAL_FLAG_PASSWORD")
if len(password) > 0 {
config.Password = password
}
return nil
}