/
minikube.go
231 lines (197 loc) · 6.51 KB
/
minikube.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/*
Copyright 2020 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"sync"
"github.com/blang/semver"
"k8s.io/client-go/util/homedir"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
kctx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version"
)
var (
GetClient = getClient
minikubeVersionWithUserFlag = semver.MustParse("1.18.0")
)
// To override during tests
var (
FindMinikubeBinary = minikubeBinary
getClusterInfo = kctx.GetClusterInfo
GetCurrentVersionFunc = getCurrentVersion
findOnce sync.Once
mk = struct {
err error // determines if version and path are valid
version semver.Version
path string
}{}
)
type Client interface {
// IsMinikube returns true if the given kubeContext maps to a minikube cluster
IsMinikube(ctx context.Context, kubeContext string) bool
// MinikubeExec returns the Cmd struct to execute minikube with given arguments
MinikubeExec(ctx context.Context, arg ...string) (*exec.Cmd, error)
}
type clientImpl struct{}
func getClient() Client {
return clientImpl{}
}
func (clientImpl) IsMinikube(ctx context.Context, kubeContext string) bool {
if _, _, err := FindMinikubeBinary(ctx); err != nil {
log.Entry(context.TODO()).Tracef("Minikube cluster not detected: %v", err)
return false
}
// short circuit if context is 'minikube'
if kubeContext == constants.DefaultMinikubeContext {
return true
}
cluster, err := getClusterInfo(kubeContext)
if err != nil {
log.Entry(context.TODO()).Tracef("failed to get cluster info: %v", err)
return false
}
if matchClusterCertPath(cluster.CertificateAuthority) {
log.Entry(context.TODO()).Debugf("Minikube cluster detected: cluster certificate for context %q found inside the minikube directory", kubeContext)
return true
}
if ok, err := matchServerURL(ctx, cluster.Server); err != nil {
log.Entry(context.TODO()).Tracef("failed to match server url: %v", err)
} else if ok {
log.Entry(context.TODO()).Debugf("Minikube cluster detected: server url for context %q matches minikube node ip", kubeContext)
return true
}
log.Entry(context.TODO()).Tracef("Minikube cluster not detected for context %q", kubeContext)
return false
}
func (clientImpl) MinikubeExec(ctx context.Context, arg ...string) (*exec.Cmd, error) {
return minikubeExec(ctx, arg...)
}
func minikubeExec(ctx context.Context, arg ...string) (*exec.Cmd, error) {
b, v, err := FindMinikubeBinary(ctx)
if err != nil && !errors.As(err, &versionErr{}) {
return nil, fmt.Errorf("getting minikube executable: %w", err)
} else if err == nil && supportsUserFlag(v) {
arg = append(arg, "--user=skaffold")
}
return exec.Command(b, arg...), nil
}
func supportsUserFlag(ver semver.Version) bool {
return ver.GE(minikubeVersionWithUserFlag)
}
// Retrieves minikube version
func getCurrentVersion(ctx context.Context) (semver.Version, error) {
cmd := exec.Command("minikube", "version", "--output=json")
out, err := util.RunCmdOut(ctx, cmd)
if err != nil {
return semver.Version{}, err
}
minikubeOutput := map[string]string{}
err = json.Unmarshal(out, &minikubeOutput)
if v, ok := minikubeOutput["minikubeVersion"]; ok {
currentVersion, err := version.ParseVersion(v)
if err != nil {
return semver.Version{}, err
}
return currentVersion, nil
}
return semver.Version{}, err
}
func minikubeBinary(ctx context.Context) (string, semver.Version, error) {
findOnce.Do(func() {
filename, err := exec.LookPath("minikube")
if err != nil {
mk.err = errors.New("unable to lookup minikube executable. Please add it to PATH environment variable")
}
if _, err := os.Stat(filename); os.IsNotExist(err) {
mk.err = fmt.Errorf("unable to find minikube executable. File not found %s", filename)
}
mk.path = filename
if v, err := GetCurrentVersionFunc(ctx); err != nil {
mk.err = versionErr{err: err}
} else {
mk.version = v
}
})
return mk.path, mk.version, mk.err
}
type versionErr struct {
err error
}
func (v versionErr) Error() string {
return v.err.Error()
}
// matchClusterCertPath checks if the cluster certificate for this context is from inside the minikube directory
func matchClusterCertPath(certPath string) bool {
return certPath != "" && util.IsSubPath(minikubePath(), certPath)
}
// matchServerURL checks if the k8s server url is same as any of the minikube nodes IPs
func matchServerURL(ctx context.Context, server string) (bool, error) {
cmd, _ := minikubeExec(ctx, "profile", "list", "-o", "json")
out, err := util.RunCmdOut(ctx, cmd)
if err != nil {
return false, fmt.Errorf("getting minikube profiles: %w", err)
}
var data profileList
if err = json.Unmarshal(out, &data); err != nil {
return false, fmt.Errorf("failed to unmarshal minikube profile list: %w", err)
}
serverURL, err := url.Parse(server)
if err != nil {
log.Entry(context.TODO()).Tracef("invalid server url: %v", err)
}
for _, v := range data.Valid {
for _, n := range v.Config.Nodes {
if err == nil && serverURL.Host == fmt.Sprintf("%s:%d", n.IP, n.Port) {
// TODO: Revisit once https://github.com/kubernetes/minikube/issues/6642 is fixed
return true, nil
}
}
}
return false, nil
}
// minikubePath returns the path to the user's minikube dir
func minikubePath() string {
minikubeHomeEnv := os.Getenv("MINIKUBE_HOME")
if minikubeHomeEnv == "" {
return filepath.Join(homedir.HomeDir(), ".minikube")
}
if filepath.Base(minikubeHomeEnv) == ".minikube" {
return minikubeHomeEnv
}
return filepath.Join(minikubeHomeEnv, ".minikube")
}
type profileList struct {
Valid []profile `json:"valid,omitempty"`
Invalid []profile `json:"invalid,omitempty"`
}
type profile struct {
Config config
}
type config struct {
Name string
Driver string
Nodes []node
}
type node struct {
IP string
Port int32
}