Skip to content

Commit eccc148

Browse files
committed
start over dynamically download kubectl client
Signed-off-by: Mohamed Chorfa <[email protected]>
1 parent d4b4ef2 commit eccc148

File tree

5 files changed

+170
-2
lines changed

5 files changed

+170
-2
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/spf13/cobra v1.0.0
1212
github.com/stretchr/testify v1.5.1
1313
github.com/xeipuuv/gojsonschema v1.2.0
14+
golang.org/x/mod v0.2.0
1415
gopkg.in/yaml.v2 v2.2.4
1516
)
1617

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
464464
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
465465
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
466466
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
467+
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
467468
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
468469
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
469470
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

pkg/kubernetes/downloader.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package kubernetes
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"time"
13+
14+
"github.com/pkg/errors"
15+
"golang.org/x/mod/semver"
16+
)
17+
18+
type KubectlDownloader interface {
19+
getServerVersion(m *Mixin) (string, error)
20+
installClient(m *Mixin, version string) error
21+
}
22+
23+
// KubectlDownloaderImplementation implements the KubectlDownloader interface
24+
type KubectlDownloaderImplementation struct{}
25+
26+
type KubectlVersion struct {
27+
ClientVersion struct {
28+
Major string `json:"major"`
29+
Minor string `json:"minor"`
30+
GitVersion string `json:"gitVersion"`
31+
GitCommit string `json:"gitCommit"`
32+
GitTreeState string `json:"gitTreeState"`
33+
BuildDate time.Time `json:"buildDate"`
34+
GoVersion string `json:"goVersion"`
35+
Compiler string `json:"compiler"`
36+
Platform string `json:"platform"`
37+
} `json:"clientVersion"`
38+
ServerVersion struct {
39+
Major string `json:"major"`
40+
Minor string `json:"minor"`
41+
GitVersion string `json:"gitVersion"`
42+
GitCommit string `json:"gitCommit"`
43+
GitTreeState string `json:"gitTreeState"`
44+
BuildDate time.Time `json:"buildDate"`
45+
GoVersion string `json:"goVersion"`
46+
Compiler string `json:"compiler"`
47+
Platform string `json:"platform"`
48+
} `json:"serverVersion"`
49+
}
50+
51+
func (m *Mixin) Init() error {
52+
53+
kdi := m.KubectlDownloader
54+
55+
serverVersion, err := kdi.getServerVersion(m)
56+
57+
if err != nil {
58+
return err
59+
}
60+
61+
if semver.Compare(m.KubernetesClientVersion, serverVersion) == -1 && serverVersion != "" {
62+
63+
// Here we are triggering the download
64+
fmt.Fprintf(m.Out, "Kubectl server version (%s) does not match client version (%s); downloading a compatible client.\n",
65+
serverVersion, m.KubernetesClientVersion)
66+
// try to install the new client
67+
err := kdi.installClient(m, serverVersion)
68+
if err != nil {
69+
return errors.Wrap(err, "unable to install a compatible kubectl client")
70+
}
71+
}
72+
73+
return err
74+
}
75+
76+
func (kdi KubectlDownloaderImplementation) installClient(m *Mixin, version string) error {
77+
78+
url := fmt.Sprintf("https://storage.googleapis.com/kubernetes-release/release/%s/bin/linux/amd64/kubectl", version)
79+
80+
// Fetch kubectl from url
81+
req, err := http.NewRequest("GET", url, nil)
82+
if err != nil {
83+
return errors.Wrap(err, "failed to construct GET request for fetching kubectl client binary")
84+
}
85+
res, err := http.DefaultClient.Do(req)
86+
if err != nil {
87+
return errors.Wrapf(err, "failed to download kubectl client binary via url: %s", url)
88+
}
89+
defer res.Body.Close()
90+
91+
// Create a temp dir
92+
tmpDir, err := m.FileSystem.TempDir("", "tmp")
93+
if err != nil {
94+
return errors.Wrap(err, "unable to create a temporary directory for downloading the kubectl client binary")
95+
}
96+
defer os.RemoveAll(tmpDir)
97+
98+
// Create the local binary
99+
kubectlBinPath, err := m.FileSystem.Create(filepath.Join(tmpDir, "kubectlBin"))
100+
if err != nil {
101+
return errors.Wrap(err, "unable to create a local file for the kubectl client binary")
102+
}
103+
104+
// Copy response body to local binary
105+
_, err = io.Copy(kubectlBinPath, res.Body)
106+
if err != nil {
107+
return errors.Wrap(err, "unable to copy the kubectl client binary to the local binary file")
108+
}
109+
110+
// Move the kubectl binary into the appropriate location
111+
binPath := "/usr/local/bin/kubectl"
112+
err = m.FileSystem.Rename(fmt.Sprintf("%s", kubectlBinPath.Name()), binPath)
113+
if err != nil {
114+
return errors.Wrapf(err, "unable to install the kubectl client binary to %q", binPath)
115+
}
116+
return nil
117+
}
118+
119+
func (kdi KubectlDownloaderImplementation) getServerVersion(m *Mixin) (string, error) {
120+
121+
var stderr bytes.Buffer
122+
currentKubectl := KubectlVersion{}
123+
124+
cmd := exec.Command("kubectl", "version", "-o", "json")
125+
cmd.Stderr = &stderr
126+
// Execute the command and the version output
127+
outputBytes, err := cmd.Output()
128+
if err != nil {
129+
return "", errors.Wrapf(err, "unable to determine kubernetes server version: %s", stderr.String())
130+
}
131+
// Rebuild version json object
132+
json.Unmarshal(outputBytes, &currentKubectl)
133+
134+
version := currentKubectl.ServerVersion.GitVersion
135+
136+
return version, nil
137+
}

