Skip to content

Commit

Permalink
Fix in-cluster config - issue #13 (#14)
Browse files Browse the repository at this point in the history
* Fix in-cluster config - issue #13
* Update CHANGELOG.md
  • Loading branch information
llomgui authored Apr 27, 2020
1 parent e8dc724 commit 055d9fc
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 91 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
## 1.0.0 (Unreleased)
## 1.0.0 (April 27, 2020)

FEATURES:

* **New Resource:** `openshift_deployment_config`
* **New Resource:** `openshift_image_stream`
* **New Resource:** `openshift_project`
* **New Resource:** `openshift_route`
* **New Resource:** `openshift_netnamespace`
* **New Resource:** `openshift_build_config`
* **New Resource:** `openshift_secret`
173 changes: 86 additions & 87 deletions openshift/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
"fmt"
"log"
"net/http"
"os"

"github.com/hashicorp/terraform-plugin-sdk/helper/logging"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"github.com/mitchellh/go-homedir"
apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
Expand Down Expand Up @@ -156,56 +156,109 @@ func Provider() terraform.ResourceProvider {
}

func providerConfigure(d *schema.ResourceData, terraformVersion string) (interface{}, error) {
var cfg *restclient.Config
var err error
if d.Get("load_config_file").(bool) {
// Config file loading
cfg, err = tryLoadingConfigFile(d)
}

cfg, err := initializeConfiguration(d)
if err != nil {
return nil, err
}

if cfg == nil {
// Attempt to load in-cluster config
cfg, err = restclient.InClusterConfig()
if err != nil {
// Fallback to standard config if we are not running inside a cluster
if err == restclient.ErrNotInCluster {
cfg = &restclient.Config{}
} else {
return nil, fmt.Errorf("Failed to configure: %s", err)
}
}
return nil, fmt.Errorf("Failed to initialize config")
}

// Overriding with static configuration
cfg.UserAgent = fmt.Sprintf("HashiCorp/1.0 Terraform/%s", terraformVersion)

if v, ok := d.GetOk("host"); ok {
cfg.Host = v.(string)
if logging.IsDebugOrHigher() {
log.Printf("[DEBUG] Enabling HTTP requests/responses tracing")
cfg.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return logging.NewTransport("Kubernetes", rt)
}
}
if v, ok := d.GetOk("username"); ok {
cfg.Username = v.(string)

if err != nil {
return nil, fmt.Errorf("Failed to configure: %s", err)
}
if v, ok := d.GetOk("password"); ok {
cfg.Password = v.(string)

return cfg, nil
}

func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) {
overrides := &clientcmd.ConfigOverrides{}
loader := &clientcmd.ClientConfigLoadingRules{}

if d.Get("load_config_file").(bool) {
log.Printf("[DEBUG] Trying to load configuration from file")
if configPath, ok := d.GetOk("config_path"); ok && configPath.(string) != "" {
path, err := homedir.Expand(configPath.(string))
if err != nil {
return nil, err
}
log.Printf("[DEBUG] Configuration file is: %s", path)
loader.ExplicitPath = path

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 = "; overridden 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 overridden context: %#v", overrides.Context)
}
log.Printf("[INFO] Successfully loaded config file (%s%s)", path, ctxSuffix)
}
}

// Overriding with static configuration
if v, ok := d.GetOk("insecure"); ok {
cfg.Insecure = v.(bool)
overrides.ClusterInfo.InsecureSkipTLSVerify = v.(bool)
}
if v, ok := d.GetOk("cluster_ca_certificate"); ok {
cfg.CAData = bytes.NewBufferString(v.(string)).Bytes()
overrides.ClusterInfo.CertificateAuthorityData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := d.GetOk("client_certificate"); ok {
cfg.CertData = bytes.NewBufferString(v.(string)).Bytes()
overrides.AuthInfo.ClientCertificateData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := d.GetOk("host"); ok {
// Server has to be the complete address of the kubernetes cluster (scheme://hostname:port), not just the hostname,
// because `overrides` are processed too late to be taken into account by `defaultServerUrlFor()`.
// This basically replicates what defaultServerUrlFor() does with config but for overrides,
// see https://github.com/kubernetes/client-go/blob/v12.0.0/rest/url_utils.go#L85-L87
hasCA := len(overrides.ClusterInfo.CertificateAuthorityData) != 0
hasCert := len(overrides.AuthInfo.ClientCertificateData) != 0
defaultTLS := hasCA || hasCert || overrides.ClusterInfo.InsecureSkipTLSVerify
host, _, err := restclient.DefaultServerURL(v.(string), "", apimachineryschema.GroupVersion{}, defaultTLS)
if err != nil {
return nil, fmt.Errorf("Failed to parse host: %s", err)
}

overrides.ClusterInfo.Server = host.String()
}
if v, ok := d.GetOk("username"); ok {
overrides.AuthInfo.Username = v.(string)
}
if v, ok := d.GetOk("password"); ok {
overrides.AuthInfo.Password = v.(string)
}
if v, ok := d.GetOk("client_key"); ok {
cfg.KeyData = bytes.NewBufferString(v.(string)).Bytes()
overrides.AuthInfo.ClientKeyData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := d.GetOk("token"); ok {
cfg.BearerToken = v.(string)
overrides.AuthInfo.Token = v.(string)
}

if v, ok := d.GetOk("exec"); ok {
Expand All @@ -220,69 +273,15 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa
} else {
return nil, fmt.Errorf("Failed to parse exec")
}
cfg.ExecProvider = exec
}

if logging.IsDebugOrHigher() {
log.Printf("[DEBUG] Enabling HTTP requests/responses tracing")
cfg.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return logging.NewTransport("Kubernetes", rt)
}
}

if err != nil {
return nil, fmt.Errorf("Failed to configure: %s", err)
}

return cfg, 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 = "; overridden 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)
overrides.AuthInfo.Exec = exec
}

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)
return nil, fmt.Errorf("Failed to initialize config: %s", err)
}

