Skip to content

Commit ded5570

Browse files
Sh4d1Quentin Brosse
authored andcommitted
feat: add kubeconfig helpers for k8s (#204)
Signed-off-by: Patrik Cyvoct <[email protected]>
1 parent e473050 commit ded5570

File tree

4 files changed

+218
-1
lines changed

4 files changed

+218
-1
lines changed

api/k8s/v1beta3/kubeconfig.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package k8s
2+
3+
import (
4+
"io/ioutil"
5+
6+
"github.com/scaleway/scaleway-sdk-go/internal/errors"
7+
"github.com/scaleway/scaleway-sdk-go/scw"
8+
"gopkg.in/yaml.v2"
9+
)
10+
11+
// Kubeconfig represents a kubernetes kubeconfig file
12+
type Kubeconfig struct {
13+
raw []byte
14+
APIVersion string `yaml:"apiVersion"`
15+
Kind string `yaml:"kind"`
16+
CurrentContext string `yaml:"current-context"`
17+
Clusters []*KubeconfigClusterWithName `yaml:"clusters"`
18+
Contexts []*KubeconfigContextWithName `yaml:"contexts"`
19+
Users []*KubeconfigUserWithName `yaml:"users"`
20+
}
21+
22+
// KubeconfigUserWithName represents a named cluster in the kubeconfig file
23+
type KubeconfigClusterWithName struct {
24+
Name string `yaml:"name"`
25+
Cluster KubeconfigCluster `yaml:"cluster"`
26+
}
27+
28+
// KubeconfigCluster represents a cluster in the kubeconfig file
29+
type KubeconfigCluster struct {
30+
Server string `yaml:"server,omitempty"`
31+
CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"`
32+
}
33+
34+
// KubeconfigContextWithName represents a named context in the kubeconfig file
35+
type KubeconfigContextWithName struct {
36+
Name string `yaml:"name"`
37+
Context KubeconfigContext `yaml:"context"`
38+
}
39+
40+
// KubeconfigContext represents a context in the kubeconfig file
41+
type KubeconfigContext struct {
42+
Cluster string `yaml:"cluster"`
43+
Namespace string `yaml:"namespace,omitempty"`
44+
User string `yaml:"user"`
45+
}
46+
47+
// KubeconfigUserWithName represents a named user in the kubeconfig file
48+
type KubeconfigUserWithName struct {
49+
Name string `yaml:"name"`
50+
User KubeconfigUser `yaml:"user"`
51+
}
52+
53+
// KubeconfigUser represents a user in the kubeconfig file
54+
type KubeconfigUser struct {
55+
ClientCertificateData []byte `yaml:"client-certificate-data,omitempty"`
56+
ClientKeyData []byte `yaml:"client-key-data,omitempty"`
57+
Password string `yaml:"password,omitempty"`
58+
Username string `yaml:"username,omitempty"`
59+
Token string `yaml:"token,omitempty"`
60+
}
61+
62+
// GetRaw returns the raw bytes of the kubeconfig
63+
func (k *Kubeconfig) GetRaw() []byte {
64+
return k.raw
65+
}
66+
67+
// GetServer returns the server URL of the cluster in the kubeconfig
68+
func (k *Kubeconfig) GetServer() (string, error) {
69+
if len(k.Clusters) != 1 {
70+
return "", errors.New("kubeconfig should have only one cluster")
71+
}
72+
73+
return k.Clusters[0].Cluster.Server, nil
74+
}
75+
76+
// GetCertificateAuthorityData returns the server certificate authority data of the cluster in the kubeconfig
77+
func (k *Kubeconfig) GetCertificateAuthorityData() (string, error) {
78+
if len(k.Clusters) != 1 {
79+
return "", errors.New("kubeconfig should have only one cluster")
80+
}
81+
82+
return k.Clusters[0].Cluster.CertificateAuthorityData, nil
83+
}
84+
85+
// GetToken returns the token for the cluster in the kubeconfig
86+
func (k *Kubeconfig) GetToken() (string, error) {
87+
if len(k.Users) != 1 {
88+
return "", errors.New("kubeconfig should have only one user")
89+
}
90+
91+
return k.Users[0].User.Token, nil
92+
}
93+
94+
// GetClusterKubeConfigRequest is the requst for GetClusterKubeConfig
95+
type GetClusterKubeConfigRequest struct {
96+
Region scw.Region `json:"-"`
97+
98+
ClusterID string `json:"-"`
99+
}
100+
101+
// GetClusterKubeConfig downloads the kubeconfig for the given cluster
102+
func (s *API) GetClusterKubeConfig(req *GetClusterKubeConfigRequest, opts ...scw.RequestOption) (*Kubeconfig, error) {
103+
kubeconfigFile, err := s.getClusterKubeConfig(&getClusterKubeConfigRequest{
104+
Region: req.Region,
105+
ClusterID: req.ClusterID,
106+
})
107+
if err != nil {
108+
return nil, errors.Wrap(err, "error getting cluster kubeconfig")
109+
}
110+
111+
kubeconfigContent, err := ioutil.ReadAll(kubeconfigFile.Content)
112+
if err != nil {
113+
return nil, errors.Wrap(err, "error reading kubeconfig content")
114+
}
115+
116+
var kubeconfig Kubeconfig
117+
err = yaml.Unmarshal(kubeconfigContent, &kubeconfig)
118+
if err != nil {
119+
return nil, errors.Wrap(err, "error unmarshaling kubeconfig")
120+
}
121+
122+
kubeconfig.raw = kubeconfigContent
123+
124+
return &kubeconfig, nil
125+
}

api/k8s/v1beta3/kubeconfig_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package k8s
2+
3+
import (
4+
"testing"
5+
6+
"github.com/scaleway/scaleway-sdk-go/internal/testhelpers"
7+
"github.com/scaleway/scaleway-sdk-go/internal/testhelpers/httprecorder"
8+
"github.com/scaleway/scaleway-sdk-go/scw"
9+
)
10+
11+
func TestAPI_GetClusterKubeConfig(t *testing.T) {
12+
client, r, err := httprecorder.CreateRecordedScwClient("cluster-test")
13+
testhelpers.AssertNoError(t, err)
14+
defer func() {
15+
testhelpers.AssertNoError(t, r.Stop()) // Make sure recorder is stopped once done with it
16+
}()
17+
18+
k8sAPI := NewAPI(client)
19+
20+
kubeconfig, err := k8sAPI.GetClusterKubeConfig(&GetClusterKubeConfigRequest{
21+
Region: scw.RegionFrPar,
22+
ClusterID: "1267e3fd-a51c-49ed-ad12-857092ee3a3c",
23+
})
24+
25+
testhelpers.AssertNoError(t, err)
26+
27+
ca, err := kubeconfig.GetCertificateAuthorityData()
28+
testhelpers.AssertNoError(t, err)
29+
testhelpers.Equals(t, "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1UQXdOakUyTlRBME9Gb1hEVEk1TVRBd05qRTJOVEEwT0Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTEprCnNOazJ6UVE0L3lXZ0ovb3BlZHlVenFzRWxnRS9IdGx1dk5WVE1Ic0tmcU4rUm01N1kwQm81SjVqZ3NhOGlKU3YKMDFrTU5VMHhVQnI2Q2tMQXVPRTByTGhVb1EvTGlqWHB2QkdSNHVYU0w2RG5uSjFQd1pUQmEwZFdWRFd6ZlBsZwpGUEJ5TmdIcFpaV1p4T0tZVHBEM0Q0T3hoMGZpS1U1ZFBWblJBUDB0bmRhNmlrOHV2OHplSUwyOVRCeDVNT3ZECjdDUjdXWlViWGh6RGVrVEN0R3drSnhjNkhWVktoZUNnbXhwZUtpOEowSXh3YlRHdDlMOFpzUENlWWtoUWtLUXAKM1IxN1JGa2FoSXlVYUs3NWRzYy9jdFZEcC92WXByS0JaVW85UEREK1cyS2pMTVFyK213ZktxL0NZOC9pbnpzMwpsR2VnUzA0R2lIdlc3Nm5YdW04Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEZm9uMTlyL25Ta2NuMkFZM3hQcW1GditUUkUKZEF0ajNRcUZ6ZVBWY1QwOHhvOEpnT1NZMzc4emV6VVpBNHVjRWhLU0o5TEhZRWsvYm9CQWd0cDlpUVl5QnJyegozOFplU2owbzFXR1VraHNqbFVGeHd1Q09DSWRuRmFVRk5PK1krU08zTHNlL2xaZ3crL3pzRXh2bTJTV2xxdDFLCnlMRGNtNU1jdGhyQ3liYWFhejhYTXgvZFY0OWN5enIyR1VUTFI0YkQ2T1ppWHZ4S1dLQ2NzY3RqMWQzRGpRYkQKNmRIVnRwTVdPdng3dEJlTFdCajhndUhWdGorSnNEc3RSV29hUmZEN2hhREdTUlVkUjJ6Wk1uYVNZYXBhQ0VuOApCMndLeDVjWWVYNC9SRDRHc05CdUVjRk9EQ2FGNUNyNWgxdDNnTm9UVExOMDlWSHBxUUZrdHdMdTNWdz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", ca)
30+
31+
token, err := kubeconfig.GetToken()
32+
testhelpers.AssertNoError(t, err)
33+
testhelpers.Equals(t, "eaeRgKfatmPsU19P1d9U0VepykX3Ik0mMtRC1eGJFi8oqMCQWdxZyXr9", token)
34+
35+
server, err := kubeconfig.GetServer()
36+
testhelpers.AssertNoError(t, err)
37+
testhelpers.Equals(t, "https://1267e3fd-a51c-49ed-ad12-857092ee3a3c.api.k8s.fr-par.scw.cloud:6443", server)
38+
39+
raw := kubeconfig.GetRaw()
40+
testhelpers.Equals(t, []byte(`apiVersion: v1
41+
clusters:
42+
- name: tfclusterthirstysatoshi
43+
cluster:
44+
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1UQXdOakUyTlRBME9Gb1hEVEk1TVRBd05qRTJOVEEwT0Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTEprCnNOazJ6UVE0L3lXZ0ovb3BlZHlVenFzRWxnRS9IdGx1dk5WVE1Ic0tmcU4rUm01N1kwQm81SjVqZ3NhOGlKU3YKMDFrTU5VMHhVQnI2Q2tMQXVPRTByTGhVb1EvTGlqWHB2QkdSNHVYU0w2RG5uSjFQd1pUQmEwZFdWRFd6ZlBsZwpGUEJ5TmdIcFpaV1p4T0tZVHBEM0Q0T3hoMGZpS1U1ZFBWblJBUDB0bmRhNmlrOHV2OHplSUwyOVRCeDVNT3ZECjdDUjdXWlViWGh6RGVrVEN0R3drSnhjNkhWVktoZUNnbXhwZUtpOEowSXh3YlRHdDlMOFpzUENlWWtoUWtLUXAKM1IxN1JGa2FoSXlVYUs3NWRzYy9jdFZEcC92WXByS0JaVW85UEREK1cyS2pMTVFyK213ZktxL0NZOC9pbnpzMwpsR2VnUzA0R2lIdlc3Nm5YdW04Q0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEZm9uMTlyL25Ta2NuMkFZM3hQcW1GditUUkUKZEF0ajNRcUZ6ZVBWY1QwOHhvOEpnT1NZMzc4emV6VVpBNHVjRWhLU0o5TEhZRWsvYm9CQWd0cDlpUVl5QnJyegozOFplU2owbzFXR1VraHNqbFVGeHd1Q09DSWRuRmFVRk5PK1krU08zTHNlL2xaZ3crL3pzRXh2bTJTV2xxdDFLCnlMRGNtNU1jdGhyQ3liYWFhejhYTXgvZFY0OWN5enIyR1VUTFI0YkQ2T1ppWHZ4S1dLQ2NzY3RqMWQzRGpRYkQKNmRIVnRwTVdPdng3dEJlTFdCajhndUhWdGorSnNEc3RSV29hUmZEN2hhREdTUlVkUjJ6Wk1uYVNZYXBhQ0VuOApCMndLeDVjWWVYNC9SRDRHc05CdUVjRk9EQ2FGNUNyNWgxdDNnTm9UVExOMDlWSHBxUUZrdHdMdTNWdz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
45+
server: https://1267e3fd-a51c-49ed-ad12-857092ee3a3c.api.k8s.fr-par.scw.cloud:6443
46+
contexts:
47+
- name: admin@tfclusterthirstysatoshi
48+
context:
49+
cluster: tfclusterthirstysatoshi
50+
user: tfclusterthirstysatoshi-admin
51+
current-context: admin@tfclusterthirstysatoshi
52+
kind: Config
53+
preferences: {}
54+
users:
55+
- name: tfclusterthirstysatoshi-admin
56+
user:
57+
token: eaeRgKfatmPsU19P1d9U0VepykX3Ik0mMtRC1eGJFi8oqMCQWdxZyXr9`), raw)
58+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
version: 1
3+
interactions:
4+
- request:
5+
body: ""
6+
form: {}
7+
headers:
8+
User-Agent:
9+
- scaleway-sdk-go/v1.0.0-beta.3+dev (go1.13.1; linux; amd64)
10+
url: https://api.scaleway.com/k8s/v1beta3/regions/fr-par/clusters/1267e3fd-a51c-49ed-ad12-857092ee3a3c/kubeconfig
11+
method: GET
12+
response:
13+
body: '{"name":"kubeconfig","content_type":"application/octet-stream","content":"YXBpVmVyc2lvbjogdjEKY2x1c3RlcnM6Ci0gbmFtZTogdGZjbHVzdGVydGhpcnN0eXNhdG9zaGkKICBjbHVzdGVyOgogICAgY2VydGlmaWNhdGUtYXV0aG9yaXR5LWRhdGE6IExTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVU41UkVORFFXSkRaMEYzU1VKQlowbENRVVJCVGtKbmEzRm9hMmxIT1hjd1FrRlJjMFpCUkVGV1RWSk5kMFZSV1VSV1VWRkVSWGR3Y21SWFNtd0tZMjAxYkdSSFZucE5RalJZUkZSRk5VMVVRWGRPYWtVeVRsUkJNRTlHYjFoRVZFazFUVlJCZDA1cVJUSk9WRUV3VDBadmQwWlVSVlJOUWtWSFFURlZSUXBCZUUxTFlUTldhVnBZU25WYVdGSnNZM3BEUTBGVFNYZEVVVmxLUzI5YVNXaDJZMDVCVVVWQ1FsRkJSR2RuUlZCQlJFTkRRVkZ2UTJkblJVSkJURXByQ25OT2F6SjZVVkUwTDNsWFowb3ZiM0JsWkhsVmVuRnpSV3huUlM5SWRHeDFkazVXVkUxSWMwdG1jVTRyVW0wMU4xa3dRbTgxU2pWcVozTmhPR2xLVTNZS01ERnJUVTVWTUhoVlFuSTJRMnRNUVhWUFJUQnlUR2hWYjFFdlRHbHFXSEIyUWtkU05IVllVMHcyUkc1dVNqRlFkMXBVUW1Fd1pGZFdSRmQ2WmxCc1p3cEdVRUo1VG1kSWNGcGFWMXA0VDB0WlZIQkVNMFEwVDNob01HWnBTMVUxWkZCV2JsSkJVREIwYm1SaE5tbHJPSFYyT0hwbFNVd3lPVlJDZURWTlQzWkVDamREVWpkWFdsVmlXR2g2UkdWclZFTjBSM2RyU25oak5raFdWa3RvWlVObmJYaHdaVXRwT0Vvd1NYaDNZbFJIZERsTU9GcHpVRU5sV1d0b1VXdExVWEFLTTFJeE4xSkdhMkZvU1hsVllVczNOV1J6WXk5amRGWkVjQzkyV1hCeVMwSmFWVzg1VUVSRUsxY3lTMnBNVFZGeUsyMTNaa3R4TDBOWk9DOXBibnB6TXdwc1IyVm5VekEwUjJsSWRsYzNObTVZZFcwNFEwRjNSVUZCWVUxcVRVTkZkMFJuV1VSV1VqQlFRVkZJTDBKQlVVUkJaMHRyVFVFNFIwRXhWV1JGZDBWQ0NpOTNVVVpOUVUxQ1FXWTRkMFJSV1VwTGIxcEphSFpqVGtGUlJVeENVVUZFWjJkRlFrRkVabTl1TVRseUwyNVRhMk51TWtGWk0zaFFjVzFHZGl0VVVrVUtaRUYwYWpOUmNVWjZaVkJXWTFRd09IaHZPRXBuVDFOWk16YzRlbVY2VlZwQk5IVmpSV2hMVTBvNVRFaFpSV3N2WW05Q1FXZDBjRGxwVVZsNVFuSnllZ296T0ZwbFUyb3diekZYUjFWcmFITnFiRlZHZUhkMVEwOURTV1J1Um1GVlJrNVBLMWtyVTA4elRITmxMMnhhWjNjckwzcHpSWGgyYlRKVFYyeHhkREZMQ25sTVJHTnROVTFqZEdoeVEzbGlZV0ZoZWpoWVRYZ3ZaRlkwT1dONWVuSXlSMVZVVEZJMFlrUTJUMXBwV0haNFMxZExRMk56WTNScU1XUXpSR3BSWWtRS05tUklWblJ3VFZkUGRuZzNkRUpsVEZkQ2FqaG5kVWhXZEdvclNuTkVjM1JTVjI5aFVtWkVOMmhoUkVkVFVsVmtVako2V2sxdVlWTlpZWEJoUTBWdU9BcENNbmRMZURWaldXVllOQzlTUkRSSGMwNUNkVVZqUms5RVEyRkdOVU55TldneGRETm5UbTlVVkV4T01EbFdTSEJ4VVVacmRIZE1kVE5XZHowS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPQogICAgc2VydmVyOiBodHRwczovLzEyNjdlM2ZkLWE1MWMtNDllZC1hZDEyLTg1NzA5MmVlM2EzYy5hcGkuazhzLmZyLXBhci5zY3cuY2xvdWQ6NjQ0Mwpjb250ZXh0czoKLSBuYW1lOiBhZG1pbkB0ZmNsdXN0ZXJ0aGlyc3R5c2F0b3NoaQogIGNvbnRleHQ6CiAgICBjbHVzdGVyOiB0ZmNsdXN0ZXJ0aGlyc3R5c2F0b3NoaQogICAgdXNlcjogdGZjbHVzdGVydGhpcnN0eXNhdG9zaGktYWRtaW4KY3VycmVudC1jb250ZXh0OiBhZG1pbkB0ZmNsdXN0ZXJ0aGlyc3R5c2F0b3NoaQpraW5kOiBDb25maWcKcHJlZmVyZW5jZXM6IHt9CnVzZXJzOgotIG5hbWU6IHRmY2x1c3RlcnRoaXJzdHlzYXRvc2hpLWFkbWluCiAgdXNlcjoKICAgIHRva2VuOiBlYWVSZ0tmYXRtUHNVMTlQMWQ5VTBWZXB5a1gzSWswbU10UkMxZUdKRmk4b3FNQ1FXZHhaeVhyOQ=="}'
14+
headers:
15+
Content-Length:
16+
- "2596"
17+
Content-Security-Policy:
18+
- default-src 'none'; frame-ancestors 'none'
19+
Content-Type:
20+
- application/json
21+
Date:
22+
- Wed, 09 Oct 2019 13:56:02 GMT
23+
Server:
24+
- scaleway_api
25+
Strict-Transport-Security:
26+
- max-age=63072000
27+
X-Content-Type-Options:
28+
- nosniff
29+
X-Frame-Options:
30+
- DENY
31+
X-Request-Id:
32+
- 991e5d5d-d2b7-4848-9147-d7bd7e4c50cc
33+
status: 200 OK
34+
code: 200
35+
duration: ""

go.sum

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
21
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
32
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
43
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

0 commit comments

Comments
 (0)