Skip to content

Commit

Permalink
Merge pull request #378 from matprig/fix/325
Browse files Browse the repository at this point in the history
Make kubeconfig attributes available as resource output
  • Loading branch information
scraly authored Feb 13, 2023
2 parents 62b8453 + 463c279 commit dd0a7f0
Show file tree
Hide file tree
Showing 26 changed files with 11,617 additions and 44 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/ybriffa/rfc3339 v0.0.0-20220203155318-1789e3fd6e70
golang.org/x/tools v0.0.0-20201118030313-598b068a9102 // indirect
gopkg.in/ini.v1 v1.57.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
)

go 1.16
65 changes: 58 additions & 7 deletions ovh/resource_cloud_project_kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,35 @@ func resourceCloudProjectKube() *schema.Resource {
Computed: true,
Sensitive: true,
},
"kubeconfig_attributes": {
Type: schema.TypeList,
Computed: true,
Sensitive: true,
Description: "The kubeconfig configuration file of the Kubernetes cluster",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"host": {
Type: schema.TypeString,
Computed: true,
},
"cluster_ca_certificate": {
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},
"client_certificate": {
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},
"client_key": {
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},
},
},
},
},
}
}
Expand All @@ -190,11 +219,9 @@ func resourceCloudProjectKubeImportState(d *schema.ResourceData, meta interface{
d.Set("service_name", serviceName)

// add kubeconfig in state
kubeConfig, err := getKubeconfig(meta.(*Config), serviceName, d.Id())
if err != nil {
if err := setKubeconfig(d, meta); err != nil {
return nil, err
}
d.Set("kubeconfig", kubeConfig)

results := make([]*schema.ResourceData, 1)
results[0] = d
Expand Down Expand Up @@ -254,12 +281,11 @@ func resourceCloudProjectKubeRead(d *schema.ResourceData, meta interface{}) erro
}
}

if d.IsNewResource() {
kubeConfig, err := getKubeconfig(config, serviceName, res.Id)
if err != nil {
if d.IsNewResource() || d.Get("kubeconfig") == "" || len(d.Get("kubeconfig_attributes").([]interface{})) == 0 {
// add kubeconfig in state
if err := setKubeconfig(d, meta); err != nil {
return err
}
d.Set("kubeconfig", kubeConfig)
}

log.Printf("[DEBUG] Read kube %+v", res)
Expand Down Expand Up @@ -479,3 +505,28 @@ func waitForCloudProjectKubeDeleted(d *schema.ResourceData, client *ovh.Client,
_, err := stateConf.WaitForState()
return err
}

func setKubeconfig(d *schema.ResourceData, meta interface{}) error {
serviceName := d.Get("service_name").(string)
kubeConfig, err := getKubeconfig(meta.(*Config), serviceName, d.Id())
if err != nil {
return err
}

if len(kubeConfig.Clusters) == 0 || len(kubeConfig.Users) == 0 {
return fmt.Errorf("kubeconfig is invalid")
}

// raw kubeconfig
d.Set("kubeconfig", kubeConfig.Raw)

// kubeconfig attributes
kubeconf := map[string]interface{}{}
kubeconf["host"] = kubeConfig.Clusters[0].Cluster.Server
kubeconf["cluster_ca_certificate"] = kubeConfig.Clusters[0].Cluster.CertificateAuthorityData
kubeconf["client_certificate"] = kubeConfig.Users[0].User.ClientCertificateData
kubeconf["client_key"] = kubeConfig.Users[0].User.ClientKeyData
_ = d.Set("kubeconfig_attributes", []map[string]interface{}{kubeconf})

return nil
}
68 changes: 64 additions & 4 deletions ovh/resource_cloud_project_kube_helpers.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,74 @@
package ovh

import "fmt"
import (
"fmt"

// getKubeconfig call the kubeconfig endpoint to retreive the kube config file
func getKubeconfig(config *Config, serviceName string, kubeID string) (*string, error) {
"gopkg.in/yaml.v3"
)

// KubectlConfig is a struct to store the kubeconfig file
// Same as https://github.com/kubernetes/kops/blob/2e84499741471ba67582aa0ba6fa3f2e3bdbe3e8/pkg/kubeconfig/config.go#L19 but with yaml format
type KubectlConfig struct {
Kind string `json:"kind" yaml:"kind"`
ApiVersion string `json:"apiVersion" yaml:"apiVersion"`
CurrentContext string `json:"current-context" yaml:"current-context"`
Clusters []*KubectlClusterWithName `json:"clusters" yaml:"clusters"`
Contexts []*KubectlContextWithName `json:"contexts" yaml:"contexts"`
Users []*KubectlUserWithName `json:"users" yaml:"users"`
Raw *string `json:"-" yaml:"-"`
}

type KubectlClusterWithName struct {
Name string `json:"name" yaml:"name"`
Cluster KubectlCluster `json:"cluster" yaml:"cluster"`
}

type KubectlCluster struct {
Server string `json:"server,omitempty" yaml:"server,omitempty"`
CertificateAuthorityData string `json:"certificate-authority-data,omitempty" yaml:"certificate-authority-data,omitempty"`
}

type KubectlContextWithName struct {
Name string `json:"name" yaml:"name"`
Context KubectlContext `json:"context" yaml:"context"`
}

type KubectlContext struct {
Cluster string `json:"cluster" yaml:"cluster"`
User string `json:"user" yaml:"user"`
}

type KubectlUserWithName struct {
Name string `json:"name" yaml:"name"`
User KubectlUser `json:"user" yaml:"user"`
}

type KubectlUser struct {
ClientCertificateData string `json:"client-certificate-data,omitempty" yaml:"client-certificate-data,omitempty"`
ClientKeyData string `json:"client-key-data,omitempty" yaml:"client-key-data,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Token string `json:"token,omitempty" yaml:"token,omitempty"`
}

// getKubeconfig call the kubeconfig endpoint to retrieve the kube config file
func getKubeconfig(config *Config, serviceName string, kubeID string) (*KubectlConfig, error) {
kubeconfigRaw := CloudProjectKubeKubeConfigResponse{}
endpoint := fmt.Sprintf("/cloud/project/%s/kube/%s/kubeconfig", serviceName, kubeID)
err := config.OVHClient.Post(endpoint, nil, &kubeconfigRaw)
if err != nil {
return nil, err
}
return &kubeconfigRaw.Content, nil

return parseKubeconfig(&kubeconfigRaw)
}

func parseKubeconfig(kubeconfigRaw *CloudProjectKubeKubeConfigResponse) (*KubectlConfig, error) {
var kubeconfig KubectlConfig
if err := yaml.Unmarshal([]byte(kubeconfigRaw.Content), &kubeconfig); err != nil {
return nil, err
}

kubeconfig.Raw = &kubeconfigRaw.Content
return &kubeconfig, nil
}
86 changes: 86 additions & 0 deletions ovh/resource_cloud_project_kube_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ovh

import (
"reflect"
"testing"
)

func Test_parseKubeconfig(t *testing.T) {
type args struct {
kubeconfigRaw *CloudProjectKubeKubeConfigResponse
}

expectedKubeconfigRaw := `apiVersion: v1
clusters:
- cluster:
certificate-authority-data: Zm9vCg==
server: https://foo.bar
name: foo
contexts:
- context:
cluster: foo
user: kubernetes-admin-foo
name: kubernetes-admin@foo
current-context: kubernetes-admin@foo
kind: Config
preferences: {}
users:
- name: kubernetes-admin-foo
user:
client-certificate-data: Zm9vCg==
client-key-data: Zm9vCg==
`

tests := []struct {
name string
args args
want *KubectlConfig
wantErr bool
}{
{
name: "expected kubeconfig content",
args: args{
kubeconfigRaw: &CloudProjectKubeKubeConfigResponse{
Content: expectedKubeconfigRaw,
},
},
want: &KubectlConfig{
Kind: "Config",
ApiVersion: "v1",
CurrentContext: "kubernetes-admin@foo",
Clusters: []*KubectlClusterWithName{
{
Name: "foo",
Cluster: KubectlCluster{Server: "https://foo.bar", CertificateAuthorityData: "Zm9vCg=="},
},
},
Contexts: []*KubectlContextWithName{
{
Name: "kubernetes-admin@foo",
Context: KubectlContext{Cluster: "foo", User: "kubernetes-admin-foo"},
},
},
Users: []*KubectlUserWithName{
{
Name: "kubernetes-admin-foo",
User: KubectlUser{ClientCertificateData: "Zm9vCg==", ClientKeyData: "Zm9vCg=="}},
},
Raw: &expectedKubeconfigRaw,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseKubeconfig(tt.args.kubeconfigRaw)
if (err != nil) != tt.wantErr {
t.Errorf("parseKubeconfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseKubeconfig() got = %v, want %v", got, tt.want)
}
})
}
}
5 changes: 5 additions & 0 deletions ovh/resource_cloud_project_kube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ func TestAccCloudProjectKube_basic(t *testing.T) {
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig"),
resource.TestCheckResourceAttr("ovh_cloud_project_kube.cluster", kubeClusterNameKey, name),
resource.TestCheckResourceAttr("ovh_cloud_project_kube.cluster", "version", version),
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig"),
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig_attributes.0.host"),
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig_attributes.0.cluster_ca_certificate"),
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig_attributes.0.client_certificate"),
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig_attributes.0.client_key"),
),
},
},
Expand Down
16 changes: 16 additions & 0 deletions vendor/gopkg.in/yaml.v3/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions vendor/gopkg.in/yaml.v3/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions vendor/gopkg.in/yaml.v3/NOTICE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit dd0a7f0

Please sign in to comment.