package kubernetes import ( "bytes" "fmt" "log" "os" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/go-homedir" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" ) func Provider() terraform.ResourceProvider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "host": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_HOST", ""), Description: "The hostname (in form of URI) of Kubernetes master.", }, "username": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_USER", ""), Description: "The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint.", }, "password": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_PASSWORD", ""), Description: "The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint.", }, "insecure": { Type: schema.TypeBool, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_INSECURE", false), Description: "Whether server should be accessed without verifying the TLS certificate.", }, "client_certificate": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_CERT_DATA", ""), Description: "PEM-encoded client certificate for TLS authentication.", }, "client_key": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_KEY_DATA", ""), Description: "PEM-encoded client certificate key for TLS authentication.", }, "cluster_ca_certificate": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""), Description: "PEM-encoded root certificates bundle for TLS authentication.", }, "config_path": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.MultiEnvDefaultFunc( []string{ "KUBE_CONFIG", "KUBECONFIG", }, "~/.kube/config"), Description: "Path to the kube config file, defaults to ~/.kube/config", }, "config_context": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX", ""), }, "config_context_auth_info": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_AUTH_INFO", ""), Description: "", }, "config_context_cluster": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_CLUSTER", ""), Description: "", }, }, ResourcesMap: map[string]*schema.Resource{ "kubernetes_config_map": resourceKubernetesConfigMap(), "kubernetes_horizontal_pod_autoscaler": resourceKubernetesHorizontalPodAutoscaler(), "kubernetes_limit_range": resourceKubernetesLimitRange(), "kubernetes_namespace": resourceKubernetesNamespace(), "kubernetes_persistent_volume": resourceKubernetesPersistentVolume(), "kubernetes_persistent_volume_claim": resourceKubernetesPersistentVolumeClaim(), "kubernetes_pod": resourceKubernetesPod(), "kubernetes_resource_quota": resourceKubernetesResourceQuota(), "kubernetes_secret": resourceKubernetesSecret(), "kubernetes_service": resourceKubernetesService(), }, ConfigureFunc: providerConfigure, } } func providerConfigure(d *schema.ResourceData) (interface{}, error) { // Config file loading cfg, err := tryLoadingConfigFile(d) if err != nil { return nil, err } if cfg == nil { cfg = &restclient.Config{} } // Overriding with static configuration cfg.UserAgent = fmt.Sprintf("HashiCorp/1.0 Terraform/%s", terraform.VersionString()) if v, ok := d.GetOk("host"); ok { cfg.Host = v.(string) } if v, ok := d.GetOk("username"); ok { cfg.Username = v.(string) } if v, ok := d.GetOk("password"); ok { cfg.Password = v.(string) } if v, ok := d.GetOk("insecure"); ok { cfg.Insecure = v.(bool) } if v, ok := d.GetOk("cluster_ca_certificate"); ok { cfg.CAData = bytes.NewBufferString(v.(string)).Bytes() } if v, ok := d.GetOk("client_certificate"); ok { cfg.CertData = bytes.NewBufferString(v.(string)).Bytes() } if v, ok := d.GetOk("client_key"); ok { cfg.KeyData = bytes.NewBufferString(v.(string)).Bytes() } k, err := kubernetes.NewForConfig(cfg) if err != nil { return nil, fmt.Errorf("Failed to configure: %s", err) } return k, nil } func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) { path, err := homedir.Expand(d.Get("config_path").(string)) if err != nil { return nil, err } loader := &clientcmd.ClientConfigLoadingRules{ ExplicitPath: path, } overrides := &clientcmd.ConfigOverrides{} ctxSuffix := "; default context" ctx, ctxOk := d.GetOk("config_context") authInfo, authInfoOk := d.GetOk("config_context_auth_info") cluster, clusterOk := d.GetOk("config_context_cluster") if ctxOk || authInfoOk || clusterOk { ctxSuffix = "; overriden context" if ctxOk { overrides.CurrentContext = ctx.(string) ctxSuffix += fmt.Sprintf("; config ctx: %s", overrides.CurrentContext) log.Printf("[DEBUG] Using custom current context: %q", overrides.CurrentContext) } overrides.Context = clientcmdapi.Context{} if authInfoOk { overrides.Context.AuthInfo = authInfo.(string) ctxSuffix += fmt.Sprintf("; auth_info: %s", overrides.Context.AuthInfo) } if clusterOk { overrides.Context.Cluster = cluster.(string) ctxSuffix += fmt.Sprintf("; cluster: %s", overrides.Context.Cluster) } log.Printf("[DEBUG] Using overidden context: %#v", overrides.Context) } cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides) cfg, err := cc.ClientConfig() if err != nil { if pathErr, ok := err.(*os.PathError); ok && os.IsNotExist(pathErr.Err) { log.Printf("[INFO] Unable to load config file as it doesn't exist at %q", path) return nil, nil } return nil, fmt.Errorf("Failed to load config (%s%s): %s", path, ctxSuffix, err) } log.Printf("[INFO] Successfully loaded config file (%s%s)", path, ctxSuffix) return cfg, nil }