github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/kubernetes/backend.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/hashicorp/terraform/internal/backend"
    12  	"github.com/hashicorp/terraform/internal/legacy/helper/schema"
    13  	"github.com/hashicorp/terraform/version"
    14  	"github.com/mitchellh/go-homedir"
    15  	k8sSchema "k8s.io/apimachinery/pkg/runtime/schema"
    16  	"k8s.io/client-go/dynamic"
    17  	"k8s.io/client-go/kubernetes"
    18  	coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
    19  	restclient "k8s.io/client-go/rest"
    20  	"k8s.io/client-go/tools/clientcmd"
    21  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    22  )
    23  
    24  // Modified from github.com/terraform-providers/terraform-provider-kubernetes
    25  
    26  const (
    27  	noConfigError = `
    28  
    29  [Kubernetes backend] Neither service_account nor load_config_file were set to true, 
    30  this could cause issues connecting to your Kubernetes cluster.
    31  `
    32  )
    33  
    34  var (
    35  	secretResource = k8sSchema.GroupVersionResource{
    36  		Group:    "",
    37  		Version:  "v1",
    38  		Resource: "secrets",
    39  	}
    40  )
    41  
    42  // New creates a new backend for kubernetes remote state.
    43  func New() backend.Backend {
    44  	s := &schema.Backend{
    45  		Schema: map[string]*schema.Schema{
    46  			"secret_suffix": {
    47  				Type:        schema.TypeString,
    48  				Required:    true,
    49  				Description: "Suffix used when creating the secret. The secret will be named in the format: `tfstate-{workspace}-{secret_suffix}`.",
    50  			},
    51  			"labels": {
    52  				Type:        schema.TypeMap,
    53  				Optional:    true,
    54  				Description: "Map of additional labels to be applied to the secret.",
    55  				Elem:        &schema.Schema{Type: schema.TypeString},
    56  			},
    57  			"namespace": {
    58  				Type:        schema.TypeString,
    59  				Optional:    true,
    60  				DefaultFunc: schema.EnvDefaultFunc("KUBE_NAMESPACE", "default"),
    61  				Description: "Namespace to store the secret in.",
    62  			},
    63  			"in_cluster_config": {
    64  				Type:        schema.TypeBool,
    65  				Optional:    true,
    66  				DefaultFunc: schema.EnvDefaultFunc("KUBE_IN_CLUSTER_CONFIG", false),
    67  				Description: "Used to authenticate to the cluster from inside a pod.",
    68  			},
    69  			"load_config_file": {
    70  				Type:        schema.TypeBool,
    71  				Optional:    true,
    72  				DefaultFunc: schema.EnvDefaultFunc("KUBE_LOAD_CONFIG_FILE", true),
    73  				Description: "Load local kubeconfig.",
    74  			},
    75  			"host": {
    76  				Type:        schema.TypeString,
    77  				Optional:    true,
    78  				DefaultFunc: schema.EnvDefaultFunc("KUBE_HOST", ""),
    79  				Description: "The hostname (in form of URI) of Kubernetes master.",
    80  			},
    81  			"username": {
    82  				Type:        schema.TypeString,
    83  				Optional:    true,
    84  				DefaultFunc: schema.EnvDefaultFunc("KUBE_USER", ""),
    85  				Description: "The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
    86  			},
    87  			"password": {
    88  				Type:        schema.TypeString,
    89  				Optional:    true,
    90  				DefaultFunc: schema.EnvDefaultFunc("KUBE_PASSWORD", ""),
    91  				Description: "The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
    92  			},
    93  			"insecure": {
    94  				Type:        schema.TypeBool,
    95  				Optional:    true,
    96  				DefaultFunc: schema.EnvDefaultFunc("KUBE_INSECURE", false),
    97  				Description: "Whether server should be accessed without verifying the TLS certificate.",
    98  			},
    99  			"client_certificate": {
   100  				Type:        schema.TypeString,
   101  				Optional:    true,
   102  				DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_CERT_DATA", ""),
   103  				Description: "PEM-encoded client certificate for TLS authentication.",
   104  			},
   105  			"client_key": {
   106  				Type:        schema.TypeString,
   107  				Optional:    true,
   108  				DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_KEY_DATA", ""),
   109  				Description: "PEM-encoded client certificate key for TLS authentication.",
   110  			},
   111  			"cluster_ca_certificate": {
   112  				Type:        schema.TypeString,
   113  				Optional:    true,
   114  				DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""),
   115  				Description: "PEM-encoded root certificates bundle for TLS authentication.",
   116  			},
   117  			"config_paths": {
   118  				Type:        schema.TypeList,
   119  				Elem:        &schema.Schema{Type: schema.TypeString},
   120  				Optional:    true,
   121  				Description: "A list of paths to kube config files. Can be set with KUBE_CONFIG_PATHS environment variable.",
   122  			},
   123  			"config_path": {
   124  				Type:        schema.TypeString,
   125  				Optional:    true,
   126  				DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_PATH", ""),
   127  				Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH environment variable.",
   128  			},
   129  			"config_context": {
   130  				Type:        schema.TypeString,
   131  				Optional:    true,
   132  				DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX", ""),
   133  			},
   134  			"config_context_auth_info": {
   135  				Type:        schema.TypeString,
   136  				Optional:    true,
   137  				DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_AUTH_INFO", ""),
   138  				Description: "",
   139  			},
   140  			"config_context_cluster": {
   141  				Type:        schema.TypeString,
   142  				Optional:    true,
   143  				DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_CLUSTER", ""),
   144  				Description: "",
   145  			},
   146  			"token": {
   147  				Type:        schema.TypeString,
   148  				Optional:    true,
   149  				DefaultFunc: schema.EnvDefaultFunc("KUBE_TOKEN", ""),
   150  				Description: "Token to authentifcate a service account.",
   151  			},
   152  			"exec": {
   153  				Type:     schema.TypeList,
   154  				Optional: true,
   155  				MaxItems: 1,
   156  				Elem: &schema.Resource{
   157  					Schema: map[string]*schema.Schema{
   158  						"api_version": {
   159  							Type:     schema.TypeString,
   160  							Required: true,
   161  						},
   162  						"command": {
   163  							Type:     schema.TypeString,
   164  							Required: true,
   165  						},
   166  						"env": {
   167  							Type:     schema.TypeMap,
   168  							Optional: true,
   169  							Elem:     &schema.Schema{Type: schema.TypeString},
   170  						},
   171  						"args": {
   172  							Type:     schema.TypeList,
   173  							Optional: true,
   174  							Elem:     &schema.Schema{Type: schema.TypeString},
   175  						},
   176  					},
   177  				},
   178  				Description: "Use a credential plugin to authenticate.",
   179  			},
   180  		},
   181  	}
   182  
   183  	result := &Backend{Backend: s}
   184  	result.Backend.ConfigureFunc = result.configure
   185  	return result
   186  }
   187  
   188  type Backend struct {
   189  	*schema.Backend
   190  
   191  	// The fields below are set from configure
   192  	kubernetesSecretClient dynamic.ResourceInterface
   193  	kubernetesLeaseClient  coordinationv1.LeaseInterface
   194  	config                 *restclient.Config
   195  	namespace              string
   196  	labels                 map[string]string
   197  	nameSuffix             string
   198  }
   199  
   200  func (b Backend) KubernetesSecretClient() (dynamic.ResourceInterface, error) {
   201  	if b.kubernetesSecretClient != nil {
   202  		return b.kubernetesSecretClient, nil
   203  	}
   204  
   205  	client, err := dynamic.NewForConfig(b.config)
   206  	if err != nil {
   207  		return nil, fmt.Errorf("Failed to configure: %s", err)
   208  	}
   209  
   210  	b.kubernetesSecretClient = client.Resource(secretResource).Namespace(b.namespace)
   211  	return b.kubernetesSecretClient, nil
   212  }
   213  
   214  func (b Backend) KubernetesLeaseClient() (coordinationv1.LeaseInterface, error) {
   215  	if b.kubernetesLeaseClient != nil {
   216  		return b.kubernetesLeaseClient, nil
   217  	}
   218  
   219  	client, err := kubernetes.NewForConfig(b.config)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	b.kubernetesLeaseClient = client.CoordinationV1().Leases(b.namespace)
   225  	return b.kubernetesLeaseClient, nil
   226  }
   227  
   228  func (b *Backend) configure(ctx context.Context) error {
   229  	if b.config != nil {
   230  		return nil
   231  	}
   232  
   233  	// Grab the resource data
   234  	data := schema.FromContextBackendConfig(ctx)
   235  
   236  	cfg, err := getInitialConfig(data)
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	// Overriding with static configuration
   242  	cfg.UserAgent = fmt.Sprintf("HashiCorp/1.0 Terraform/%s", version.String())
   243  
   244  	if v, ok := data.GetOk("host"); ok {
   245  		cfg.Host = v.(string)
   246  	}
   247  	if v, ok := data.GetOk("username"); ok {
   248  		cfg.Username = v.(string)
   249  	}
   250  	if v, ok := data.GetOk("password"); ok {
   251  		cfg.Password = v.(string)
   252  	}
   253  	if v, ok := data.GetOk("insecure"); ok {
   254  		cfg.Insecure = v.(bool)
   255  	}
   256  	if v, ok := data.GetOk("cluster_ca_certificate"); ok {
   257  		cfg.CAData = bytes.NewBufferString(v.(string)).Bytes()
   258  	}
   259  	if v, ok := data.GetOk("client_certificate"); ok {
   260  		cfg.CertData = bytes.NewBufferString(v.(string)).Bytes()
   261  	}
   262  	if v, ok := data.GetOk("client_key"); ok {
   263  		cfg.KeyData = bytes.NewBufferString(v.(string)).Bytes()
   264  	}
   265  	if v, ok := data.GetOk("token"); ok {
   266  		cfg.BearerToken = v.(string)
   267  	}
   268  
   269  	if v, ok := data.GetOk("labels"); ok {
   270  		labels := map[string]string{}
   271  		for k, vv := range v.(map[string]interface{}) {
   272  			labels[k] = vv.(string)
   273  		}
   274  		b.labels = labels
   275  	}
   276  
   277  	ns := data.Get("namespace").(string)
   278  	b.namespace = ns
   279  	b.nameSuffix = data.Get("secret_suffix").(string)
   280  	b.config = cfg
   281  
   282  	return nil
   283  }
   284  
   285  func getInitialConfig(data *schema.ResourceData) (*restclient.Config, error) {
   286  	var cfg *restclient.Config
   287  	var err error
   288  
   289  	inCluster := data.Get("in_cluster_config").(bool)
   290  	if inCluster {
   291  		cfg, err = restclient.InClusterConfig()
   292  		if err != nil {
   293  			return nil, err
   294  		}
   295  	} else {
   296  		cfg, err = tryLoadingConfigFile(data)
   297  		if err != nil {
   298  			return nil, err
   299  		}
   300  	}
   301  
   302  	if cfg == nil {
   303  		cfg = &restclient.Config{}
   304  	}
   305  	return cfg, err
   306  }
   307  
   308  func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) {
   309  	loader := &clientcmd.ClientConfigLoadingRules{}
   310  
   311  	configPaths := []string{}
   312  	if v, ok := d.Get("config_path").(string); ok && v != "" {
   313  		configPaths = []string{v}
   314  	} else if v, ok := d.Get("config_paths").([]interface{}); ok && len(v) > 0 {
   315  		for _, p := range v {
   316  			configPaths = append(configPaths, p.(string))
   317  		}
   318  	} else if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" {
   319  		configPaths = filepath.SplitList(v)
   320  	}
   321  
   322  	expandedPaths := []string{}
   323  	for _, p := range configPaths {
   324  		path, err := homedir.Expand(p)
   325  		if err != nil {
   326  			log.Printf("[DEBUG] Could not expand path: %s", err)
   327  			return nil, err
   328  		}
   329  		log.Printf("[DEBUG] Using kubeconfig: %s", path)
   330  		expandedPaths = append(expandedPaths, path)
   331  	}
   332  
   333  	if len(expandedPaths) == 1 {
   334  		loader.ExplicitPath = expandedPaths[0]
   335  	} else {
   336  		loader.Precedence = expandedPaths
   337  	}
   338  
   339  	overrides := &clientcmd.ConfigOverrides{}
   340  	ctxSuffix := "; default context"
   341  
   342  	ctx, ctxOk := d.GetOk("config_context")
   343  	authInfo, authInfoOk := d.GetOk("config_context_auth_info")
   344  	cluster, clusterOk := d.GetOk("config_context_cluster")
   345  	if ctxOk || authInfoOk || clusterOk {
   346  		ctxSuffix = "; overriden context"
   347  		if ctxOk {
   348  			overrides.CurrentContext = ctx.(string)
   349  			ctxSuffix += fmt.Sprintf("; config ctx: %s", overrides.CurrentContext)
   350  			log.Printf("[DEBUG] Using custom current context: %q", overrides.CurrentContext)
   351  		}
   352  
   353  		overrides.Context = clientcmdapi.Context{}
   354  		if authInfoOk {
   355  			overrides.Context.AuthInfo = authInfo.(string)
   356  			ctxSuffix += fmt.Sprintf("; auth_info: %s", overrides.Context.AuthInfo)
   357  		}
   358  		if clusterOk {
   359  			overrides.Context.Cluster = cluster.(string)
   360  			ctxSuffix += fmt.Sprintf("; cluster: %s", overrides.Context.Cluster)
   361  		}
   362  		log.Printf("[DEBUG] Using overidden context: %#v", overrides.Context)
   363  	}
   364  
   365  	if v, ok := d.GetOk("exec"); ok {
   366  		exec := &clientcmdapi.ExecConfig{}
   367  		if spec, ok := v.([]interface{})[0].(map[string]interface{}); ok {
   368  			exec.APIVersion = spec["api_version"].(string)
   369  			exec.Command = spec["command"].(string)
   370  			exec.Args = expandStringSlice(spec["args"].([]interface{}))
   371  			for kk, vv := range spec["env"].(map[string]interface{}) {
   372  				exec.Env = append(exec.Env, clientcmdapi.ExecEnvVar{Name: kk, Value: vv.(string)})
   373  			}
   374  		} else {
   375  			return nil, fmt.Errorf("Failed to parse exec")
   376  		}
   377  		overrides.AuthInfo.Exec = exec
   378  	}
   379  
   380  	cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides)
   381  	cfg, err := cc.ClientConfig()
   382  	if err != nil {
   383  		if pathErr, ok := err.(*os.PathError); ok && os.IsNotExist(pathErr.Err) {
   384  			log.Printf("[INFO] Unable to load config file as it doesn't exist at %q", pathErr.Path)
   385  			return nil, nil
   386  		}
   387  		return nil, fmt.Errorf("Failed to initialize kubernetes configuration: %s", err)
   388  	}
   389  
   390  	log.Printf("[INFO] Successfully initialized config")
   391  	return cfg, nil
   392  }
   393  
   394  func expandStringSlice(s []interface{}) []string {
   395  	result := make([]string, len(s), len(s))
   396  	for k, v := range s {
   397  		// Handle the Terraform parser bug which turns empty strings in lists to nil.
   398  		if v == nil {
   399  			result[k] = ""
   400  		} else {
   401  			result[k] = v.(string)
   402  		}
   403  	}
   404  	return result
   405  }