log.Printf("[INFO] Successfully loaded config file (%s%s)", path, ctxSuffix)
log.Printf("[INFO] Successfully initialized config")
return cfg, nil
}
13 changes: 13 additions & 0 deletions website/docs/guides/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,21 @@ provider block completely empty.
provider "openshift" {}
```

If running in-cluster with an appropriate service account token available, you
just need to disable config file loading:

```hcl
provider "kubernetes" {
load_config_file = "false"
}
```

If you wish to configure the provider statically you can do so by providing TLS certificates:

```hcl
provider "openshift" {
load_config_file = "false"
host = "https://104.196.242.174"
client_certificate = file("~/.kube/client-cert.pem")
Expand All @@ -73,6 +84,8 @@ or by providing username and password (HTTP Basic Authorization):

```hcl
provider "openshift" {
load_config_file = "false"
host = "https://104.196.242.174"
username = "ClusterMaster"
Expand Down
25 changes: 22 additions & 3 deletions website/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,29 @@ oc config use-context default-system

Read [more about `oc` in the official docs](https://docs.openshift.com/container-platform/3.11/cli_reference/index.html).

### In-cluster service account token

If no other configuration is specified, and when it detects it is running in a kubernetes pod,
the provider will try to use the service account token from the `/var/run/secrets/kubernetes.io/serviceaccount/token` path.
Detection of in-cluster execution is based on the sole availability both of the `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment variables,
with non empty values.

```hcl
provider "kubernetes" {
load_config_file = "false"
}
```

If you have any other static configuration setting specifiedin a config file or static configuration, in-cluster service account token will not be tried.

### Statically defined credentials

The other way is **statically** define TLS certificate credentials:
An other way is **statically** define TLS certificate credentials:

```hcl
provider "openshift" {
load_config_file = "false"
host = "https://104.196.242.174"
client_certificate = "${file("~/.kube/client-cert.pem")}"
Expand All @@ -73,6 +90,8 @@ or username and password (HTTP Basic Authorization):

```hcl
provider "openshift" {
load_config_file = "false"
host = "https://104.196.242.174"
username = "username"
Expand All @@ -88,7 +107,7 @@ i.e. any static field will override its counterpart loaded from the config.

The following arguments are supported:

* `host` - (Optional) The hostname (in form of URI) of Kubernetes master. Can be sourced from `KUBE_HOST`. Defaults to `https://localhost`.
* `host` - (Optional) The hostname (in form of URI) of Kubernetes master. Can be sourced from `KUBE_HOST`.
* `username` - (Optional) The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint. Can be sourced from `KUBE_USER`.
* `password` - (Optional) The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint. Can be sourced from `KUBE_PASSWORD`.
* `insecure` - (Optional) Whether server should be accessed without verifying the TLS certificate. Can be sourced from `KUBE_INSECURE`. Defaults to `false`.
Expand All @@ -100,7 +119,7 @@ The following arguments are supported:
* `config_context_auth_info` - (Optional) Authentication info context of the kube config (name of the kubeconfig user, `--user` flag in `kubectl`). Can be sourced from `KUBE_CTX_AUTH_INFO`.
* `config_context_cluster` - (Optional) Cluster context of the kube config (name of the kubeconfig cluster, `--cluster` flag in `kubectl`). Can be sourced from `KUBE_CTX_CLUSTER`.
* `token` - (Optional) Token of your service account. Can be sourced from `KUBE_TOKEN`.
* `load_config_file` - (Optional) By default the local config (~/.kube/config) is loaded when you use this provider. This option at false disable this behaviour. Can be sourced from `KUBE_LOAD_CONFIG_FILE`.
* `load_config_file` - (Optional) By default the local config (~/.kube/config) is loaded when you use this provider. This option at false disables this behaviour which is desired when statically specifying the configuration or relying on in-cluster config. Can be sourced from `KUBE_LOAD_CONFIG_FILE`.
* `exec` - (Optional) Configuration block to use an [exec-based credential plugin] (https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins), e.g. call an external command to receive user credentials.
* `api_version` - (Required) API version to use when decoding the ExecCredentials resource, e.g. `client.authentication.k8s.io/v1beta1`.
* `command` - (Required) Command to execute.
Expand Down

0 comments on commit 055d9fc

Please sign in to comment.