Merge pull request #1886 from r2d4/localkube-bootstrapper
Add localkube as a bootstrapperpull/1902/head
commit
b0db008b87
|
@ -29,6 +29,8 @@ import (
|
|||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
)
|
||||
|
||||
const Bootstrapper = "bootstrapper"
|
||||
|
||||
type setFn func(string, string) error
|
||||
|
||||
type Setting struct {
|
||||
|
@ -113,6 +115,10 @@ var settings = []Setting{
|
|||
name: config.MachineProfile,
|
||||
set: SetString,
|
||||
},
|
||||
{
|
||||
name: Bootstrapper,
|
||||
set: SetString, //TODO(r2d4): more validation here?
|
||||
},
|
||||
{
|
||||
name: "dashboard",
|
||||
set: SetBool,
|
||||
|
|
|
@ -23,9 +23,9 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config"
|
||||
cmdUtil "k8s.io/minikube/cmd/util"
|
||||
"k8s.io/minikube/pkg/minikube/cluster"
|
||||
"k8s.io/minikube/pkg/minikube/config"
|
||||
"k8s.io/minikube/pkg/minikube/machine"
|
||||
)
|
||||
|
||||
|
@ -45,15 +45,12 @@ var logsCmd = &cobra.Command{
|
|||
os.Exit(1)
|
||||
}
|
||||
defer api.Close()
|
||||
h, err := api.Load(config.GetMachineName())
|
||||
clusterBootstrapper, err := GetClusterBootstrapper(api, viper.GetString(cmdcfg.Bootstrapper))
|
||||
if err != nil {
|
||||
glog.Errorln("Error getting host")
|
||||
glog.Exitf("Error getting cluster bootstrapper: %s", err)
|
||||
}
|
||||
cmdRunner, err := machine.GetCommandRunner(h)
|
||||
if err != nil {
|
||||
glog.Errorln("Error getting command runner interface")
|
||||
}
|
||||
s, err := cluster.GetHostLogs(cmdRunner, follow)
|
||||
|
||||
s, err := clusterBootstrapper.GetClusterLogs(follow)
|
||||
if err != nil {
|
||||
log.Println("Error getting machine logs:", err)
|
||||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
|
|
|
@ -18,18 +18,23 @@ package cmd
|
|||
|
||||
import (
|
||||
goflag "flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/machine/libmachine"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
configCmd "k8s.io/minikube/cmd/minikube/cmd/config"
|
||||
"k8s.io/minikube/cmd/util"
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper/localkube"
|
||||
"k8s.io/minikube/pkg/minikube/config"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/minikube/notify"
|
||||
|
@ -98,7 +103,6 @@ func Execute() {
|
|||
if err := RootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle config values for flags used in external packages (e.g. glog)
|
||||
|
@ -121,6 +125,7 @@ func setFlagsUsingViper() {
|
|||
func init() {
|
||||
RootCmd.PersistentFlags().StringP(config.MachineProfile, "p", constants.DefaultMachineName, `The name of the minikube VM being used.
|
||||
This can be modified to allow for multiple minikube instances to be run independently`)
|
||||
RootCmd.PersistentFlags().StringP(configCmd.Bootstrapper, "b", constants.DefaultClusterBootstrapper, "The name of the cluster bootstrapper that will set up the kubernetes cluster.")
|
||||
RootCmd.AddCommand(configCmd.ConfigCmd)
|
||||
RootCmd.AddCommand(configCmd.AddonsCmd)
|
||||
RootCmd.AddCommand(configCmd.ProfileCmd)
|
||||
|
@ -157,3 +162,20 @@ func setupViper() {
|
|||
viper.SetDefault(config.WantKubectlDownloadMsg, true)
|
||||
setFlagsUsingViper()
|
||||
}
|
||||
|
||||
// GetClusterBootstrapper returns a new bootstrapper for the cluster
|
||||
func GetClusterBootstrapper(api libmachine.API, bootstrapperName string) (bootstrapper.Bootstrapper, error) {
|
||||
var b bootstrapper.Bootstrapper
|
||||
var err error
|
||||
switch bootstrapperName {
|
||||
case bootstrapper.BootstrapperTypeLocalkube:
|
||||
b, err = localkube.NewLocalkubeBootstrapper(api)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting localkube bootstrapper")
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown bootstrapper: %s", bootstrapperName)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
|
|
@ -32,7 +32,9 @@ import (
|
|||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config"
|
||||
cmdUtil "k8s.io/minikube/cmd/util"
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/minikube/cluster"
|
||||
cfg "k8s.io/minikube/pkg/minikube/config"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
|
@ -91,6 +93,11 @@ func runStart(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
defer api.Close()
|
||||
|
||||
exists, err := api.Exists(cfg.GetMachineName())
|
||||
if err != nil {
|
||||
glog.Exitf("checking if machine exists: %s", err)
|
||||
}
|
||||
|
||||
diskSize := viper.GetString(humanReadableDiskSize)
|
||||
diskSizeMB := util.CalculateDiskSizeInMB(diskSize)
|
||||
|
||||
|
@ -170,9 +177,10 @@ func runStart(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
}
|
||||
|
||||
kubernetesConfig := cluster.KubernetesConfig{
|
||||
kubernetesConfig := bootstrapper.KubernetesConfig{
|
||||
KubernetesVersion: selectedKubernetesVersion,
|
||||
NodeIP: ip,
|
||||
NodeName: cfg.GetMachineName(),
|
||||
APIServerName: viper.GetString(apiServerName),
|
||||
DNSDomain: viper.GetString(dnsDomain),
|
||||
FeatureGates: viper.GetString(featureGates),
|
||||
|
@ -181,6 +189,11 @@ func runStart(cmd *cobra.Command, args []string) {
|
|||
ExtraOptions: extraOptions,
|
||||
}
|
||||
|
||||
clusterBootstrapper, err := GetClusterBootstrapper(api, viper.GetString(cmdcfg.Bootstrapper))
|
||||
if err != nil {
|
||||
glog.Exitf("Error getting cluster bootstrapper: %s", err)
|
||||
}
|
||||
|
||||
// Write profile cluster configuration to file
|
||||
clusterConfig := cluster.Config{
|
||||
MachineConfig: config,
|
||||
|
@ -191,30 +204,18 @@ func runStart(cmd *cobra.Command, args []string) {
|
|||
glog.Errorln("Error saving profile cluster configuration: ", err)
|
||||
}
|
||||
|
||||
cmdRunner, err := machine.GetCommandRunner(host)
|
||||
if err != nil {
|
||||
glog.Errorln("Error getting command runner interface")
|
||||
}
|
||||
|
||||
fmt.Println("Moving files into cluster...")
|
||||
if err := cluster.UpdateCluster(cmdRunner, kubernetesConfig); err != nil {
|
||||
if err := clusterBootstrapper.UpdateCluster(kubernetesConfig); err != nil {
|
||||
glog.Errorln("Error updating cluster: ", err)
|
||||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
}
|
||||
|
||||
fmt.Println("Setting up certs...")
|
||||
if err := cluster.SetupCerts(cmdRunner, kubernetesConfig); err != nil {
|
||||
if err := clusterBootstrapper.SetupCerts(kubernetesConfig); err != nil {
|
||||
glog.Errorln("Error configuring authentication: ", err)
|
||||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
}
|
||||
|
||||
fmt.Println("Starting cluster components...")
|
||||
|
||||
if err := cluster.StartCluster(cmdRunner, kubernetesConfig); err != nil {
|
||||
glog.Errorln("Error starting cluster: ", err)
|
||||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
}
|
||||
|
||||
fmt.Println("Connecting to cluster...")
|
||||
kubeHost, err := host.Driver.GetURL()
|
||||
if err != nil {
|
||||
|
@ -243,6 +244,20 @@ func runStart(cmd *cobra.Command, args []string) {
|
|||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
}
|
||||
|
||||
fmt.Println("Starting cluster components...")
|
||||
|
||||
if !exists {
|
||||
if err := clusterBootstrapper.StartCluster(kubernetesConfig); err != nil {
|
||||
glog.Errorln("Error starting cluster: ", err)
|
||||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
}
|
||||
} else {
|
||||
if err := clusterBootstrapper.RestartCluster(kubernetesConfig); err != nil {
|
||||
glog.Errorln("Error restarting cluster: ", err)
|
||||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
}
|
||||
}
|
||||
|
||||
// start 9p server mount
|
||||
if viper.GetBool(createMount) {
|
||||
fmt.Printf("Setting up hostmount on %s...\n", viper.GetString(mountString))
|
||||
|
|
|
@ -24,6 +24,8 @@ import (
|
|||
"github.com/docker/machine/libmachine/state"
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config"
|
||||
cmdUtil "k8s.io/minikube/cmd/util"
|
||||
"k8s.io/minikube/pkg/minikube/cluster"
|
||||
"k8s.io/minikube/pkg/minikube/config"
|
||||
|
@ -62,18 +64,14 @@ var statusCmd = &cobra.Command{
|
|||
cs := state.None.String()
|
||||
ks := state.None.String()
|
||||
if ms == state.Running.String() {
|
||||
h, err := api.Load(config.GetMachineName())
|
||||
clusterBootstrapper, err := GetClusterBootstrapper(api, viper.GetString(cmdcfg.Bootstrapper))
|
||||
if err != nil {
|
||||
glog.Exitln("Error getting host")
|
||||
}
|
||||
cmdRunner, err := machine.GetCommandRunner(h)
|
||||
if err != nil {
|
||||
glog.Errorln("Error getting command runner interface")
|
||||
glog.Errorf("Error getting cluster bootstrapper: %s", err)
|
||||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
}
|
||||
cs, err = cluster.GetLocalkubeStatus(cmdRunner)
|
||||
cs, err = clusterBootstrapper.GetClusterStatus()
|
||||
if err != nil {
|
||||
glog.Errorln("Error localkube status:", err)
|
||||
glog.Errorln("Error cluster status:", err)
|
||||
cmdUtil.MaybeReportErrorAndExit(err)
|
||||
}
|
||||
ip, err := cluster.GetHostDriverIP(api)
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 bootstrapper
|
||||
|
||||
import "k8s.io/minikube/pkg/util"
|
||||
|
||||
// Bootstrapper contains all the methods needed to bootstrap a kubernetes cluster
|
||||
type Bootstrapper interface {
|
||||
StartCluster(KubernetesConfig) error
|
||||
UpdateCluster(KubernetesConfig) error
|
||||
RestartCluster(KubernetesConfig) error
|
||||
GetClusterLogs(follow bool) (string, error)
|
||||
SetupCerts(cfg KubernetesConfig) error
|
||||
GetClusterStatus() (string, error)
|
||||
}
|
||||
|
||||
// KubernetesConfig contains the parameters used to configure the VM Kubernetes.
|
||||
type KubernetesConfig struct {
|
||||
KubernetesVersion string
|
||||
NodeIP string
|
||||
NodeName string
|
||||
APIServerName string
|
||||
DNSDomain string
|
||||
ContainerRuntime string
|
||||
NetworkPlugin string
|
||||
FeatureGates string
|
||||
ExtraOptions util.ExtraOptionSlice
|
||||
}
|
||||
|
||||
const (
|
||||
BootstrapperTypeLocalkube = "localkube"
|
||||
)
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 bootstrapper
|
||||
|
||||
import (
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/tools/clientcmd/api/latest"
|
||||
"k8s.io/minikube/pkg/minikube/assets"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/util"
|
||||
"k8s.io/minikube/pkg/util/kubeconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
certs = []string{"ca.crt", "ca.key", "apiserver.crt", "apiserver.key"}
|
||||
// This is the internalIP , the API server and other components communicate on.
|
||||
internalIP = net.ParseIP(util.DefaultServiceClusterIP)
|
||||
)
|
||||
|
||||
// SetupCerts gets the generated credentials required to talk to the APIServer.
|
||||
func SetupCerts(cmd CommandRunner, k8s KubernetesConfig) error {
|
||||
localPath := constants.GetMinipath()
|
||||
glog.Infoln("Setting up certificates for IP: %s", k8s.NodeIP)
|
||||
|
||||
ip := net.ParseIP(k8s.NodeIP)
|
||||
caCert := filepath.Join(localPath, "ca.crt")
|
||||
caKey := filepath.Join(localPath, "ca.key")
|
||||
publicPath := filepath.Join(localPath, "apiserver.crt")
|
||||
privatePath := filepath.Join(localPath, "apiserver.key")
|
||||
if err := generateCerts(caCert, caKey, publicPath, privatePath, ip, k8s.APIServerName, k8s.DNSDomain); err != nil {
|
||||
return errors.Wrap(err, "Error generating certs")
|
||||
}
|
||||
|
||||
copyableFiles := []assets.CopyableFile{}
|
||||
|
||||
for _, cert := range certs {
|
||||
p := filepath.Join(localPath, cert)
|
||||
perms := "0644"
|
||||
if strings.HasSuffix(cert, ".key") {
|
||||
perms = "0600"
|
||||
}
|
||||
certFile, err := assets.NewFileAsset(p, util.DefaultCertPath, cert, perms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copyableFiles = append(copyableFiles, certFile)
|
||||
}
|
||||
|
||||
kubeCfgSetup := &kubeconfig.KubeConfigSetup{
|
||||
ClusterName: k8s.NodeName,
|
||||
ClusterServerAddress: "https://localhost:8443",
|
||||
ClientCertificate: filepath.Join(util.DefaultCertPath, "apiserver.crt"),
|
||||
ClientKey: filepath.Join(util.DefaultCertPath, "apiserver.key"),
|
||||
CertificateAuthority: filepath.Join(util.DefaultCertPath, "ca.crt"),
|
||||
KeepContext: false,
|
||||
}
|
||||
|
||||
kubeCfg := api.NewConfig()
|
||||
kubeconfig.PopulateKubeConfig(kubeCfgSetup, kubeCfg)
|
||||
data, err := runtime.Encode(latest.Codec, kubeCfg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "encoding kubeconfig")
|
||||
}
|
||||
|
||||
kubeCfgFile := assets.NewMemoryAsset(data,
|
||||
util.DefaultLocalkubeDirectory, "kubeconfig", "0644")
|
||||
copyableFiles = append(copyableFiles, kubeCfgFile)
|
||||
|
||||
for _, f := range copyableFiles {
|
||||
if err := cmd.Copy(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateCerts(caCert, caKey, pub, priv string, ip net.IP, name string, dnsDomain string) error {
|
||||
if !(util.CanReadFile(caCert) && util.CanReadFile(caKey)) {
|
||||
if err := util.GenerateCACert(caCert, caKey, name); err != nil {
|
||||
return errors.Wrap(err, "Error generating certificate")
|
||||
}
|
||||
}
|
||||
|
||||
ips := []net.IP{ip, internalIP}
|
||||
if err := util.GenerateSignedCert(pub, priv, ips, util.GetAlternateDNS(dnsDomain), caCert, caKey); err != nil {
|
||||
return errors.Wrap(err, "Error generating signed cert")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 bootstrapper
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/minikube/tests"
|
||||
)
|
||||
|
||||
func TestSetupCerts(t *testing.T) {
|
||||
tempDir := tests.MakeTempDir()
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
f := NewFakeCommandRunner()
|
||||
k8s := KubernetesConfig{
|
||||
APIServerName: constants.APIServerName,
|
||||
DNSDomain: constants.ClusterDNSDomain,
|
||||
}
|
||||
|
||||
var filesToBeTransferred []string
|
||||
for _, cert := range certs {
|
||||
filesToBeTransferred = append(filesToBeTransferred, filepath.Join(constants.GetMinipath(), cert))
|
||||
}
|
||||
|
||||
if err := SetupCerts(f, k8s); err != nil {
|
||||
t.Fatalf("Error starting cluster: %s", err)
|
||||
}
|
||||
for _, cert := range filesToBeTransferred {
|
||||
_, err := f.GetFileToContents(cert)
|
||||
if err != nil {
|
||||
t.Errorf("Cert not generated: %s", cert)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
package localkube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
gflag "flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"text/template"
|
||||
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
)
|
||||
|
||||
|
@ -65,7 +65,7 @@ else
|
|||
fi
|
||||
`
|
||||
|
||||
func GetStartCommand(kubernetesConfig KubernetesConfig) (string, error) {
|
||||
func GetStartCommand(kubernetesConfig bootstrapper.KubernetesConfig) (string, error) {
|
||||
localkubeStartCommand, err := GenLocalkubeStartCmd(kubernetesConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -93,7 +93,7 @@ func GetStartCommand(kubernetesConfig KubernetesConfig) (string, error) {
|
|||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func GetStartCommandNoSystemd(kubernetesConfig KubernetesConfig, localkubeStartCmd string) (string, error) {
|
||||
func GetStartCommandNoSystemd(kubernetesConfig bootstrapper.KubernetesConfig, localkubeStartCmd string) (string, error) {
|
||||
t := template.Must(template.New("startCommand").Parse(startCommandNoSystemdTemplate))
|
||||
buf := bytes.Buffer{}
|
||||
data := struct {
|
||||
|
@ -113,7 +113,7 @@ func GetStartCommandNoSystemd(kubernetesConfig KubernetesConfig, localkubeStartC
|
|||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func GetStartCommandSystemd(kubernetesConfig KubernetesConfig, localkubeStartCmd string) (string, error) {
|
||||
func GetStartCommandSystemd(kubernetesConfig bootstrapper.KubernetesConfig, localkubeStartCmd string) (string, error) {
|
||||
t, err := template.New("localkubeConfig").Parse(localkubeSystemdTmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -131,7 +131,7 @@ func GetStartCommandSystemd(kubernetesConfig KubernetesConfig, localkubeStartCmd
|
|||
constants.LocalkubeServicePath), nil
|
||||
}
|
||||
|
||||
func GenLocalkubeStartCmd(kubernetesConfig KubernetesConfig) (string, error) {
|
||||
func GenLocalkubeStartCmd(kubernetesConfig bootstrapper.KubernetesConfig) (string, error) {
|
||||
flagVals := make([]string, len(constants.LogFlags))
|
||||
for _, logFlag := range constants.LogFlags {
|
||||
if logVal := gflag.Lookup(logFlag); logVal != nil && logVal.Value.String() != logVal.DefValue {
|
||||
|
@ -226,38 +226,3 @@ else
|
|||
fi
|
||||
fi
|
||||
`, constants.LocalkubePIDPath)
|
||||
|
||||
func GetMountCleanupCommand(path string) string {
|
||||
return fmt.Sprintf("sudo umount %s;", path)
|
||||
}
|
||||
|
||||
var mountTemplate = `
|
||||
sudo mkdir -p {{.Path}} || true;
|
||||
sudo mount -t 9p -o trans=tcp,port={{.Port}},dfltuid={{.UID}},dfltgid={{.GID}},version={{.Version}},msize={{.Msize}} {{.IP}} {{.Path}};
|
||||
sudo chmod 775 {{.Path}};`
|
||||
|
||||
func GetMountCommand(ip net.IP, path, port, mountVersion string, uid, gid, msize int) (string, error) {
|
||||
t := template.Must(template.New("mountCommand").Parse(mountTemplate))
|
||||
buf := bytes.Buffer{}
|
||||
data := struct {
|
||||
IP string
|
||||
Path string
|
||||
Port string
|
||||
Version string
|
||||
UID int
|
||||
GID int
|
||||
Msize int
|
||||
}{
|
||||
IP: ip.String(),
|
||||
Path: path,
|
||||
Port: port,
|
||||
Version: mountVersion,
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
Msize: msize,
|
||||
}
|
||||
if err := t.Execute(&buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
package localkube
|
||||
|
||||
import (
|
||||
gflag "flag"
|
||||
|
@ -22,6 +22,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/util"
|
||||
)
|
||||
|
||||
|
@ -31,7 +32,7 @@ func TestGetStartCommandCustomValues(t *testing.T) {
|
|||
"vmodule": "cluster*=5",
|
||||
}
|
||||
flagMapToSetFlags(flagMap)
|
||||
startCommand, err := GetStartCommand(KubernetesConfig{})
|
||||
startCommand, err := GetStartCommand(bootstrapper.KubernetesConfig{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error generating start command: %s", err)
|
||||
}
|
||||
|
@ -46,7 +47,7 @@ func TestGetStartCommandCustomValues(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetStartCommandExtraOptions(t *testing.T) {
|
||||
k := KubernetesConfig{
|
||||
k := bootstrapper.KubernetesConfig{
|
||||
ExtraOptions: util.ExtraOptionSlice{
|
||||
util.ExtraOption{Component: "a", Key: "b", Value: "c"},
|
||||
util.ExtraOption{Component: "d", Key: "e.f", Value: "g"},
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 localkube
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/minikube/pkg/minikube/assets"
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/minikube/config"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/minikube/sshutil"
|
||||
|
||||
"github.com/docker/machine/libmachine"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type LocalkubeBootstrapper struct {
|
||||
cmd bootstrapper.CommandRunner
|
||||
}
|
||||
|
||||
func NewLocalkubeBootstrapper(api libmachine.API) (*LocalkubeBootstrapper, error) {
|
||||
h, err := api.Load(config.GetMachineName())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting api client")
|
||||
}
|
||||
var cmd bootstrapper.CommandRunner
|
||||
// The none driver executes commands directly on the host
|
||||
if h.Driver.DriverName() == constants.DriverNone {
|
||||
cmd = &bootstrapper.ExecRunner{}
|
||||
} else {
|
||||
client, err := sshutil.NewSSHClient(h.Driver)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting ssh client")
|
||||
}
|
||||
cmd = bootstrapper.NewSSHRunner(client)
|
||||
}
|
||||
return &LocalkubeBootstrapper{
|
||||
cmd: cmd,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetClusterLogs If follow is specified, it will tail the logs
|
||||
func (lk *LocalkubeBootstrapper) GetClusterLogs(follow bool) (string, error) {
|
||||
logsCommand, err := GetLogsCommand(follow)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Error getting logs command")
|
||||
}
|
||||
|
||||
logs, err := lk.cmd.CombinedOutput(logsCommand)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "getting cluster logs")
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// GetClusterStatus gets the status of localkube from the host VM.
|
||||
func (lk *LocalkubeBootstrapper) GetClusterStatus() (string, error) {
|
||||
s, err := lk.cmd.CombinedOutput(localkubeStatusCommand)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
if state.Running.String() == s {
|
||||
return state.Running.String(), nil
|
||||
} else if state.Stopped.String() == s {
|
||||
return state.Stopped.String(), nil
|
||||
} else {
|
||||
return "", fmt.Errorf("Error: Unrecognize output from GetLocalkubeStatus: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
// StartCluster starts a k8s cluster on the specified Host.
|
||||
func (lk *LocalkubeBootstrapper) StartCluster(kubernetesConfig bootstrapper.KubernetesConfig) error {
|
||||
startCommand, err := GetStartCommand(kubernetesConfig)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error generating start command: %s", err)
|
||||
}
|
||||
err = lk.cmd.Run(startCommand) //needs to be sudo for none driver
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error running ssh command: %s", startCommand)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lk *LocalkubeBootstrapper) RestartCluster(kubernetesConfig bootstrapper.KubernetesConfig) error {
|
||||
return lk.StartCluster(kubernetesConfig)
|
||||
}
|
||||
|
||||
func (lk *LocalkubeBootstrapper) UpdateCluster(config bootstrapper.KubernetesConfig) error {
|
||||
copyableFiles := []assets.CopyableFile{}
|
||||
var localkubeFile assets.CopyableFile
|
||||
var err error
|
||||
|
||||
//add url/file/bundled localkube to file list
|
||||
if localkubeURIWasSpecified(config) && config.KubernetesVersion != constants.DefaultKubernetesVersion {
|
||||
lCacher := localkubeCacher{config}
|
||||
localkubeFile, err = lCacher.fetchLocalkubeFromURI()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error updating localkube from uri")
|
||||
}
|
||||
} else {
|
||||
localkubeFile = assets.NewBinDataAsset("out/localkube", "/usr/local/bin", "localkube", "0777")
|
||||
}
|
||||
copyableFiles = append(copyableFiles, localkubeFile)
|
||||
|
||||
// add addons to file list
|
||||
// custom addons
|
||||
assets.AddMinikubeAddonsDirToAssets(©ableFiles)
|
||||
// bundled addons
|
||||
for _, addonBundle := range assets.Addons {
|
||||
if isEnabled, err := addonBundle.IsEnabled(); err == nil && isEnabled {
|
||||
for _, addon := range addonBundle.Assets {
|
||||
copyableFiles = append(copyableFiles, addon)
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range copyableFiles {
|
||||
if err := lk.cmd.Copy(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lk *LocalkubeBootstrapper) SetupCerts(k8s bootstrapper.KubernetesConfig) error {
|
||||
return bootstrapper.SetupCerts(lk.cmd, k8s)
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
package localkube
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -27,13 +27,14 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
|
||||
"k8s.io/minikube/pkg/minikube/assets"
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/util"
|
||||
)
|
||||
|
||||
// localkubeCacher is a struct with methods designed for caching localkube
|
||||
type localkubeCacher struct {
|
||||
k8sConf KubernetesConfig
|
||||
k8sConf bootstrapper.KubernetesConfig
|
||||
}
|
||||
|
||||
func (l *localkubeCacher) getLocalkubeCacheFilepath() string {
|
||||
|
@ -41,6 +42,11 @@ func (l *localkubeCacher) getLocalkubeCacheFilepath() string {
|
|||
filepath.Base(url.QueryEscape("localkube-"+l.k8sConf.KubernetesVersion)))
|
||||
}
|
||||
|
||||
func localkubeURIWasSpecified(config bootstrapper.KubernetesConfig) bool {
|
||||
// see if flag is different than default -> it was passed by user
|
||||
return config.KubernetesVersion != constants.DefaultKubernetesVersion
|
||||
}
|
||||
|
||||
func (l *localkubeCacher) isLocalkubeCached() bool {
|
||||
if _, err := os.Stat(l.getLocalkubeCacheFilepath()); os.IsNotExist(err) {
|
||||
return false
|
||||
|
@ -71,7 +77,7 @@ func (l *localkubeCacher) fetchLocalkubeFromURI() (assets.CopyableFile, error) {
|
|||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error parsing --kubernetes-version url")
|
||||
}
|
||||
if urlObj.Scheme == fileScheme {
|
||||
if urlObj.Scheme == constants.FileScheme {
|
||||
return l.genLocalkubeFileFromFile()
|
||||
}
|
||||
return l.genLocalkubeFileFromURL()
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 localkube
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/minikube/tests"
|
||||
)
|
||||
|
||||
func TestStartCluster(t *testing.T) {
|
||||
expectedStartCmd, err := GetStartCommand(bootstrapper.KubernetesConfig{})
|
||||
if err != nil {
|
||||
t.Fatalf("generating start command: %s", err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
description string
|
||||
startCmd string
|
||||
}{
|
||||
{
|
||||
description: "start cluster success",
|
||||
startCmd: expectedStartCmd,
|
||||
},
|
||||
{
|
||||
description: "start cluster failure",
|
||||
startCmd: "something else",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
f.SetCommandToOutput(map[string]string{test.startCmd: "ok"})
|
||||
l := LocalkubeBootstrapper{f}
|
||||
err := l.StartCluster(bootstrapper.KubernetesConfig{})
|
||||
if err != nil && test.startCmd == expectedStartCmd {
|
||||
t.Errorf("Error starting cluster: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCluster(t *testing.T) {
|
||||
defaultCfg := bootstrapper.KubernetesConfig{
|
||||
KubernetesVersion: constants.DefaultKubernetesVersion,
|
||||
}
|
||||
defaultAddons := []string{
|
||||
"deploy/addons/kube-dns/kube-dns-cm.yaml",
|
||||
"deploy/addons/kube-dns/kube-dns-svc.yaml",
|
||||
"deploy/addons/addon-manager.yaml",
|
||||
"deploy/addons/dashboard/dashboard-rc.yaml",
|
||||
"deploy/addons/dashboard/dashboard-svc.yaml",
|
||||
"deploy/addons/storageclass/storageclass.yaml",
|
||||
"deploy/addons/kube-dns/kube-dns-controller.yaml",
|
||||
}
|
||||
cases := []struct {
|
||||
description string
|
||||
k8s bootstrapper.KubernetesConfig
|
||||
expectedFiles []string
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
description: "transfer localkube correct",
|
||||
k8s: defaultCfg,
|
||||
expectedFiles: []string{"out/localkube"},
|
||||
},
|
||||
{
|
||||
description: "addons are transferred",
|
||||
k8s: defaultCfg,
|
||||
expectedFiles: defaultAddons,
|
||||
},
|
||||
{
|
||||
description: "no localkube version",
|
||||
k8s: bootstrapper.KubernetesConfig{},
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
l := LocalkubeBootstrapper{f}
|
||||
err := l.UpdateCluster(test.k8s)
|
||||
if err != nil && !test.shouldErr {
|
||||
t.Errorf("Error updating cluster: %s", err)
|
||||
return
|
||||
}
|
||||
if err == nil && test.shouldErr {
|
||||
t.Error("Didn't get error, but expected to")
|
||||
return
|
||||
}
|
||||
for _, expectedFile := range test.expectedFiles {
|
||||
_, err := f.GetFileToContents(expectedFile)
|
||||
if err != nil {
|
||||
t.Errorf("Expected file %s, but was not present", expectedFile)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLocalkubeStatus(t *testing.T) {
|
||||
cases := []struct {
|
||||
description string
|
||||
statusCmdMap map[string]string
|
||||
expectedStatus string
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
description: "get status running",
|
||||
statusCmdMap: map[string]string{localkubeStatusCommand: "Running"},
|
||||
expectedStatus: "Running",
|
||||
},
|
||||
{
|
||||
description: "get status stopped",
|
||||
statusCmdMap: map[string]string{localkubeStatusCommand: "Stopped"},
|
||||
expectedStatus: "Stopped",
|
||||
},
|
||||
{
|
||||
description: "get status unknown status",
|
||||
statusCmdMap: map[string]string{localkubeStatusCommand: "Recalculating..."},
|
||||
shouldErr: true,
|
||||
},
|
||||
{
|
||||
description: "get status error",
|
||||
statusCmdMap: map[string]string{"a": "b"},
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
f.SetCommandToOutput(test.statusCmdMap)
|
||||
l := LocalkubeBootstrapper{f}
|
||||
actualStatus, err := l.GetClusterStatus()
|
||||
if err != nil && !test.shouldErr {
|
||||
t.Errorf("Error getting localkube status: %s", err)
|
||||
return
|
||||
}
|
||||
if err == nil && test.shouldErr {
|
||||
t.Error("Didn't get error, but expected to")
|
||||
return
|
||||
}
|
||||
if test.expectedStatus != actualStatus {
|
||||
t.Errorf("Expected status: %s, Actual status: %s", test.expectedStatus, actualStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHostLogs(t *testing.T) {
|
||||
logs, err := GetLogsCommand(false)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting logs command: %s", err)
|
||||
}
|
||||
logsf, err := GetLogsCommand(true)
|
||||
if err != nil {
|
||||
t.Fatalf("Error gettings logs -f command: %s", err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
description string
|
||||
logsCmdMap map[string]string
|
||||
follow bool
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
description: "get logs correct",
|
||||
logsCmdMap: map[string]string{logs: "fee"},
|
||||
},
|
||||
{
|
||||
description: "follow logs correct",
|
||||
logsCmdMap: map[string]string{logsf: "fi"},
|
||||
follow: true,
|
||||
},
|
||||
{
|
||||
description: "get logs incorrect",
|
||||
logsCmdMap: map[string]string{"fo": "fum"},
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
f.SetCommandToOutput(test.logsCmdMap)
|
||||
l := LocalkubeBootstrapper{f}
|
||||
_, err := l.GetClusterLogs(test.follow)
|
||||
if err != nil && !test.shouldErr {
|
||||
t.Errorf("Error getting localkube logs: %s", err)
|
||||
return
|
||||
}
|
||||
if err == nil && test.shouldErr {
|
||||
t.Error("Didn't get error, but expected to")
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLocalkubeCached(t *testing.T) {
|
||||
tempDir := tests.MakeTempDir()
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
inputArr := [...]string{
|
||||
"v1.3.3",
|
||||
"1.3.0",
|
||||
"http://test-url.localkube.com/localkube-binary",
|
||||
"file:///test/dir/to/localkube-binary",
|
||||
}
|
||||
|
||||
localkubeCacher := localkubeCacher{
|
||||
k8sConf: bootstrapper.KubernetesConfig{},
|
||||
}
|
||||
|
||||
inner := func(input string) {
|
||||
localkubeCacher.k8sConf = bootstrapper.KubernetesConfig{
|
||||
KubernetesVersion: input,
|
||||
}
|
||||
if localkubeCacher.isLocalkubeCached() {
|
||||
t.Errorf("IsLocalKubeCached returned true even though %s was not cached",
|
||||
localkubeCacher.getLocalkubeCacheFilepath())
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Create(localkubeCacher.getLocalkubeCacheFilepath())
|
||||
if err != nil {
|
||||
t.Errorf("failed to create dummy cache file: %v", err)
|
||||
return
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
if !localkubeCacher.isLocalkubeCached() {
|
||||
t.Errorf("IsLocalKubeCached returned false even though %s was cached",
|
||||
localkubeCacher.getLocalkubeCacheFilepath())
|
||||
}
|
||||
|
||||
}
|
||||
for _, input := range inputArr {
|
||||
inner(input)
|
||||
}
|
||||
}
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||
package cluster
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/machine/drivers/virtualbox"
|
||||
|
@ -38,16 +38,10 @@ import (
|
|||
"github.com/docker/machine/libmachine/state"
|
||||
"github.com/golang/glog"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/tools/clientcmd/api/latest"
|
||||
|
||||
"k8s.io/minikube/pkg/minikube/assets"
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
cfg "k8s.io/minikube/pkg/minikube/config"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/util"
|
||||
"k8s.io/minikube/pkg/util/kubeconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -154,22 +148,6 @@ func GetHostStatus(api libmachine.API) (string, error) {
|
|||
return s.String(), nil
|
||||
}
|
||||
|
||||
// GetLocalkubeStatus gets the status of localkube from the host VM.
|
||||
func GetLocalkubeStatus(cmd bootstrapper.CommandRunner) (string, error) {
|
||||
s, err := cmd.CombinedOutput(localkubeStatusCommand)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
if state.Running.String() == s {
|
||||
return state.Running.String(), nil
|
||||
} else if state.Stopped.String() == s {
|
||||
return state.Stopped.String(), nil
|
||||
} else {
|
||||
return "", fmt.Errorf("Error: Unrecognize output from GetLocalkubeStatus: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
// GetHostDriverIP gets the ip address of the current minikube cluster
|
||||
func GetHostDriverIP(api libmachine.API) (net.IP, error) {
|
||||
host, err := CheckIfApiExistsAndLoad(api)
|
||||
|
@ -188,121 +166,6 @@ func GetHostDriverIP(api libmachine.API) (net.IP, error) {
|
|||
return ip, nil
|
||||
}
|
||||
|
||||
// StartCluster starts a k8s cluster on the specified Host.
|
||||
func StartCluster(cmd bootstrapper.CommandRunner, kubernetesConfig KubernetesConfig) error {
|
||||
startCommand, err := GetStartCommand(kubernetesConfig)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error generating start command: %s", err)
|
||||
}
|
||||
if err := cmd.Run(startCommand); err != nil {
|
||||
return errors.Wrapf(err, "Error running start command: %s", startCommand)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateCluster(cmd bootstrapper.CommandRunner, config KubernetesConfig) error {
|
||||
copyableFiles := []assets.CopyableFile{}
|
||||
var localkubeFile assets.CopyableFile
|
||||
var err error
|
||||
|
||||
//add url/file/bundled localkube to file list
|
||||
if localkubeURIWasSpecified(config) && config.KubernetesVersion != constants.DefaultKubernetesVersion {
|
||||
lCacher := localkubeCacher{config}
|
||||
localkubeFile, err = lCacher.fetchLocalkubeFromURI()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error updating localkube from uri")
|
||||
}
|
||||
} else {
|
||||
localkubeFile = assets.NewBinDataAsset("out/localkube", "/usr/local/bin", "localkube", "0777")
|
||||
}
|
||||
copyableFiles = append(copyableFiles, localkubeFile)
|
||||
|
||||
// add addons to file list
|
||||
// custom addons
|
||||
assets.AddMinikubeAddonsDirToAssets(©ableFiles)
|
||||
// bundled addons
|
||||
for _, addonBundle := range assets.Addons {
|
||||
if isEnabled, err := addonBundle.IsEnabled(); err == nil && isEnabled {
|
||||
for _, addon := range addonBundle.Assets {
|
||||
copyableFiles = append(copyableFiles, addon)
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range copyableFiles {
|
||||
// fmt.Println(f.GetAssetName())
|
||||
if err := cmd.Copy(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func localkubeURIWasSpecified(config KubernetesConfig) bool {
|
||||
// see if flag is different than default -> it was passed by user
|
||||
return config.KubernetesVersion != constants.DefaultKubernetesVersion
|
||||
}
|
||||
|
||||
// SetupCerts gets the generated credentials required to talk to the APIServer.
|
||||
func SetupCerts(cmd bootstrapper.CommandRunner, k8s KubernetesConfig) error {
|
||||
localPath := constants.GetMinipath()
|
||||
ip := net.ParseIP(k8s.NodeIP)
|
||||
glog.Infoln("Setting up certificates for IP: %s", ip)
|
||||
|
||||
caCert := filepath.Join(localPath, "ca.crt")
|
||||
caKey := filepath.Join(localPath, "ca.key")
|
||||
publicPath := filepath.Join(localPath, "apiserver.crt")
|
||||
privatePath := filepath.Join(localPath, "apiserver.key")
|
||||
if err := GenerateCerts(caCert, caKey, publicPath, privatePath, ip, k8s.APIServerName, k8s.DNSDomain); err != nil {
|
||||
return errors.Wrap(err, "Error generating certs")
|
||||
}
|
||||
|
||||
copyableFiles := []assets.CopyableFile{}
|
||||
|
||||
for _, cert := range certs {
|
||||
p := filepath.Join(localPath, cert)
|
||||
perms := "0644"
|
||||
if strings.HasSuffix(cert, ".key") {
|
||||
perms = "0600"
|
||||
}
|
||||
certFile, err := assets.NewFileAsset(p, util.DefaultCertPath, cert, perms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copyableFiles = append(copyableFiles, certFile)
|
||||
}
|
||||
|
||||
kubeCfgSetup := &kubeconfig.KubeConfigSetup{
|
||||
ClusterName: cfg.GetMachineName(),
|
||||
ClusterServerAddress: "https://localhost:8443",
|
||||
ClientCertificate: filepath.Join(util.DefaultCertPath, "apiserver.crt"),
|
||||
ClientKey: filepath.Join(util.DefaultCertPath, "apiserver.key"),
|
||||
CertificateAuthority: filepath.Join(util.DefaultCertPath, "ca.crt"),
|
||||
KeepContext: false,
|
||||
}
|
||||
|
||||
kubeCfg := api.NewConfig()
|
||||
kubeconfig.PopulateKubeConfig(kubeCfgSetup, kubeCfg)
|
||||
data, err := runtime.Encode(latest.Codec, kubeCfg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setup certs: encoding kubeconfig")
|
||||
}
|
||||
|
||||
kubeCfgFile := assets.NewMemoryAsset(data,
|
||||
util.DefaultLocalkubeDirectory, "kubeconfig", "0644")
|
||||
copyableFiles = append(copyableFiles, kubeCfgFile)
|
||||
|
||||
for _, f := range copyableFiles {
|
||||
if err := cmd.Copy(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func engineOptions(config MachineConfig) *engine.Options {
|
||||
o := engine.Options{
|
||||
Env: config.DockerEnv,
|
||||
|
@ -400,20 +263,6 @@ func GetHostDockerEnv(api libmachine.API) (map[string]string, error) {
|
|||
return envMap, nil
|
||||
}
|
||||
|
||||
// GetHostLogs gets the localkube logs of the host VM.
|
||||
// If follow is specified, it will tail the logs
|
||||
func GetHostLogs(cmd bootstrapper.CommandRunner, follow bool) (string, error) {
|
||||
logsCommand, err := GetLogsCommand(follow)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Error getting logs command")
|
||||
}
|
||||
logs, err := cmd.CombinedOutput(logsCommand)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "running logs command")
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// MountHost runs the mount command from the 9p client on the VM to the 9p server on the host
|
||||
func MountHost(api libmachine.API, ip net.IP, path, port, mountVersion string, uid, gid, msize int) error {
|
||||
host, err := CheckIfApiExistsAndLoad(api)
|
||||
|
@ -536,3 +385,38 @@ func EnsureMinikubeRunningOrExit(api libmachine.API, exitStatus int) {
|
|||
os.Exit(exitStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func GetMountCleanupCommand(path string) string {
|
||||
return fmt.Sprintf("sudo umount %s;", path)
|
||||
}
|
||||
|
||||
var mountTemplate = `
|
||||
sudo mkdir -p {{.Path}} || true;
|
||||
sudo mount -t 9p -o trans=tcp,port={{.Port}},dfltuid={{.UID}},dfltgid={{.GID}},version={{.Version}},msize={{.Msize}} {{.IP}} {{.Path}};
|
||||
sudo chmod 775 {{.Path}};`
|
||||
|
||||
func GetMountCommand(ip net.IP, path, port, mountVersion string, uid, gid, msize int) (string, error) {
|
||||
t := template.Must(template.New("mountCommand").Parse(mountTemplate))
|
||||
buf := bytes.Buffer{}
|
||||
data := struct {
|
||||
IP string
|
||||
Path string
|
||||
Port string
|
||||
Version string
|
||||
UID int
|
||||
GID int
|
||||
Msize int
|
||||
}{
|
||||
IP: ip.String(),
|
||||
Path: path,
|
||||
Port: port,
|
||||
Version: mountVersion,
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
Msize: msize,
|
||||
}
|
||||
if err := t.Execute(&buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package cluster
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -26,7 +25,6 @@ import (
|
|||
"github.com/docker/machine/libmachine/host"
|
||||
"github.com/docker/machine/libmachine/provision"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/minikube/config"
|
||||
"k8s.io/minikube/pkg/minikube/constants"
|
||||
"k8s.io/minikube/pkg/minikube/tests"
|
||||
|
@ -81,224 +79,6 @@ func TestCreateHost(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStartCluster(t *testing.T) {
|
||||
expectedStartCmd, err := GetStartCommand(KubernetesConfig{})
|
||||
if err != nil {
|
||||
t.Fatalf("generating start command: %s", err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
description string
|
||||
startCmd string
|
||||
}{
|
||||
{
|
||||
description: "start cluster success",
|
||||
startCmd: expectedStartCmd,
|
||||
},
|
||||
{
|
||||
description: "start cluster failure",
|
||||
startCmd: "something else",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
f.SetCommandToOutput(map[string]string{test.startCmd: "ok"})
|
||||
err := StartCluster(f, KubernetesConfig{})
|
||||
if err != nil && test.startCmd == expectedStartCmd {
|
||||
t.Errorf("Error starting cluster: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCluster(t *testing.T) {
|
||||
defaultCfg := KubernetesConfig{
|
||||
KubernetesVersion: constants.DefaultKubernetesVersion,
|
||||
}
|
||||
defaultAddons := []string{
|
||||
"deploy/addons/kube-dns/kube-dns-cm.yaml",
|
||||
"deploy/addons/kube-dns/kube-dns-svc.yaml",
|
||||
"deploy/addons/addon-manager.yaml",
|
||||
"deploy/addons/dashboard/dashboard-rc.yaml",
|
||||
"deploy/addons/dashboard/dashboard-svc.yaml",
|
||||
"deploy/addons/storageclass/storageclass.yaml",
|
||||
"deploy/addons/kube-dns/kube-dns-controller.yaml",
|
||||
}
|
||||
cases := []struct {
|
||||
description string
|
||||
k8s KubernetesConfig
|
||||
expectedFiles []string
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
description: "transfer localkube correct",
|
||||
k8s: defaultCfg,
|
||||
expectedFiles: []string{"out/localkube"},
|
||||
},
|
||||
{
|
||||
description: "addons are transferred",
|
||||
k8s: defaultCfg,
|
||||
expectedFiles: defaultAddons,
|
||||
},
|
||||
{
|
||||
description: "no localkube version",
|
||||
k8s: KubernetesConfig{},
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
err := UpdateCluster(f, test.k8s)
|
||||
if err != nil && !test.shouldErr {
|
||||
t.Errorf("Error updating cluster: %s", err)
|
||||
return
|
||||
}
|
||||
if err == nil && test.shouldErr {
|
||||
t.Error("Didn't get error, but expected to")
|
||||
return
|
||||
}
|
||||
for _, expectedFile := range test.expectedFiles {
|
||||
_, err := f.GetFileToContents(expectedFile)
|
||||
if err != nil {
|
||||
t.Errorf("Expected file %s, but was not present", expectedFile)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLocalkubeStatus(t *testing.T) {
|
||||
cases := []struct {
|
||||
description string
|
||||
statusCmdMap map[string]string
|
||||
expectedStatus string
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
description: "get status running",
|
||||
statusCmdMap: map[string]string{localkubeStatusCommand: "Running"},
|
||||
expectedStatus: "Running",
|
||||
},
|
||||
{
|
||||
description: "get status stopped",
|
||||
statusCmdMap: map[string]string{localkubeStatusCommand: "Stopped"},
|
||||
expectedStatus: "Stopped",
|
||||
},
|
||||
{
|
||||
description: "get status unknown status",
|
||||
statusCmdMap: map[string]string{localkubeStatusCommand: "Recalculating..."},
|
||||
shouldErr: true,
|
||||
},
|
||||
{
|
||||
description: "get status error",
|
||||
statusCmdMap: map[string]string{"a": "b"},
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
f.SetCommandToOutput(test.statusCmdMap)
|
||||
actualStatus, err := GetLocalkubeStatus(f)
|
||||
if err != nil && !test.shouldErr {
|
||||
t.Errorf("Error getting localkube status: %s", err)
|
||||
return
|
||||
}
|
||||
if err == nil && test.shouldErr {
|
||||
t.Error("Didn't get error, but expected to")
|
||||
return
|
||||
}
|
||||
if test.expectedStatus != actualStatus {
|
||||
t.Errorf("Expected status: %s, Actual status: %s", test.expectedStatus, actualStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHostLogs(t *testing.T) {
|
||||
logs, err := GetLogsCommand(false)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting logs command: %s", err)
|
||||
}
|
||||
logsf, err := GetLogsCommand(true)
|
||||
if err != nil {
|
||||
t.Fatalf("Error gettings logs -f command: %s", err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
description string
|
||||
logsCmdMap map[string]string
|
||||
follow bool
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
description: "get logs correct",
|
||||
logsCmdMap: map[string]string{logs: "fee"},
|
||||
},
|
||||
{
|
||||
description: "follow logs correct",
|
||||
logsCmdMap: map[string]string{logsf: "fi"},
|
||||
follow: true,
|
||||
},
|
||||
{
|
||||
description: "get logs incorrect",
|
||||
logsCmdMap: map[string]string{"fo": "fum"},
|
||||
shouldErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
f.SetCommandToOutput(test.logsCmdMap)
|
||||
_, err := GetHostLogs(f, test.follow)
|
||||
if err != nil && !test.shouldErr {
|
||||
t.Errorf("Error getting localkube logs: %s", err)
|
||||
return
|
||||
}
|
||||
if err == nil && test.shouldErr {
|
||||
t.Error("Didn't get error, but expected to")
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupCerts(t *testing.T) {
|
||||
tempDir := tests.MakeTempDir()
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
f := bootstrapper.NewFakeCommandRunner()
|
||||
k8s := KubernetesConfig{
|
||||
APIServerName: constants.APIServerName,
|
||||
DNSDomain: constants.ClusterDNSDomain,
|
||||
}
|
||||
|
||||
var filesToBeTransferred []string
|
||||
for _, cert := range certs {
|
||||
filesToBeTransferred = append(filesToBeTransferred, filepath.Join(constants.GetMinipath(), cert))
|
||||
}
|
||||
|
||||
if err := SetupCerts(f, k8s); err != nil {
|
||||
t.Fatalf("Error starting cluster: %s", err)
|
||||
}
|
||||
for _, cert := range filesToBeTransferred {
|
||||
_, err := f.GetFileToContents(cert)
|
||||
if err != nil {
|
||||
t.Errorf("Cert not generated: %s", cert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartHostExists(t *testing.T) {
|
||||
api := tests.NewMockAPI()
|
||||
// Create an initial host.
|
||||
|
@ -609,46 +389,3 @@ func TestCreateSSHShell(t *testing.T) {
|
|||
t.Fatalf("Expected ssh session to be run")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLocalkubeCached(t *testing.T) {
|
||||
tempDir := tests.MakeTempDir()
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
inputArr := [...]string{
|
||||
"v1.3.3",
|
||||
"1.3.0",
|
||||
"http://test-url.localkube.com/localkube-binary",
|
||||
"file:///test/dir/to/localkube-binary",
|
||||
}
|
||||
|
||||
localkubeCacher := localkubeCacher{
|
||||
k8sConf: KubernetesConfig{},
|
||||
}
|
||||
|
||||
inner := func(input string) {
|
||||
localkubeCacher.k8sConf = KubernetesConfig{
|
||||
KubernetesVersion: input,
|
||||
}
|
||||
if localkubeCacher.isLocalkubeCached() {
|
||||
t.Errorf("IsLocalKubeCached returned true even though %s was not cached",
|
||||
localkubeCacher.getLocalkubeCacheFilepath())
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Create(localkubeCacher.getLocalkubeCacheFilepath())
|
||||
if err != nil {
|
||||
t.Errorf("failed to create dummy cache file: %v", err)
|
||||
return
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
if !localkubeCacher.isLocalkubeCached() {
|
||||
t.Errorf("IsLocalKubeCached returned false even though %s was cached",
|
||||
localkubeCacher.getLocalkubeCacheFilepath())
|
||||
}
|
||||
|
||||
}
|
||||
for _, input := range inputArr {
|
||||
inner(input)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 (
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/minikube/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
// This is the internalIP , the API server and other components communicate on.
|
||||
internalIP = net.ParseIP(util.DefaultServiceClusterIP)
|
||||
)
|
||||
|
||||
func GenerateCerts(caCert, caKey, pub, priv string, ip net.IP, name string, dnsDomain string) error {
|
||||
if !(util.CanReadFile(caCert) && util.CanReadFile(caKey)) {
|
||||
if err := util.GenerateCACert(caCert, caKey, name); err != nil {
|
||||
return errors.Wrap(err, "Error generating certificate")
|
||||
}
|
||||
}
|
||||
|
||||
ips := []net.IP{ip, internalIP}
|
||||
if err := util.GenerateSignedCert(pub, priv, ips, util.GetAlternateDNS(dnsDomain), caCert, caKey); err != nil {
|
||||
return errors.Wrap(err, "Error generating signed cert")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -16,7 +16,10 @@ limitations under the License.
|
|||
|
||||
package cluster
|
||||
|
||||
import "k8s.io/minikube/pkg/util"
|
||||
import (
|
||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||
"k8s.io/minikube/pkg/util"
|
||||
)
|
||||
|
||||
// MachineConfig contains the parameters used to start a cluster.
|
||||
type MachineConfig struct {
|
||||
|
@ -37,20 +40,8 @@ type MachineConfig struct {
|
|||
DisableDriverMounts bool // Only used by virtualbox and xhyve
|
||||
}
|
||||
|
||||
// KubernetesConfig contains the parameters used to configure the VM Kubernetes.
|
||||
type KubernetesConfig struct {
|
||||
KubernetesVersion string
|
||||
NodeIP string
|
||||
APIServerName string
|
||||
DNSDomain string
|
||||
ContainerRuntime string
|
||||
NetworkPlugin string
|
||||
FeatureGates string
|
||||
ExtraOptions util.ExtraOptionSlice
|
||||
}
|
||||
|
||||
// Config contains machine and k8s config
|
||||
type Config struct {
|
||||
MachineConfig MachineConfig
|
||||
KubernetesConfig KubernetesConfig
|
||||
KubernetesConfig bootstrapper.KubernetesConfig
|
||||
}
|
||||
|
|
|
@ -91,12 +91,13 @@ const (
|
|||
DefaultVMDriver = "virtualbox"
|
||||
DefaultStatusFormat = "minikube: {{.MinikubeStatus}}\n" +
|
||||
"cluster: {{.ClusterStatus}}\n" + "kubectl: {{.KubeconfigStatus}}\n"
|
||||
DefaultAddonListFormat = "- {{.AddonName}}: {{.AddonStatus}}\n"
|
||||
DefaultConfigViewFormat = "- {{.ConfigKey}}: {{.ConfigValue}}\n"
|
||||
GithubMinikubeReleasesURL = "https://storage.googleapis.com/minikube/releases.json"
|
||||
KubernetesVersionGCSURL = "https://storage.googleapis.com/minikube/k8s_releases.json"
|
||||
DefaultWait = 20
|
||||
DefaultInterval = 6
|
||||
DefaultAddonListFormat = "- {{.AddonName}}: {{.AddonStatus}}\n"
|
||||
DefaultConfigViewFormat = "- {{.ConfigKey}}: {{.ConfigValue}}\n"
|
||||
GithubMinikubeReleasesURL = "https://storage.googleapis.com/minikube/releases.json"
|
||||
KubernetesVersionGCSURL = "https://storage.googleapis.com/minikube/k8s_releases.json"
|
||||
DefaultWait = 20
|
||||
DefaultInterval = 6
|
||||
DefaultClusterBootstrapper = "localkube"
|
||||
)
|
||||
|
||||
var DefaultIsoUrl = fmt.Sprintf("https://storage.googleapis.com/%s/minikube-%s.iso", minikubeVersion.GetIsoPath(), minikubeVersion.GetIsoVersion())
|
||||
|
@ -144,3 +145,4 @@ const (
|
|||
|
||||
const IsMinikubeChildProcess = "IS_MINIKUBE_CHILD_PROCESS"
|
||||
const DriverNone = "none"
|
||||
const FileScheme = "file"
|
||||
|
|
Loading…
Reference in New Issue