doks: fetch new kubeconfig credentials only when expired (#311)

* Upgrade godo to v1.22.0

* doks: fetch new kubeconfig credentials only when expired
This commit is contained in:
Steven Normore 2019-10-08 11:32:24 -04:00 committed by Andrew Starr-Bochicchio
parent 356fd5c157
commit 0b606c4e68
11 changed files with 127 additions and 38 deletions

View File

@ -2,6 +2,7 @@ package digitalocean
import (
"context"
"encoding/base64"
"fmt"
"log"
"strings"
@ -134,6 +135,11 @@ func kubernetesConfigSchema() *schema.Schema {
Type: schema.TypeString,
Computed: true,
},
"expires_at": {
Type: schema.TypeString,
Computed: true,
},
},
},
}
@ -220,14 +226,28 @@ func digitaloceanKubernetesClusterRead(client *godo.Client, cluster *godo.Kubern
}
}
// fetch the K8s config and update the resource
config, resp, err := client.Kubernetes.GetKubeConfig(context.Background(), cluster.ID)
if err != nil {
if resp != nil && resp.StatusCode == 404 {
return fmt.Errorf("Unable to fetch Kubernetes config: %s", err)
// fetch cluster credentials and update the resource if the credentials are expired.
var creds map[string]interface{}
if d.Get("kube_config") != nil && len(d.Get("kube_config").([]interface{})) > 0 {
creds = d.Get("kube_config").([]interface{})[0].(map[string]interface{})
}
var expiresAt time.Time
if creds["expires_at"] != nil && creds["expires_at"].(string) != "" {
var err error
expiresAt, err = time.Parse(time.RFC3339, creds["expires_at"].(string))
if err != nil {
return fmt.Errorf("Unable to parse Kubernetes credentials expiry: %s", err)
}
}
d.Set("kube_config", flattenKubeConfig(config))
if expiresAt.IsZero() || expiresAt.Before(time.Now()) {
creds, resp, err := client.Kubernetes.GetCredentials(context.Background(), cluster.ID, &godo.KubernetesClusterCredentialsGetRequest{})
if err != nil {
if resp != nil && resp.StatusCode == 404 {
return fmt.Errorf("Unable to fetch Kubernetes credentials: %s", err)
}
}
d.Set("kube_config", flattenCredentials(cluster.Name, creds))
}
return nil
}
@ -328,8 +348,8 @@ type kubernetesConfigCluster struct {
Name string `yaml:"name"`
}
type kubernetesConfigClusterData struct {
ClusterCACertificate string `yaml:"certificate-authority-data"`
Server string `yaml:"server"`
CertificateAuthorityData string `yaml:"certificate-authority-data"`
Server string `yaml:"server"`
}
type kubernetesConfigUser struct {
@ -338,40 +358,60 @@ type kubernetesConfigUser struct {
}
type kubernetesConfigUserData struct {
ClientKeyData string `yaml:"client-key-data"`
ClientCertificate string `yaml:"client-certificate-data"`
Token string `yaml:"token"`
ClientKeyData string `yaml:"client-key-data"`
ClientCertificateData string `yaml:"client-certificate-data"`
Token string `yaml:"token"`
}
func flattenKubeConfig(config *godo.KubernetesClusterConfig) []interface{} {
rawConfig := map[string]interface{}{
"raw_config": string(config.KubeconfigYAML),
func flattenCredentials(name string, creds *godo.KubernetesClusterCredentials) []interface{} {
raw := map[string]interface{}{
"cluster_ca_certificate": base64.StdEncoding.EncodeToString(creds.CertificateAuthorityData),
"host": creds.Server,
"token": creds.Token,
"expires_at": creds.ExpiresAt.Format(time.RFC3339),
}
// parse the yaml into an object
var c kubernetesConfig
err := yaml.Unmarshal(config.KubeconfigYAML, &c)
if creds.ClientKeyData != nil {
raw["client_key"] = string(creds.ClientKeyData)
}
if creds.ClientCertificateData != nil {
raw["client_certificate"] = string(creds.ClientCertificateData)
}
kubeconfigYAML, err := renderKubeconfig(name, creds)
if err != nil {
log.Printf("[DEBUG] error unmarshalling config: %s", err)
log.Printf("[DEBUG] error marshalling config: %s", err)
return nil
}
raw["raw_config"] = string(kubeconfigYAML)
if len(c.Clusters) < 1 {
return []interface{}{rawConfig}
return []interface{}{raw}
}
func renderKubeconfig(name string, creds *godo.KubernetesClusterCredentials) ([]byte, error) {
config := kubernetesConfig{
Clusters: []kubernetesConfigCluster{{
Name: "",
Cluster: kubernetesConfigClusterData{
CertificateAuthorityData: base64.StdEncoding.EncodeToString(creds.CertificateAuthorityData),
Server: creds.Server,
},
}},
Users: []kubernetesConfigUser{{
Name: "",
User: kubernetesConfigUserData{
Token: creds.Token,
},
}},
}
rawConfig["cluster_ca_certificate"] = c.Clusters[0].Cluster.ClusterCACertificate
rawConfig["host"] = c.Clusters[0].Cluster.Server
if len(c.Users) < 1 {
return []interface{}{rawConfig}
if creds.ClientKeyData != nil {
config.Users[0].User.ClientKeyData = base64.StdEncoding.EncodeToString(creds.ClientKeyData)
}
rawConfig["client_key"] = c.Users[0].User.ClientKeyData
rawConfig["client_certificate"] = c.Users[0].User.ClientCertificate
rawConfig["token"] = c.Users[0].User.Token
return []interface{}{rawConfig}
if creds.ClientCertificateData != nil {
config.Users[0].User.ClientCertificateData = base64.StdEncoding.EncodeToString(creds.ClientCertificateData)
}
return yaml.Marshal(config)
}
// we need to filter tags to remove any automatically added to avoid state problems,

View File

@ -53,6 +53,7 @@ func TestAccDigitalOceanKubernetesCluster_Basic(t *testing.T) {
resource.TestCheckResourceAttrSet("digitalocean_kubernetes_cluster.foobar", "kube_config.0.cluster_ca_certificate"),
resource.TestCheckResourceAttrSet("digitalocean_kubernetes_cluster.foobar", "kube_config.0.host"),
resource.TestCheckResourceAttrSet("digitalocean_kubernetes_cluster.foobar", "kube_config.0.token"),
resource.TestCheckResourceAttrSet("digitalocean_kubernetes_cluster.foobar", "kube_config.0.expires_at"),
),
},
},

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/terraform-providers/terraform-provider-digitalocean
require (
contrib.go.opencensus.io/exporter/ocagent v0.6.0 // indirect
github.com/aws/aws-sdk-go v1.22.0
github.com/digitalocean/godo v1.21.0
github.com/digitalocean/godo v1.22.0
github.com/hashicorp/terraform v0.12.8
github.com/terraform-providers/terraform-provider-kubernetes v1.7.0
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4

4
go.sum
View File

@ -107,8 +107,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/digitalocean/godo v1.21.0 h1:oSgoZGague6FDPSRjhCiYd8s66QjkWJn+Jt4R/1kEyo=
github.com/digitalocean/godo v1.21.0/go.mod h1:AAPQ+tiM4st79QHlEBTg8LM7JQNre4SAQCbn56wEyKY=
github.com/digitalocean/godo v1.22.0 h1:bVFBKXW2TlynZ9SqmlM6ZSW6UPEzFckltSIUT5NC8L4=
github.com/digitalocean/godo v1.22.0/go.mod h1:iJnN9rVu6K5LioLxLimlq0uRI+y/eAQjROUmeU/r0hY=
github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=

View File

@ -7,6 +7,7 @@ go:
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- tip
matrix:

View File

@ -1,5 +1,13 @@
# Change Log
## [v1.22.0] - 2019-09-24
- #259 Add Kubernetes GetCredentials method - @snormore
## [v1.21.1] - 2019-09-19
- #257 Upgrade to Go 1.13 - @bentranter
## [v1.21.0] - 2019-09-16
- #255 Add DropletID to Kubernetes Node instance - @snormore

View File

@ -1,6 +1,6 @@
module github.com/digitalocean/godo
go 1.12
go 1.13
require (
github.com/google/go-querystring v1.0.0

View File

@ -1,6 +1,7 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
@ -14,6 +15,8 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

View File

@ -17,7 +17,7 @@ import (
)
const (
libraryVersion = "1.21.0"
libraryVersion = "1.22.0"
defaultBaseURL = "https://api.digitalocean.com/"
userAgent = "godo/" + libraryVersion
mediaType = "application/json"

View File

@ -8,6 +8,7 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
@ -27,6 +28,7 @@ type KubernetesService interface {
GetUser(context.Context, string) (*KubernetesClusterUser, *Response, error)
GetUpgrades(context.Context, string) ([]*KubernetesVersion, *Response, error)
GetKubeConfig(context.Context, string) (*KubernetesClusterConfig, *Response, error)
GetCredentials(context.Context, string, *KubernetesClusterCredentialsGetRequest) (*KubernetesClusterCredentials, *Response, error)
List(context.Context, *ListOptions) ([]*KubernetesCluster, *Response, error)
Update(context.Context, string, *KubernetesClusterUpdateRequest) (*KubernetesCluster, *Response, error)
Upgrade(context.Context, string, *KubernetesClusterUpgradeRequest) (*Response, error)
@ -117,6 +119,11 @@ type KubernetesNodeDeleteRequest struct {
SkipDrain bool `json:"skip_drain,omitempty"`
}
// KubernetesClusterCredentialsGetRequest is a request to get cluster credentials.
type KubernetesClusterCredentialsGetRequest struct {
ExpirySeconds *int `json:"expiry_seconds,omitempty"`
}
// KubernetesCluster represents a Kubernetes cluster.
type KubernetesCluster struct {
ID string `json:"id,omitempty"`
@ -146,6 +153,16 @@ type KubernetesClusterUser struct {
Groups []string `json:"groups,omitempty"`
}
// KubernetesClusterCredentials represents Kubernetes cluster credentials.
type KubernetesClusterCredentials struct {
Server string `json:"server"`
CertificateAuthorityData []byte `json:"certificate_authority_data"`
ClientCertificateData []byte `json:"client_certificate_data"`
ClientKeyData []byte `json:"client_key_data"`
Token string `json:"token"`
ExpiresAt time.Time `json:"expires_at"`
}
// KubernetesMaintenancePolicy is a configuration to set the maintenance window
// of a cluster
type KubernetesMaintenancePolicy struct {
@ -480,6 +497,25 @@ func (svc *KubernetesServiceOp) GetKubeConfig(ctx context.Context, clusterID str
return res, resp, nil
}
// GetCredentials returns a Kubernetes API server credentials for the specified cluster.
func (svc *KubernetesServiceOp) GetCredentials(ctx context.Context, clusterID string, get *KubernetesClusterCredentialsGetRequest) (*KubernetesClusterCredentials, *Response, error) {
path := fmt.Sprintf("%s/%s/credentials", kubernetesClustersPath, clusterID)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
q := req.URL.Query()
if get.ExpirySeconds != nil {
q.Add("expiry_seconds", strconv.Itoa(*get.ExpirySeconds))
}
credentials := new(KubernetesClusterCredentials)
resp, err := svc.client.Do(ctx, req, credentials)
if err != nil {
return nil, nil, err
}
return credentials, resp, nil
}
// Update updates a Kubernetes cluster's properties.
func (svc *KubernetesServiceOp) Update(ctx context.Context, clusterID string, update *KubernetesClusterUpdateRequest) (*KubernetesCluster, *Response, error) {
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)

2
vendor/modules.txt vendored
View File

@ -79,7 +79,7 @@ github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1
github.com/davecgh/go-spew/spew
# github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dgrijalva/jwt-go
# github.com/digitalocean/godo v1.21.0
# github.com/digitalocean/godo v1.22.0
github.com/digitalocean/godo
# github.com/fatih/color v1.7.0
github.com/fatih/color