Skip to content

Commit dd0a7f0

Browse files
authored
Merge pull request #378 from matprig/fix/325
Make kubeconfig attributes available as resource output
2 parents 62b8453 + 463c279 commit dd0a7f0

26 files changed

+11617
-44
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/ybriffa/rfc3339 v0.0.0-20220203155318-1789e3fd6e70
1212
golang.org/x/tools v0.0.0-20201118030313-598b068a9102 // indirect
1313
gopkg.in/ini.v1 v1.57.0
14+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
1415
)
1516

1617
go 1.16

ovh/resource_cloud_project_kube.go

+58-7
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,35 @@ func resourceCloudProjectKube() *schema.Resource {
174174
Computed: true,
175175
Sensitive: true,
176176
},
177+
"kubeconfig_attributes": {
178+
Type: schema.TypeList,
179+
Computed: true,
180+
Sensitive: true,
181+
Description: "The kubeconfig configuration file of the Kubernetes cluster",
182+
Elem: &schema.Resource{
183+
Schema: map[string]*schema.Schema{
184+
"host": {
185+
Type: schema.TypeString,
186+
Computed: true,
187+
},
188+
"cluster_ca_certificate": {
189+
Type: schema.TypeString,
190+
Computed: true,
191+
Sensitive: true,
192+
},
193+
"client_certificate": {
194+
Type: schema.TypeString,
195+
Computed: true,
196+
Sensitive: true,
197+
},
198+
"client_key": {
199+
Type: schema.TypeString,
200+
Computed: true,
201+
Sensitive: true,
202+
},
203+
},
204+
},
205+
},
177206
},
178207
}
179208
}
@@ -190,11 +219,9 @@ func resourceCloudProjectKubeImportState(d *schema.ResourceData, meta interface{
190219
d.Set("service_name", serviceName)
191220

192221
// add kubeconfig in state
193-
kubeConfig, err := getKubeconfig(meta.(*Config), serviceName, d.Id())
194-
if err != nil {
222+
if err := setKubeconfig(d, meta); err != nil {
195223
return nil, err
196224
}
197-
d.Set("kubeconfig", kubeConfig)
198225

199226
results := make([]*schema.ResourceData, 1)
200227
results[0] = d
@@ -254,12 +281,11 @@ func resourceCloudProjectKubeRead(d *schema.ResourceData, meta interface{}) erro
254281
}
255282
}
256283

257-
if d.IsNewResource() {
258-
kubeConfig, err := getKubeconfig(config, serviceName, res.Id)
259-
if err != nil {
284+
if d.IsNewResource() || d.Get("kubeconfig") == "" || len(d.Get("kubeconfig_attributes").([]interface{})) == 0 {
285+
// add kubeconfig in state
286+
if err := setKubeconfig(d, meta); err != nil {
260287
return err
261288
}
262-
d.Set("kubeconfig", kubeConfig)
263289
}
264290

265291
log.Printf("[DEBUG] Read kube %+v", res)
@@ -479,3 +505,28 @@ func waitForCloudProjectKubeDeleted(d *schema.ResourceData, client *ovh.Client,
479505
_, err := stateConf.WaitForState()
480506
return err
481507
}
508+
509+
func setKubeconfig(d *schema.ResourceData, meta interface{}) error {
510+
serviceName := d.Get("service_name").(string)
511+
kubeConfig, err := getKubeconfig(meta.(*Config), serviceName, d.Id())
512+
if err != nil {
513+
return err
514+
}
515+
516+
if len(kubeConfig.Clusters) == 0 || len(kubeConfig.Users) == 0 {
517+
return fmt.Errorf("kubeconfig is invalid")
518+
}
519+
520+
// raw kubeconfig
521+
d.Set("kubeconfig", kubeConfig.Raw)
522+
523+
// kubeconfig attributes
524+
kubeconf := map[string]interface{}{}
525+
kubeconf["host"] = kubeConfig.Clusters[0].Cluster.Server
526+
kubeconf["cluster_ca_certificate"] = kubeConfig.Clusters[0].Cluster.CertificateAuthorityData
527+
kubeconf["client_certificate"] = kubeConfig.Users[0].User.ClientCertificateData
528+
kubeconf["client_key"] = kubeConfig.Users[0].User.ClientKeyData
529+
_ = d.Set("kubeconfig_attributes", []map[string]interface{}{kubeconf})
530+
531+
return nil
532+
}
+64-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,74 @@
11
package ovh
22

3-
import "fmt"
3+
import (
4+
"fmt"
45

5-
// getKubeconfig call the kubeconfig endpoint to retreive the kube config file
6-
func getKubeconfig(config *Config, serviceName string, kubeID string) (*string, error) {
6+
"gopkg.in/yaml.v3"
7+
)
8+
9+
// KubectlConfig is a struct to store the kubeconfig file
10+
// Same as https://github.com/kubernetes/kops/blob/2e84499741471ba67582aa0ba6fa3f2e3bdbe3e8/pkg/kubeconfig/config.go#L19 but with yaml format
11+
type KubectlConfig struct {
12+
Kind string `json:"kind" yaml:"kind"`
13+
ApiVersion string `json:"apiVersion" yaml:"apiVersion"`
14+
CurrentContext string `json:"current-context" yaml:"current-context"`
15+
Clusters []*KubectlClusterWithName `json:"clusters" yaml:"clusters"`
16+
Contexts []*KubectlContextWithName `json:"contexts" yaml:"contexts"`
17+
Users []*KubectlUserWithName `json:"users" yaml:"users"`
18+
Raw *string `json:"-" yaml:"-"`
19+
}
20+
21+
type KubectlClusterWithName struct {
22+
Name string `json:"name" yaml:"name"`
23+
Cluster KubectlCluster `json:"cluster" yaml:"cluster"`
24+
}
25+
26+
type KubectlCluster struct {
27+
Server string `json:"server,omitempty" yaml:"server,omitempty"`
28+
CertificateAuthorityData string `json:"certificate-authority-data,omitempty" yaml:"certificate-authority-data,omitempty"`
29+
}
30+
31+
type KubectlContextWithName struct {
32+
Name string `json:"name" yaml:"name"`
33+
Context KubectlContext `json:"context" yaml:"context"`
34+
}
35+
36+
type KubectlContext struct {
37+
Cluster string `json:"cluster" yaml:"cluster"`
38+
User string `json:"user" yaml:"user"`
39+
}
40+
41+
type KubectlUserWithName struct {
42+
Name string `json:"name" yaml:"name"`
43+
User KubectlUser `json:"user" yaml:"user"`
44+
}
45+
46+
type KubectlUser struct {
47+
ClientCertificateData string `json:"client-certificate-data,omitempty" yaml:"client-certificate-data,omitempty"`
48+
ClientKeyData string `json:"client-key-data,omitempty" yaml:"client-key-data,omitempty"`
49+
Password string `json:"password,omitempty" yaml:"password,omitempty"`
50+
Username string `json:"username,omitempty" yaml:"username,omitempty"`
51+
Token string `json:"token,omitempty" yaml:"token,omitempty"`
52+
}
53+
54+
// getKubeconfig call the kubeconfig endpoint to retrieve the kube config file
55+
func getKubeconfig(config *Config, serviceName string, kubeID string) (*KubectlConfig, error) {
756
kubeconfigRaw := CloudProjectKubeKubeConfigResponse{}
857
endpoint := fmt.Sprintf("/cloud/project/%s/kube/%s/kubeconfig", serviceName, kubeID)
958
err := config.OVHClient.Post(endpoint, nil, &kubeconfigRaw)
1059
if err != nil {
1160
return nil, err
1261
}
13-
return &kubeconfigRaw.Content, nil
62+
63+
return parseKubeconfig(&kubeconfigRaw)
64+
}
65+
66+
func parseKubeconfig(kubeconfigRaw *CloudProjectKubeKubeConfigResponse) (*KubectlConfig, error) {
67+
var kubeconfig KubectlConfig
68+
if err := yaml.Unmarshal([]byte(kubeconfigRaw.Content), &kubeconfig); err != nil {
69+
return nil, err
70+
}
71+
72+
kubeconfig.Raw = &kubeconfigRaw.Content
73+
return &kubeconfig, nil
1474
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package ovh
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func Test_parseKubeconfig(t *testing.T) {
9+
type args struct {
10+
kubeconfigRaw *CloudProjectKubeKubeConfigResponse
11+
}
12+
13+
expectedKubeconfigRaw := `apiVersion: v1
14+
clusters:
15+
- cluster:
16+
certificate-authority-data: Zm9vCg==
17+
server: https://foo.bar
18+
name: foo
19+
contexts:
20+
- context:
21+
cluster: foo
22+
user: kubernetes-admin-foo
23+
name: kubernetes-admin@foo
24+
current-context: kubernetes-admin@foo
25+
kind: Config
26+
preferences: {}
27+
users:
28+
- name: kubernetes-admin-foo
29+
user:
30+
client-certificate-data: Zm9vCg==
31+
client-key-data: Zm9vCg==
32+
33+
`
34+
35+
tests := []struct {
36+
name string
37+
args args
38+
want *KubectlConfig
39+
wantErr bool
40+
}{
41+
{
42+
name: "expected kubeconfig content",
43+
args: args{
44+
kubeconfigRaw: &CloudProjectKubeKubeConfigResponse{
45+
Content: expectedKubeconfigRaw,
46+
},
47+
},
48+
want: &KubectlConfig{
49+
Kind: "Config",
50+
ApiVersion: "v1",
51+
CurrentContext: "kubernetes-admin@foo",
52+
Clusters: []*KubectlClusterWithName{
53+
{
54+
Name: "foo",
55+
Cluster: KubectlCluster{Server: "https://foo.bar", CertificateAuthorityData: "Zm9vCg=="},
56+
},
57+
},
58+
Contexts: []*KubectlContextWithName{
59+
{
60+
Name: "kubernetes-admin@foo",
61+
Context: KubectlContext{Cluster: "foo", User: "kubernetes-admin-foo"},
62+
},
63+
},
64+
Users: []*KubectlUserWithName{
65+
{
66+
Name: "kubernetes-admin-foo",
67+
User: KubectlUser{ClientCertificateData: "Zm9vCg==", ClientKeyData: "Zm9vCg=="}},
68+
},
69+
Raw: &expectedKubeconfigRaw,
70+
},
71+
wantErr: false,
72+
},
73+
}
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
got, err := parseKubeconfig(tt.args.kubeconfigRaw)
77+
if (err != nil) != tt.wantErr {
78+
t.Errorf("parseKubeconfig() error = %v, wantErr %v", err, tt.wantErr)
79+
return
80+
}
81+
if !reflect.DeepEqual(got, tt.want) {
82+
t.Errorf("parseKubeconfig() got = %v, want %v", got, tt.want)
83+
}
84+
})
85+
}
86+
}

ovh/resource_cloud_project_kube_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,11 @@ func TestAccCloudProjectKube_basic(t *testing.T) {
328328
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig"),
329329
resource.TestCheckResourceAttr("ovh_cloud_project_kube.cluster", kubeClusterNameKey, name),
330330
resource.TestCheckResourceAttr("ovh_cloud_project_kube.cluster", "version", version),
331+
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig"),
332+
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig_attributes.0.host"),
333+
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig_attributes.0.cluster_ca_certificate"),
334+
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig_attributes.0.client_certificate"),
335+
resource.TestCheckResourceAttrSet("ovh_cloud_project_kube.cluster", "kubeconfig_attributes.0.client_key"),
331336
),
332337
},
333338
},

vendor/gopkg.in/yaml.v3/.travis.yml

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/gopkg.in/yaml.v3/LICENSE

+50
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/gopkg.in/yaml.v3/NOTICE

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)