pkg/kubernetes/helpers.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,38 @@ type TestMixin struct {
1111
TestContext *context.TestContext
1212
}
1313

14+
const MockkubectlClientVersion string = "v1.15.5"
15+
16+
type MockKubectlDownloader struct {
17+
GetServerVersion func(m *Mixin) (string, error)
18+
InstallClient func(m *Mixin, version string) error
19+
}
20+
21+
func (kd MockKubectlDownloader) getServerVersion(m *Mixin) (string, error) {
22+
return kd.GetServerVersion(m)
23+
}
24+
25+
func (kd MockKubectlDownloader) installClient(m *Mixin, version string) error {
26+
return kd.InstallClient(m, version)
27+
}
28+
29+
func NewMockKubectlDownloader() MockKubectlDownloader {
30+
31+
return MockKubectlDownloader{
32+
GetServerVersion: func(m *Mixin) (string, error) {
33+
return MockkubectlClientVersion, nil
34+
},
35+
InstallClient: func(m *Mixin, version string) error {
36+
return nil
37+
},
38+
}
39+
}
40+
1441
func NewTestMixin(t *testing.T) *TestMixin {
1542
c := context.NewTestContext(t)
1643
m := New()
1744
m.Context = c.Context
45+
m.KubectlDownloader = NewMockKubectlDownloader()
1846
return &TestMixin{
1947
Mixin: m,
2048
TestContext: c,

pkg/kubernetes/kubernetes.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//go:generate packr2
2-
32
package kubernetes
43

54
import (
@@ -22,14 +21,16 @@ const (
2221

2322
type Mixin struct {
2423
*context.Context
25-
schemas *packr.Box
24+
schemas *packr.Box
25+
KubectlDownloader
2626
KubernetesClientVersion string
2727
}
2828

2929
func New() *Mixin {
3030
return &Mixin{
3131
Context: context.New(),
3232
schemas: NewSchemaBox(),
33+
KubectlDownloader: KubectlDownloaderImplementation{},
3334
KubernetesClientVersion: defaultKubernetesClientVersion,
3435
}
3536
}

0 commit comments

Comments
 (0)