diff --git a/cmd/minikube/cmd/config/addons_list.go b/cmd/minikube/cmd/config/addons_list.go index 18a5ff8016a1..8737fe22248e 100644 --- a/cmd/minikube/cmd/config/addons_list.go +++ b/cmd/minikube/cmd/config/addons_list.go @@ -102,9 +102,9 @@ var printAddonsList = func() { for _, addonName := range addonNames { addonBundle := assets.Addons[addonName] - addonStatus, err := addonBundle.IsEnabled() + addonStatus, err := addonBundle.IsEnabled(pName) if err != nil { - exit.WithError("Error getting addons status", err) + out.WarningT("Unable to get addon status for {{.name}}: {{.error}}", out.V{"name": addonName, "error": err}) } tData = append(tData, []string{addonName, pName, fmt.Sprintf("%s %s", stringFromStatus(addonStatus), iconFromStatus(addonStatus))}) } @@ -114,12 +114,11 @@ var printAddonsList = func() { v, _, err := config.ListProfiles() if err != nil { - glog.Infof("error getting list of porfiles: %v", err) + glog.Errorf("list profiles returned error: %v", err) } if len(v) > 1 { out.T(out.Tip, "To see addons list for other profiles use: `minikube addons -p name list`") } - } var printAddonsJSON = func() { @@ -135,9 +134,10 @@ var printAddonsJSON = func() { for _, addonName := range addonNames { addonBundle := assets.Addons[addonName] - addonStatus, err := addonBundle.IsEnabled() + addonStatus, err := addonBundle.IsEnabled(pName) if err != nil { - exit.WithError("Error getting addons status", err) + glog.Errorf("Unable to get addon status for %s: %v", addonName, err) + continue } addonsMap[addonName] = map[string]interface{}{ diff --git a/cmd/minikube/cmd/config/disable.go b/cmd/minikube/cmd/config/disable.go index c6c538f4b101..e9916a8929bc 100644 --- a/cmd/minikube/cmd/config/disable.go +++ b/cmd/minikube/cmd/config/disable.go @@ -39,7 +39,7 @@ var addonsDisableCmd = &cobra.Command{ if err != nil { exit.WithError("disable failed", err) } - out.SuccessT(`"{{.minikube_addon}}" was successfully disabled`, out.V{"minikube_addon": addon}) + out.T(out.AddonDisable, `"The '{{.minikube_addon}}' addon is disabled`, out.V{"minikube_addon": addon}) }, } diff --git a/cmd/minikube/cmd/config/enable.go b/cmd/minikube/cmd/config/enable.go index a920b9be0d77..8bef60235a63 100644 --- a/cmd/minikube/cmd/config/enable.go +++ b/cmd/minikube/cmd/config/enable.go @@ -33,13 +33,12 @@ var addonsEnableCmd = &cobra.Command{ if len(args) != 1 { exit.UsageT("usage: minikube addons enable ADDON_NAME") } - addon := args[0] err := addons.Set(addon, "true", viper.GetString(config.MachineProfile)) if err != nil { exit.WithError("enable failed", err) } - out.SuccessT("{{.addonName}} was successfully enabled", out.V{"addonName": addon}) + out.T(out.AddonEnable, "The '{{.addonName}}' addon is enabled", out.V{"addonName": addon}) }, } diff --git a/cmd/minikube/cmd/config/open.go b/cmd/minikube/cmd/config/open.go index 458950b91728..a65250810d47 100644 --- a/cmd/minikube/cmd/config/open.go +++ b/cmd/minikube/cmd/config/open.go @@ -24,8 +24,10 @@ import ( "github.com/pkg/browser" "github.com/spf13/cobra" + "github.com/spf13/viper" "k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/cluster" + pkg_config "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/exit" "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/out" @@ -66,7 +68,8 @@ var addonsOpenCmd = &cobra.Command{ } defer api.Close() - if !cluster.IsMinikubeRunning(api) { + profileName := viper.GetString(pkg_config.MachineProfile) + if !cluster.IsHostRunning(api, profileName) { os.Exit(1) } addon, ok := assets.Addons[addonName] // validate addon input @@ -75,7 +78,7 @@ var addonsOpenCmd = &cobra.Command{ To see the list of available addons run: minikube addons list`, out.V{"name": addonName}) } - ok, err = addon.IsEnabled() + ok, err = addon.IsEnabled(profileName) if err != nil { exit.WithError("IsEnabled failed", err) } diff --git a/cmd/minikube/cmd/config/profile.go b/cmd/minikube/cmd/config/profile.go index 03eb016426c3..67203ebdbf69 100644 --- a/cmd/minikube/cmd/config/profile.go +++ b/cmd/minikube/cmd/config/profile.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" pkgConfig "k8s.io/minikube/pkg/minikube/config" + pkg_config "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/exit" "k8s.io/minikube/pkg/minikube/kubeconfig" @@ -78,7 +79,7 @@ var ProfileCmd = &cobra.Command{ } cc, err := pkgConfig.Load(profile) // might err when loading older version of cfg file that doesn't have KeepContext field - if err != nil && !os.IsNotExist(err) { + if err != nil && !pkg_config.IsNotExist(err) { out.ErrT(out.Sad, `Error loading profile config: {{.error}}`, out.V{"error": err}) } if err == nil { diff --git a/cmd/minikube/cmd/config/profile_list.go b/cmd/minikube/cmd/config/profile_list.go index 8df06f2d2d8c..d3b90721a81a 100644 --- a/cmd/minikube/cmd/config/profile_list.go +++ b/cmd/minikube/cmd/config/profile_list.go @@ -141,7 +141,7 @@ var printProfilesJSON = func() { var body = map[string]interface{}{} - if err == nil || os.IsNotExist(err) { + if err == nil || config.IsNotExist(err) { body["valid"] = valid body["invalid"] = invalid jsonString, _ := json.Marshal(body) diff --git a/cmd/minikube/cmd/dashboard.go b/cmd/minikube/cmd/dashboard.go index 8b2e8e201791..f247b365d764 100644 --- a/cmd/minikube/cmd/dashboard.go +++ b/cmd/minikube/cmd/dashboard.go @@ -36,7 +36,6 @@ import ( pkgaddons "k8s.io/minikube/pkg/addons" "k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/cluster" - "k8s.io/minikube/pkg/minikube/config" pkg_config "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/exit" "k8s.io/minikube/pkg/minikube/machine" @@ -61,7 +60,7 @@ var dashboardCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { profileName := viper.GetString(pkg_config.MachineProfile) cc, err := pkg_config.Load(profileName) - if err != nil && !os.IsNotExist(err) { + if err != nil && !pkg_config.IsNotExist(err) { exit.WithError("Error loading profile config", err) } @@ -103,18 +102,18 @@ var dashboardCmd = &cobra.Command{ exit.WithCodeT(exit.NoInput, "kubectl not found in PATH, but is required for the dashboard. Installation guide: https://kubernetes.io/docs/tasks/tools/install-kubectl/") } - if !cluster.IsMinikubeRunning(api) { + if !cluster.IsHostRunning(api, profileName) { os.Exit(1) } // Check dashboard status before enabling it dashboardAddon := assets.Addons["dashboard"] - dashboardStatus, _ := dashboardAddon.IsEnabled() + dashboardStatus, _ := dashboardAddon.IsEnabled(profileName) if !dashboardStatus { // Send status messages to stderr for folks re-using this output. out.ErrT(out.Enabling, "Enabling dashboard ...") // Enable the dashboard add-on - err = pkgaddons.Set("dashboard", "true", viper.GetString(config.MachineProfile)) + err = pkgaddons.Set("dashboard", "true", profileName) if err != nil { exit.WithError("Unable to enable dashboard", err) } diff --git a/cmd/minikube/cmd/delete.go b/cmd/minikube/cmd/delete.go index 5efe7e2ee3bb..73af19bd666c 100644 --- a/cmd/minikube/cmd/delete.go +++ b/cmd/minikube/cmd/delete.go @@ -188,7 +188,7 @@ func deleteProfile(profile *pkg_config.Profile) error { } defer api.Close() cc, err := pkg_config.Load(profile.Name) - if err != nil && !os.IsNotExist(err) { + if err != nil && !pkg_config.IsNotExist(err) { delErr := profileDeletionErr(profile.Name, fmt.Sprintf("error loading profile config: %v", err)) return DeletionError{Err: delErr, Errtype: MissingProfile} } @@ -223,7 +223,7 @@ func deleteProfile(profile *pkg_config.Profile) error { deleteProfileDirectory(profile.Name) if err := pkg_config.DeleteProfile(profile.Name); err != nil { - if os.IsNotExist(err) { + if pkg_config.IsNotExist(err) { delErr := profileDeletionErr(profile.Name, fmt.Sprintf("\"%s\" profile does not exist", profile.Name)) return DeletionError{Err: delErr, Errtype: MissingProfile} } diff --git a/cmd/minikube/cmd/kubectl.go b/cmd/minikube/cmd/kubectl.go index 706a9703e288..0a5eaea6596c 100644 --- a/cmd/minikube/cmd/kubectl.go +++ b/cmd/minikube/cmd/kubectl.go @@ -27,7 +27,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "k8s.io/minikube/pkg/minikube/config" - pkg_config "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/exit" "k8s.io/minikube/pkg/minikube/machine" @@ -51,8 +50,8 @@ minikube kubectl -- get pods --namespace kube-system`, } defer api.Close() - cc, err := pkg_config.Load(viper.GetString(config.MachineProfile)) - if err != nil && !os.IsNotExist(err) { + cc, err := config.Load(viper.GetString(config.MachineProfile)) + if err != nil && !config.IsNotExist(err) { out.ErrLn("Error loading profile config: %v", err) } diff --git a/cmd/minikube/cmd/pause.go b/cmd/minikube/cmd/pause.go index 493c2c0990b2..1e7c68a01dbb 100644 --- a/cmd/minikube/cmd/pause.go +++ b/cmd/minikube/cmd/pause.go @@ -53,7 +53,7 @@ func runPause(cmd *cobra.Command, args []string) { defer api.Close() cc, err := config.Load(cname) - if err != nil && !os.IsNotExist(err) { + if err != nil && !config.IsNotExist(err) { exit.WithError("Error loading profile config", err) } diff --git a/cmd/minikube/cmd/service.go b/cmd/minikube/cmd/service.go index 8d844deb0638..bcb11759d0c0 100644 --- a/cmd/minikube/cmd/service.go +++ b/cmd/minikube/cmd/service.go @@ -25,8 +25,10 @@ import ( "github.com/golang/glog" "github.com/pkg/browser" "github.com/spf13/cobra" + "github.com/spf13/viper" "k8s.io/minikube/pkg/minikube/cluster" + pkg_config "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/exit" "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/out" @@ -71,7 +73,8 @@ var serviceCmd = &cobra.Command{ } defer api.Close() - if !cluster.IsMinikubeRunning(api) { + profileName := viper.GetString(pkg_config.MachineProfile) + if !cluster.IsHostRunning(api, profileName) { os.Exit(1) } diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 4ccd39cf2f27..df4916aaf0c4 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -46,7 +46,7 @@ import ( "github.com/spf13/viper" "golang.org/x/sync/errgroup" cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config" - pkgaddons "k8s.io/minikube/pkg/addons" + "k8s.io/minikube/pkg/addons" "k8s.io/minikube/pkg/minikube/bootstrapper" "k8s.io/minikube/pkg/minikube/bootstrapper/bsutil" "k8s.io/minikube/pkg/minikube/bootstrapper/images" @@ -104,7 +104,6 @@ const ( imageMirrorCountry = "image-mirror-country" mountString = "mount-string" disableDriverMounts = "disable-driver-mounts" - addons = "addons" cacheImages = "cache-images" uuid = "uuid" vpnkitSock = "hyperkit-vpnkit-sock" @@ -172,7 +171,7 @@ func initMinikubeFlags() { startCmd.Flags().String(containerRuntime, "docker", "The container runtime to be used (docker, crio, containerd).") startCmd.Flags().Bool(createMount, false, "This will start the mount daemon and automatically mount files into minikube.") startCmd.Flags().String(mountString, constants.DefaultMountDir+":/minikube-host", "The argument to pass the minikube mount command on start.") - startCmd.Flags().StringArrayVar(&addonList, addons, nil, "Enable addons. see `minikube addons list` for a list of valid addon names.") + startCmd.Flags().StringArrayVar(&addonList, "addons", nil, "Enable addons. see `minikube addons list` for a list of valid addon names.") startCmd.Flags().String(criSocket, "", "The cri socket path to be used.") startCmd.Flags().String(networkPlugin, "", "The name of the network plugin.") startCmd.Flags().Bool(enableDefaultCNI, false, "Enable the default CNI plugin (/etc/cni/net.d/k8s.conf). Used in conjunction with \"--network-plugin=cni\".") @@ -296,7 +295,7 @@ func runStart(cmd *cobra.Command, args []string) { } existing, err := config.Load(viper.GetString(config.MachineProfile)) - if err != nil && !os.IsNotExist(err) { + if err != nil && !config.IsNotExist(err) { exit.WithCodeT(exit.Data, "Unable to load config: {{.error}}", out.V{"error": err}) } @@ -368,8 +367,8 @@ func runStart(cmd *cobra.Command, args []string) { bootstrapCluster(bs, cr, mRunner, mc, preExists, isUpgrade) configureMounts() - // enable addons with start command - enableAddons() + // enable addons + addons.Start(viper.GetString(config.MachineProfile), addonList) if err = cacheAndLoadImagesInConfig(); err != nil { out.T(out.FailureType, "Unable to load cached images from config file.") @@ -408,15 +407,6 @@ func cacheISO(cfg *config.MachineConfig, driverName string) { } } -func enableAddons() { - for _, a := range addonList { - err := pkgaddons.Set(a, "true", viper.GetString(config.MachineProfile)) - if err != nil { - exit.WithError("addon enable failed", err) - } - } -} - func displayVersion(version string) { prefix := "" if viper.GetString(config.MachineProfile) != constants.DefaultMachineName { @@ -761,7 +751,7 @@ func validateUser(drvName string) { os.Exit(exit.Permissions) } _, err = config.Load(viper.GetString(config.MachineProfile)) - if err == nil || !os.IsNotExist(err) { + if err == nil || !config.IsNotExist(err) { out.T(out.Tip, "Tip: To remove this root owned cluster, run: sudo {{.cmd}} delete", out.V{"cmd": minikubeCmd()}) } if !useForce { diff --git a/cmd/minikube/cmd/unpause.go b/cmd/minikube/cmd/unpause.go index f2d86f8d9e12..4a3356edffbd 100644 --- a/cmd/minikube/cmd/unpause.go +++ b/cmd/minikube/cmd/unpause.go @@ -45,7 +45,7 @@ var unpauseCmd = &cobra.Command{ defer api.Close() cc, err := config.Load(cname) - if err != nil && !os.IsNotExist(err) { + if err != nil && !config.IsNotExist(err) { exit.WithError("Error loading profile config", err) } diff --git a/go.mod b/go.mod index c31b89c1c93a..346a954e3531 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,6 @@ require ( golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 golang.org/x/text v0.3.2 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect - gotest.tools v2.2.0+incompatible k8s.io/api v0.17.2 k8s.io/apimachinery v0.17.2 k8s.io/client-go v11.0.0+incompatible diff --git a/pkg/addons/addons.go b/pkg/addons/addons.go index 0437793d043c..06cf51d7dcd5 100644 --- a/pkg/addons/addons.go +++ b/pkg/addons/addons.go @@ -18,9 +18,10 @@ package addons import ( "fmt" - "os" - "path/filepath" + "path" "strconv" + "strings" + "time" "github.com/golang/glog" "github.com/pkg/errors" @@ -41,6 +42,7 @@ const defaultStorageClassProvisioner = "standard" // Set sets a value func Set(name, value, profile string) error { + glog.Infof("Setting %s=%s in profile %q", name, value, profile) a, valid := isAddonValid(name) if !valid { return errors.Errorf("%s is not a valid addon", name) @@ -66,6 +68,7 @@ func Set(name, value, profile string) error { return errors.Wrap(err, "running callbacks") } + glog.Infof("Writing new config for %q ...", profile) // Write the value return config.Write(profile, c) } @@ -100,6 +103,7 @@ func SetBool(m *config.MachineConfig, name string, val string) error { // enableOrDisableAddon updates addon status executing any commands necessary func enableOrDisableAddon(name, val, profile string) error { + glog.Infof("Setting addon %s=%s in %q", name, val, profile) enable, err := strconv.ParseBool(val) if err != nil { return errors.Wrapf(err, "parsing bool: %s", name) @@ -107,14 +111,14 @@ func enableOrDisableAddon(name, val, profile string) error { addon := assets.Addons[name] // check addon status before enabling/disabling it - alreadySet, err := isAddonAlreadySet(addon, enable) + alreadySet, err := isAddonAlreadySet(addon, enable, profile) if err != nil { out.ErrT(out.Conflict, "{{.error}}", out.V{"error": err}) return err } - //if addon is already enabled or disabled, do nothing + if alreadySet { - return nil + glog.Warningf("addon %s should already be in state %v", name, val) } if name == "istio" && enable { @@ -134,20 +138,15 @@ func enableOrDisableAddon(name, val, profile string) error { } defer api.Close() - //if minikube is not running, we return and simply update the value in the addon - //config and rewrite the file - if !cluster.IsMinikubeRunning(api) { - return nil - } - cfg, err := config.Load(profile) - if err != nil && !os.IsNotExist(err) { + if err != nil && !config.IsNotExist(err) { exit.WithCodeT(exit.Data, "Unable to load config: {{.error}}", out.V{"error": err}) } - host, err := cluster.CheckIfHostExistsAndLoad(api, cfg.Name) - if err != nil { - return errors.Wrap(err, "getting host") + host, err := cluster.CheckIfHostExistsAndLoad(api, profile) + if err != nil || !cluster.IsHostRunning(api, profile) { + glog.Warningf("%q is not running, writing %s=%v to disk and skipping enablement (err=%v)", profile, addon.Name(), enable, err) + return nil } cmd, err := machine.CommandRunner(host) @@ -159,11 +158,10 @@ func enableOrDisableAddon(name, val, profile string) error { return enableOrDisableAddonInternal(addon, cmd, data, enable, profile) } -func isAddonAlreadySet(addon *assets.Addon, enable bool) (bool, error) { - addonStatus, err := addon.IsEnabled() - +func isAddonAlreadySet(addon *assets.Addon, enable bool, profile string) (bool, error) { + addonStatus, err := addon.IsEnabled(profile) if err != nil { - return false, errors.Wrap(err, "get the addon status") + return false, errors.Wrap(err, "is enabled") } if addonStatus && enable { @@ -178,42 +176,50 @@ func isAddonAlreadySet(addon *assets.Addon, enable bool) (bool, error) { func enableOrDisableAddonInternal(addon *assets.Addon, cmd command.Runner, data interface{}, enable bool, profile string) error { files := []string{} for _, addon := range addon.Assets { - var addonFile assets.CopyableFile + var f assets.CopyableFile var err error if addon.IsTemplate() { - addonFile, err = addon.Evaluate(data) + f, err = addon.Evaluate(data) if err != nil { return errors.Wrapf(err, "evaluate bundled addon %s asset", addon.GetAssetName()) } } else { - addonFile = addon + f = addon } + fPath := path.Join(f.GetTargetDir(), f.GetTargetName()) + if enable { - if err := cmd.Copy(addonFile); err != nil { + glog.Infof("installing %s", fPath) + if err := cmd.Copy(f); err != nil { return err } } else { + glog.Infof("Removing %+v", fPath) defer func() { - if err := cmd.Remove(addonFile); err != nil { - glog.Warningf("error removing %s; addon should still be disabled as expected", addonFile) + if err := cmd.Remove(f); err != nil { + glog.Warningf("error removing %s; addon should still be disabled as expected", fPath) } }() } - files = append(files, filepath.Join(addonFile.GetTargetDir(), addonFile.GetTargetName())) + files = append(files, fPath) } command, err := kubectlCommand(profile, files, enable) if err != nil { return err } - if result, err := cmd.RunCmd(command); err != nil { - return errors.Wrapf(err, "error updating addon:\n%s", result.Output()) + glog.Infof("Running: %s", command) + rr, err := cmd.RunCmd(command) + if err != nil { + return errors.Wrapf(err, "addon apply") } + glog.Infof("output:\n%s", rr.Output()) return nil } // enableOrDisableStorageClasses enables or disables storage classes func enableOrDisableStorageClasses(name, val, profile string) error { + glog.Infof("enableOrDisableStorageClasses %s=%v on %q", name, val, profile) enable, err := strconv.ParseBool(val) if err != nil { return errors.Wrap(err, "Error parsing boolean") @@ -228,6 +234,17 @@ func enableOrDisableStorageClasses(name, val, profile string) error { return errors.Wrapf(err, "Error getting storagev1 interface %v ", err) } + api, err := machine.NewAPIClient() + if err != nil { + return errors.Wrap(err, "machine client") + } + defer api.Close() + + if !cluster.IsHostRunning(api, profile) { + glog.Warningf("%q is not running, writing %s=%v to disk and skipping enablement", profile, name, val) + return enableOrDisableAddon(name, val, profile) + } + if enable { // Only StorageClass for 'name' should be marked as default err = storageclass.SetDefaultStorageClass(storagev1, class) @@ -244,3 +261,39 @@ func enableOrDisableStorageClasses(name, val, profile string) error { return enableOrDisableAddon(name, val, profile) } + +// Start enables the default addons for a profile, plus any additional +func Start(profile string, additional []string) { + start := time.Now() + glog.Infof("enableAddons") + defer func() { + glog.Infof("enableAddons completed in %s", time.Since(start)) + }() + toEnable := []string{} + + // Apply addons that are enabled by default + for name, a := range assets.Addons { + enabled, err := a.IsEnabled(profile) + if err != nil { + glog.Errorf("is-enabled failed for %q: %v", a.Name(), err) + continue + } + if enabled { + toEnable = append(toEnable, name) + } + } + + toEnable = append(toEnable, additional...) + if len(toEnable) == 0 { + return + } + + out.T(out.AddonEnable, "Enabling addons: {{.addons}}", out.V{"addons": strings.Join(toEnable, ", ")}) + for _, a := range toEnable { + err := Set(a, "true", profile) + if err != nil { + // Intentionally non-fatal + out.WarningT("Enabling '{{.name}}' returned an error: {{.error}}", out.V{"name": a, "error": err}) + } + } +} diff --git a/pkg/addons/addons_test.go b/pkg/addons/addons_test.go index 2e7a6bed07dd..e0337705c131 100644 --- a/pkg/addons/addons_test.go +++ b/pkg/addons/addons_test.go @@ -17,93 +17,115 @@ limitations under the License. package addons import ( - "fmt" + "io/ioutil" "os" + "path/filepath" "testing" - "gotest.tools/assert" "k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/localpath" ) +func createTestProfile(t *testing.T) string { + t.Helper() + td, err := ioutil.TempDir("", "profile") + if err != nil { + t.Fatalf("tempdir: %v", err) + } + + err = os.Setenv(localpath.MinikubeHome, td) + if err != nil { + t.Errorf("error setting up test environment. could not set %s", localpath.MinikubeHome) + } + + // Not necessary, but it is a handy random alphanumeric + name := filepath.Base(td) + if err := os.MkdirAll(config.ProfileFolderPath(name), 0777); err != nil { + t.Fatalf("error creating temporary directory") + } + if err := config.DefaultLoader.WriteConfigToFile(name, &config.MachineConfig{}); err != nil { + t.Fatalf("error creating temporary profile config: %v", err) + } + return name +} + func TestIsAddonAlreadySet(t *testing.T) { - testCases := []struct { - addonName string - }{ - { - addonName: "ingress", - }, - - { - addonName: "registry", - }, - } - - for _, test := range testCases { - addon := assets.Addons[test.addonName] - addonStatus, _ := addon.IsEnabled() - - alreadySet, err := isAddonAlreadySet(addon, addonStatus) - if !alreadySet { - if addonStatus { - t.Errorf("Did not get expected status, \n\n expected %+v already enabled", test.addonName) - } else { - t.Errorf("Did not get expected status, \n\n expected %+v already disabled", test.addonName) - } - } - if err != nil { - t.Errorf("Got unexpected error: %+v", err) - } + profile := createTestProfile(t) + if err := Set("registry", "true", profile); err != nil { + t.Errorf("unable to set registry true: %v", err) + } + enabled, err := assets.Addons["registry"].IsEnabled(profile) + if err != nil { + t.Errorf("registry: %v", err) } + if !enabled { + t.Errorf("expected registry to be enabled") + } + + enabled, err = assets.Addons["ingress"].IsEnabled(profile) + if err != nil { + t.Errorf("ingress: %v", err) + } + if enabled { + t.Errorf("expected ingress to not be enabled") + } + } func TestDisableUnknownAddon(t *testing.T) { - tmpProfile := "temp-minikube-profile" - if err := Set("InvalidAddon", "false", tmpProfile); err == nil { + profile := createTestProfile(t) + if err := Set("InvalidAddon", "false", profile); err == nil { t.Fatalf("Disable did not return error for unknown addon") } } func TestEnableUnknownAddon(t *testing.T) { - tmpProfile := "temp-minikube-profile" - if err := Set("InvalidAddon", "true", tmpProfile); err == nil { + profile := createTestProfile(t) + if err := Set("InvalidAddon", "true", profile); err == nil { t.Fatalf("Enable did not return error for unknown addon") } } func TestEnableAndDisableAddon(t *testing.T) { - tests := []struct { - name string - enable bool - }{ - { - name: "test enable", - enable: true, - }, { - name: "test disable", - enable: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - tmpProfile := "temp-minikube-profile" - if err := os.MkdirAll(config.ProfileFolderPath(tmpProfile), 0777); err != nil { - t.Fatalf("error creating temporary directory") - } - defer os.RemoveAll(config.ProfileFolderPath(tmpProfile)) - - if err := config.DefaultLoader.WriteConfigToFile(tmpProfile, &config.MachineConfig{}); err != nil { - t.Fatalf("error creating temporary profile config: %v", err) - } - if err := Set("dashboard", fmt.Sprintf("%t", test.enable), tmpProfile); err != nil { - t.Fatalf("Disable returned unexpected error: " + err.Error()) - } - c, err := config.DefaultLoader.LoadConfigFromFile(tmpProfile) - if err != nil { - t.Fatalf("error loading config: %v", err) - } - assert.Equal(t, c.Addons["dashboard"], test.enable) - }) + profile := createTestProfile(t) + + // enable + if err := Set("dashboard", "true", profile); err != nil { + t.Errorf("Disable returned unexpected error: " + err.Error()) + } + + c, err := config.DefaultLoader.LoadConfigFromFile(profile) + if err != nil { + t.Errorf("unable to load profile: %v", err) + } + if c.Addons["dashboard"] != true { + t.Errorf("expected dashboard to be enabled") + } + + // disable + if err := Set("dashboard", "false", profile); err != nil { + t.Errorf("Disable returned unexpected error: " + err.Error()) + } + + c, err = config.DefaultLoader.LoadConfigFromFile(profile) + if err != nil { + t.Errorf("unable to load profile: %v", err) + } + if c.Addons["dashboard"] != false { + t.Errorf("expected dashboard to be enabled") + } +} + +func TestStart(t *testing.T) { + profile := createTestProfile(t) + Start(profile, []string{"dashboard"}) + + enabled, err := assets.Addons["dashboard"].IsEnabled(profile) + if err != nil { + t.Errorf("dashboard: %v", err) + } + if !enabled { + t.Errorf("expected dashboard to be enabled") } } diff --git a/pkg/addons/kubectl.go b/pkg/addons/kubectl.go index 5cc96383f874..1e3fbe2a978e 100644 --- a/pkg/addons/kubectl.go +++ b/pkg/addons/kubectl.go @@ -17,7 +17,6 @@ limitations under the License. package addons import ( - "os" "os/exec" "path" @@ -53,7 +52,7 @@ func kubectlCommand(profile string, files []string, enable bool) (*exec.Cmd, err func kubernetesVersion(profile string) (string, error) { cc, err := config.Load(profile) - if err != nil && !os.IsNotExist(err) { + if err != nil && !config.IsNotExist(err) { return "", err } version := constants.DefaultKubernetesVersion diff --git a/pkg/minikube/assets/addons.go b/pkg/minikube/assets/addons.go index 67fd6b33df0d..3bdcb758aab8 100644 --- a/pkg/minikube/assets/addons.go +++ b/pkg/minikube/assets/addons.go @@ -23,8 +23,8 @@ import ( "path/filepath" "runtime" + "github.com/golang/glog" "github.com/pkg/errors" - "github.com/spf13/viper" "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/localpath" @@ -54,14 +54,21 @@ func (a *Addon) Name() string { return a.addonName } -// IsEnabled checks if an Addon is enabled for the current profile -func (a *Addon) IsEnabled() (bool, error) { - c, err := config.Load(viper.GetString(config.MachineProfile)) - if err == nil { - if status, ok := c.Addons[a.Name()]; ok { - return status, nil - } +// IsEnabled checks if an Addon is enabled for the given profile +func (a *Addon) IsEnabled(profile string) (bool, error) { + c, err := config.Load(profile) + if err != nil { + return false, errors.Wrap(err, "load") + } + + // Is this addon explicitly listed in their configuration? + status, ok := c.Addons[a.Name()] + glog.V(1).Infof("IsEnabled %q = %v (listed in config=%v)", a.Name(), status, ok) + if ok { + return status, nil } + + // Return the default unconfigured state of the addon return a.enabled, nil } diff --git a/pkg/minikube/bootstrapper/bsutil/files.go b/pkg/minikube/bootstrapper/bsutil/files.go index decb2fed55b8..df6cb7472f16 100644 --- a/pkg/minikube/bootstrapper/bsutil/files.go +++ b/pkg/minikube/bootstrapper/bsutil/files.go @@ -20,7 +20,6 @@ package bsutil import ( "path" - "github.com/pkg/errors" "k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/vmpath" @@ -52,33 +51,3 @@ func ConfigFileAssets(cfg config.KubernetesConfig, kubeadm []byte, kubelet []byt } return fs } - -// AddAddons adds addons to list of files -func AddAddons(files *[]assets.CopyableFile, data interface{}) error { - // add addons to file list - // custom addons - if err := assets.AddMinikubeDirAssets(files); err != nil { - return errors.Wrap(err, "adding minikube dir assets") - } - // bundled addons - for _, addonBundle := range assets.Addons { - if isEnabled, err := addonBundle.IsEnabled(); err == nil && isEnabled { - for _, addon := range addonBundle.Assets { - if addon.IsTemplate() { - addonFile, err := addon.Evaluate(data) - if err != nil { - return errors.Wrapf(err, "evaluate bundled addon %s asset", addon.GetAssetName()) - } - - *files = append(*files, addonFile) - } else { - *files = append(*files, addon) - } - } - } else if err != nil { - return nil - } - } - - return nil -} diff --git a/pkg/minikube/bootstrapper/bsutil/kubeadm_test.go b/pkg/minikube/bootstrapper/bsutil/kubeadm_test.go index a5aef997bdcd..4a94b9dc1e3d 100644 --- a/pkg/minikube/bootstrapper/bsutil/kubeadm_test.go +++ b/pkg/minikube/bootstrapper/bsutil/kubeadm_test.go @@ -120,7 +120,7 @@ func TestGenerateKubeadmYAMLDNS(t *testing.T) { t.Run(tname, func(t *testing.T) { cfg := tc.cfg cfg.Nodes = []config.Node{ - config.Node{ + { IP: "1.1.1.1", Name: "mk", ControlPlane: true, @@ -179,7 +179,7 @@ func TestGenerateKubeadmYAML(t *testing.T) { {"options", "docker", false, config.MachineConfig{KubernetesConfig: config.KubernetesConfig{ExtraOptions: extraOpts}}}, {"crio-options-gates", "crio", false, config.MachineConfig{KubernetesConfig: config.KubernetesConfig{ExtraOptions: extraOpts, FeatureGates: "a=b"}}}, {"unknown-component", "docker", true, config.MachineConfig{KubernetesConfig: config.KubernetesConfig{ExtraOptions: config.ExtraOptionSlice{config.ExtraOption{Component: "not-a-real-component", Key: "killswitch", Value: "true"}}}}}, - {"containerd-api-port", "containerd", false, config.MachineConfig{Nodes: []config.Node{config.Node{Port: 12345}}}}, + {"containerd-api-port", "containerd", false, config.MachineConfig{Nodes: []config.Node{{Port: 12345}}}}, {"containerd-pod-network-cidr", "containerd", false, config.MachineConfig{KubernetesConfig: config.KubernetesConfig{ExtraOptions: extraOptsPodCidr}}}, {"image-repository", "docker", false, config.MachineConfig{KubernetesConfig: config.KubernetesConfig{ImageRepository: "test/repo"}}}, } @@ -199,7 +199,7 @@ func TestGenerateKubeadmYAML(t *testing.T) { cfg.Nodes[0].ControlPlane = true } else { cfg.Nodes = []config.Node{ - config.Node{ + { IP: "1.1.1.1", Name: "mk", ControlPlane: true, diff --git a/pkg/minikube/bootstrapper/bsutil/kubelet_test.go b/pkg/minikube/bootstrapper/bsutil/kubelet_test.go index 8b49bc653b3c..b50a4b89ef83 100644 --- a/pkg/minikube/bootstrapper/bsutil/kubelet_test.go +++ b/pkg/minikube/bootstrapper/bsutil/kubelet_test.go @@ -42,7 +42,7 @@ func TestGenerateKubeletConfig(t *testing.T) { ContainerRuntime: "docker", }, Nodes: []config.Node{ - config.Node{ + { IP: "192.168.1.100", Name: "minikube", ControlPlane: true, @@ -67,7 +67,7 @@ ExecStart=/var/lib/minikube/binaries/v1.11.10/kubelet --allow-privileged=true -- ContainerRuntime: "cri-o", }, Nodes: []config.Node{ - config.Node{ + { IP: "192.168.1.100", Name: "minikube", ControlPlane: true, @@ -92,7 +92,7 @@ ExecStart=/var/lib/minikube/binaries/v1.17.2/kubelet --authorization-mode=Webhoo ContainerRuntime: "containerd", }, Nodes: []config.Node{ - config.Node{ + { IP: "192.168.1.100", Name: "minikube", ControlPlane: true, @@ -124,7 +124,7 @@ ExecStart=/var/lib/minikube/binaries/v1.17.2/kubelet --authorization-mode=Webhoo }, }, Nodes: []config.Node{ - config.Node{ + { IP: "192.168.1.100", Name: "minikube", ControlPlane: true, @@ -150,7 +150,7 @@ ExecStart=/var/lib/minikube/binaries/v1.17.2/kubelet --authorization-mode=Webhoo ImageRepository: "docker-proxy-image.io/google_containers", }, Nodes: []config.Node{ - config.Node{ + { IP: "192.168.1.100", Name: "minikube", ControlPlane: true, diff --git a/pkg/minikube/bootstrapper/kubeadm/kubeadm.go b/pkg/minikube/bootstrapper/kubeadm/kubeadm.go index ad2f481dd521..23dcf30ead1a 100644 --- a/pkg/minikube/bootstrapper/kubeadm/kubeadm.go +++ b/pkg/minikube/bootstrapper/kubeadm/kubeadm.go @@ -41,7 +41,6 @@ import ( "k8s.io/minikube/pkg/drivers/kic" "k8s.io/minikube/pkg/drivers/kic/oci" "k8s.io/minikube/pkg/kapi" - "k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/bootstrapper" "k8s.io/minikube/pkg/minikube/bootstrapper/bsutil" "k8s.io/minikube/pkg/minikube/bootstrapper/bsutil/kverify" @@ -484,10 +483,6 @@ func (k *Bootstrapper) UpdateCluster(cfg config.MachineConfig) error { } files := bsutil.ConfigFileAssets(cfg.KubernetesConfig, kubeadmCfg, kubeletCfg, kubeletService, cniFile) - if err := bsutil.AddAddons(&files, assets.GenerateTemplateData(cfg.KubernetesConfig)); err != nil { - return errors.Wrap(err, "adding addons") - } - // Combine mkdir request into a single call to reduce load dirs := []string{} for _, f := range files { diff --git a/pkg/minikube/cluster/cluster.go b/pkg/minikube/cluster/cluster.go index af3f6cc1845c..16f934cec1a2 100644 --- a/pkg/minikube/cluster/cluster.go +++ b/pkg/minikube/cluster/cluster.go @@ -627,17 +627,18 @@ func getIPForInterface(name string) (net.IP, error) { // CheckIfHostExistsAndLoad checks if a host exists, and loads it if it does func CheckIfHostExistsAndLoad(api libmachine.API, machineName string) (*host.Host, error) { + glog.Infof("Checking if %q exists ...", machineName) exists, err := api.Exists(machineName) if err != nil { return nil, errors.Wrapf(err, "Error checking that machine exists: %s", machineName) } if !exists { - return nil, errors.Errorf("Machine does not exist for api.Exists(%s)", machineName) + return nil, errors.Errorf("machine %q does not exist", machineName) } host, err := api.Load(machineName) if err != nil { - return nil, errors.Wrapf(err, "Error loading store for: %s", machineName) + return nil, errors.Wrapf(err, "loading machine %q", machineName) } return host, nil } @@ -666,14 +667,15 @@ func CreateSSHShell(api libmachine.API, args []string) error { return client.Shell(args...) } -// IsMinikubeRunning checks that minikube has a status available and that -// the status is `Running` -func IsMinikubeRunning(api libmachine.API) bool { - s, err := GetHostStatus(api, viper.GetString(config.MachineProfile)) +// IsHostRunning asserts that this profile's primary host is in state "Running" +func IsHostRunning(api libmachine.API, name string) bool { + s, err := GetHostStatus(api, name) if err != nil { + glog.Warningf("host status for %q returned error: %v", name, err) return false } if s != state.Running.String() { + glog.Warningf("%q host status: %s", name, s) return false } return true diff --git a/pkg/minikube/config/config.go b/pkg/minikube/config/config.go index 7abd2a8d8119..14a6aa5d3340 100644 --- a/pkg/minikube/config/config.go +++ b/pkg/minikube/config/config.go @@ -18,12 +18,13 @@ package config import ( "encoding/json" - "errors" "fmt" "io" "io/ioutil" "os" + "github.com/pkg/errors" + "k8s.io/minikube/pkg/minikube/localpath" ) @@ -53,6 +54,22 @@ var ( ErrKeyNotFound = errors.New("specified key could not be found in config") ) +type ErrNotExist struct { + s string +} + +func (e *ErrNotExist) Error() string { + return e.s +} + +// IsNotExist returns whether the error means a nonexistent configuration +func IsNotExist(err error) bool { + if _, ok := err.(*ErrNotExist); ok { + return true + } + return false +} + // MinikubeConfig represents minikube config type MinikubeConfig map[string]interface{} @@ -148,17 +165,20 @@ func (c *simpleConfigLoader) LoadConfigFromFile(profileName string, miniHome ... // Move to profile package path := profileFilePath(profileName, miniHome...) - if _, err := os.Stat(path); os.IsNotExist(err) { - return nil, err + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + return nil, &ErrNotExist{fmt.Sprintf("cluster %q does not exist", profileName)} + } + return nil, errors.Wrap(err, "stat") } data, err := ioutil.ReadFile(path) if err != nil { - return nil, err + return nil, errors.Wrap(err, "read") } if err := json.Unmarshal(data, &cc); err != nil { - return nil, err + return nil, errors.Wrap(err, "unmarshal") } return &cc, nil } diff --git a/pkg/minikube/out/style.go b/pkg/minikube/out/style.go index 682ca2fc0c08..56f6bf8b3526 100644 --- a/pkg/minikube/out/style.go +++ b/pkg/minikube/out/style.go @@ -116,6 +116,8 @@ var styles = map[StyleEnum]style{ MountOptions: {Prefix: "💾 "}, Fileserver: {Prefix: "🚀 ", OmitNewline: true}, DryRun: {Prefix: "🏜️ "}, + AddonEnable: {Prefix: "🌟 "}, + AddonDisable: {Prefix: "🌑 "}, } // Add a prefix to a string diff --git a/pkg/minikube/out/style_enum.go b/pkg/minikube/out/style_enum.go index 0d0c6e9ae1f5..150b19fa3781 100644 --- a/pkg/minikube/out/style_enum.go +++ b/pkg/minikube/out/style_enum.go @@ -87,4 +87,6 @@ const ( Pause Unpause DryRun + AddonEnable + AddonDisable ) diff --git a/pkg/minikube/tunnel/cluster_inspector_test.go b/pkg/minikube/tunnel/cluster_inspector_test.go index 4c8332facd15..c3be2e2483ab 100644 --- a/pkg/minikube/tunnel/cluster_inspector_test.go +++ b/pkg/minikube/tunnel/cluster_inspector_test.go @@ -40,10 +40,14 @@ func TestAPIError(t *testing.T) { machineAPI, configLoader, machineName, } - s, r, err := inspector.getStateAndRoute() + _, _, err := inspector.getStateAndRoute() + if err == nil { + t.Errorf("expected error, got nil") + } - if err == nil || !strings.Contains(err.Error(), "Machine does not exist") { - t.Errorf("cluster inspector should propagate errors from API, getStateAndRoute() returned \"%v, %v\", %v", s, r, err) + // Make sure we properly propagate errors upward + if !strings.Contains(err.Error(), "exist") { + t.Errorf("getStateAndRoute error=%q, expected *exist*", err) } }