2019-12-18 19:31:29 +00:00
|
|
|
/*
|
|
|
|
Copyright 2019 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 addons
|
|
|
|
|
|
|
|
import (
|
2019-12-19 05:35:46 +00:00
|
|
|
"fmt"
|
2020-01-30 23:54:04 +00:00
|
|
|
"path"
|
2020-02-03 18:49:40 +00:00
|
|
|
"sort"
|
2019-12-18 19:31:29 +00:00
|
|
|
"strconv"
|
2020-01-31 01:46:25 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2019-12-18 19:31:29 +00:00
|
|
|
|
2020-01-22 23:11:33 +00:00
|
|
|
"github.com/golang/glog"
|
2019-12-18 19:31:29 +00:00
|
|
|
"github.com/pkg/errors"
|
2019-12-30 15:10:05 +00:00
|
|
|
"github.com/spf13/viper"
|
2019-12-18 19:31:29 +00:00
|
|
|
"k8s.io/minikube/pkg/minikube/assets"
|
|
|
|
"k8s.io/minikube/pkg/minikube/cluster"
|
|
|
|
"k8s.io/minikube/pkg/minikube/command"
|
|
|
|
"k8s.io/minikube/pkg/minikube/config"
|
|
|
|
"k8s.io/minikube/pkg/minikube/exit"
|
|
|
|
"k8s.io/minikube/pkg/minikube/machine"
|
|
|
|
"k8s.io/minikube/pkg/minikube/out"
|
|
|
|
"k8s.io/minikube/pkg/minikube/storageclass"
|
2019-12-30 15:10:05 +00:00
|
|
|
pkgutil "k8s.io/minikube/pkg/util"
|
2019-12-18 19:31:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// defaultStorageClassProvisioner is the name of the default storage class provisioner
|
|
|
|
const defaultStorageClassProvisioner = "standard"
|
|
|
|
|
2020-01-13 20:37:19 +00:00
|
|
|
// Set sets a value
|
2019-12-19 05:35:46 +00:00
|
|
|
func Set(name, value, profile string) error {
|
2020-01-30 23:54:04 +00:00
|
|
|
glog.Infof("Setting %s=%s in profile %q", name, value, profile)
|
2019-12-19 06:21:53 +00:00
|
|
|
a, valid := isAddonValid(name)
|
2019-12-19 05:35:46 +00:00
|
|
|
if !valid {
|
|
|
|
return errors.Errorf("%s is not a valid addon", name)
|
|
|
|
}
|
|
|
|
|
2019-12-19 06:21:53 +00:00
|
|
|
// Run any additional validations for this property
|
|
|
|
if err := run(name, value, profile, a.validations); err != nil {
|
2020-01-06 21:42:09 +00:00
|
|
|
return errors.Wrap(err, "running validations")
|
2019-12-19 06:21:53 +00:00
|
|
|
}
|
|
|
|
|
2019-12-19 05:35:46 +00:00
|
|
|
// Set the value
|
|
|
|
c, err := config.Load(profile)
|
|
|
|
if err != nil {
|
2020-01-06 21:42:09 +00:00
|
|
|
return errors.Wrap(err, "loading profile")
|
2019-12-19 05:35:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.set(c, name, value); err != nil {
|
2020-01-06 21:42:09 +00:00
|
|
|
return errors.Wrap(err, "setting new value of addon")
|
2019-12-19 05:35:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run any callbacks for this property
|
|
|
|
if err := run(name, value, profile, a.callbacks); err != nil {
|
2020-01-06 21:42:09 +00:00
|
|
|
return errors.Wrap(err, "running callbacks")
|
2019-12-19 05:35:46 +00:00
|
|
|
}
|
|
|
|
|
2020-02-03 18:49:40 +00:00
|
|
|
glog.Infof("Writing out %q config to set %s=%v...", profile, name, value)
|
2019-12-19 05:35:46 +00:00
|
|
|
return config.Write(profile, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs all the validation or callback functions and collects errors
|
|
|
|
func run(name, value, profile string, fns []setFn) error {
|
|
|
|
var errors []error
|
|
|
|
for _, fn := range fns {
|
|
|
|
err := fn(name, value, profile)
|
|
|
|
if err != nil {
|
|
|
|
errors = append(errors, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(errors) > 0 {
|
|
|
|
return fmt.Errorf("%v", errors)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-18 19:31:29 +00:00
|
|
|
// SetBool sets a bool value
|
2019-12-19 05:35:46 +00:00
|
|
|
func SetBool(m *config.MachineConfig, name string, val string) error {
|
2019-12-18 19:31:29 +00:00
|
|
|
b, err := strconv.ParseBool(val)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-19 05:35:46 +00:00
|
|
|
if m.Addons == nil {
|
|
|
|
m.Addons = map[string]bool{}
|
|
|
|
}
|
2019-12-18 19:31:29 +00:00
|
|
|
m.Addons[name] = b
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-19 06:33:00 +00:00
|
|
|
// enableOrDisableAddon updates addon status executing any commands necessary
|
|
|
|
func enableOrDisableAddon(name, val, profile string) error {
|
2020-01-30 23:54:04 +00:00
|
|
|
glog.Infof("Setting addon %s=%s in %q", name, val, profile)
|
2019-12-18 19:31:29 +00:00
|
|
|
enable, err := strconv.ParseBool(val)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "parsing bool: %s", name)
|
|
|
|
}
|
|
|
|
addon := assets.Addons[name]
|
|
|
|
|
|
|
|
// check addon status before enabling/disabling it
|
2020-01-31 20:19:25 +00:00
|
|
|
alreadySet, err := isAddonAlreadySet(addon, enable, profile)
|
2019-12-18 19:31:29 +00:00
|
|
|
if err != nil {
|
|
|
|
out.ErrT(out.Conflict, "{{.error}}", out.V{"error": err})
|
|
|
|
return err
|
|
|
|
}
|
2020-01-30 23:54:04 +00:00
|
|
|
|
2019-12-18 19:31:29 +00:00
|
|
|
if alreadySet {
|
2020-01-30 23:54:04 +00:00
|
|
|
glog.Warningf("addon %s should already be in state %v", name, val)
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
|
|
|
|
2019-12-30 15:10:05 +00:00
|
|
|
if name == "istio" && enable {
|
|
|
|
minMem := 8192
|
|
|
|
minCpus := 4
|
|
|
|
memorySizeMB := pkgutil.CalculateSizeInMB(viper.GetString("memory"))
|
|
|
|
cpuCount := viper.GetInt("cpus")
|
|
|
|
if memorySizeMB < minMem || cpuCount < minCpus {
|
|
|
|
out.WarningT("Enable istio needs {{.minMem}} MB of memory and {{.minCpus}} CPUs.", out.V{"minMem": minMem, "minCpus": minCpus})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(r2d4): config package should not reference API, pull this out
|
2019-12-18 19:31:29 +00:00
|
|
|
api, err := machine.NewAPIClient()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "machine client")
|
|
|
|
}
|
|
|
|
defer api.Close()
|
|
|
|
|
2019-12-19 06:02:08 +00:00
|
|
|
cfg, err := config.Load(profile)
|
2020-01-31 22:19:08 +00:00
|
|
|
if err != nil && !config.IsNotExist(err) {
|
2019-12-18 19:31:29 +00:00
|
|
|
exit.WithCodeT(exit.Data, "Unable to load config: {{.error}}", out.V{"error": err})
|
|
|
|
}
|
|
|
|
|
2020-01-31 20:19:25 +00:00
|
|
|
host, err := cluster.CheckIfHostExistsAndLoad(api, profile)
|
|
|
|
if err != nil || !cluster.IsHostRunning(api, profile) {
|
2020-01-31 21:24:30 +00:00
|
|
|
glog.Warningf("%q is not running, writing %s=%v to disk and skipping enablement (err=%v)", profile, addon.Name(), enable, err)
|
2020-01-31 20:19:25 +00:00
|
|
|
return nil
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cmd, err := machine.CommandRunner(host)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "command runner")
|
|
|
|
}
|
|
|
|
|
|
|
|
data := assets.GenerateTemplateData(cfg.KubernetesConfig)
|
2020-01-14 18:52:27 +00:00
|
|
|
return enableOrDisableAddonInternal(addon, cmd, data, enable, profile)
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
|
|
|
|
2020-01-31 20:19:25 +00:00
|
|
|
func isAddonAlreadySet(addon *assets.Addon, enable bool, profile string) (bool, error) {
|
|
|
|
addonStatus, err := addon.IsEnabled(profile)
|
2019-12-18 19:31:29 +00:00
|
|
|
if err != nil {
|
2020-01-31 20:19:25 +00:00
|
|
|
return false, errors.Wrap(err, "is enabled")
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if addonStatus && enable {
|
|
|
|
return true, nil
|
|
|
|
} else if !addonStatus && !enable {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2020-01-14 18:52:27 +00:00
|
|
|
func enableOrDisableAddonInternal(addon *assets.Addon, cmd command.Runner, data interface{}, enable bool, profile string) error {
|
2020-01-22 22:18:20 +00:00
|
|
|
files := []string{}
|
2020-01-06 21:42:09 +00:00
|
|
|
for _, addon := range addon.Assets {
|
2020-01-30 23:54:04 +00:00
|
|
|
var f assets.CopyableFile
|
2020-01-22 22:18:20 +00:00
|
|
|
var err error
|
2020-01-06 21:42:09 +00:00
|
|
|
if addon.IsTemplate() {
|
2020-01-30 23:54:04 +00:00
|
|
|
f, err = addon.Evaluate(data)
|
2020-01-06 21:42:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "evaluate bundled addon %s asset", addon.GetAssetName())
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
2020-01-06 21:42:09 +00:00
|
|
|
|
|
|
|
} else {
|
2020-01-30 23:54:04 +00:00
|
|
|
f = addon
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
2020-01-30 23:54:04 +00:00
|
|
|
fPath := path.Join(f.GetTargetDir(), f.GetTargetName())
|
|
|
|
|
2020-01-22 22:18:20 +00:00
|
|
|
if enable {
|
2020-01-30 23:54:04 +00:00
|
|
|
glog.Infof("installing %s", fPath)
|
|
|
|
if err := cmd.Copy(f); err != nil {
|
2020-01-22 22:18:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
2020-01-30 23:54:04 +00:00
|
|
|
glog.Infof("Removing %+v", fPath)
|
2020-01-22 23:11:33 +00:00
|
|
|
defer func() {
|
2020-01-30 23:54:04 +00:00
|
|
|
if err := cmd.Remove(f); err != nil {
|
|
|
|
glog.Warningf("error removing %s; addon should still be disabled as expected", fPath)
|
2020-01-22 23:11:33 +00:00
|
|
|
}
|
|
|
|
}()
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
2020-01-30 23:54:04 +00:00
|
|
|
files = append(files, fPath)
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
2020-01-22 22:18:20 +00:00
|
|
|
command, err := kubectlCommand(profile, files, enable)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-30 23:54:04 +00:00
|
|
|
glog.Infof("Running: %s", command)
|
|
|
|
rr, err := cmd.RunCmd(command)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "addon apply")
|
2020-01-22 22:18:20 +00:00
|
|
|
}
|
2020-01-30 23:54:04 +00:00
|
|
|
glog.Infof("output:\n%s", rr.Output())
|
2020-01-22 22:18:20 +00:00
|
|
|
return nil
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
|
|
|
|
2019-12-19 06:33:00 +00:00
|
|
|
// enableOrDisableStorageClasses enables or disables storage classes
|
|
|
|
func enableOrDisableStorageClasses(name, val, profile string) error {
|
2020-01-31 20:19:25 +00:00
|
|
|
glog.Infof("enableOrDisableStorageClasses %s=%v on %q", name, val, profile)
|
2019-12-18 19:31:29 +00:00
|
|
|
enable, err := strconv.ParseBool(val)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Error parsing boolean")
|
|
|
|
}
|
|
|
|
|
|
|
|
class := defaultStorageClassProvisioner
|
|
|
|
if name == "storage-provisioner-gluster" {
|
|
|
|
class = "glusterfile"
|
|
|
|
}
|
|
|
|
storagev1, err := storageclass.GetStoragev1()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Error getting storagev1 interface %v ", err)
|
|
|
|
}
|
|
|
|
|
2020-01-31 20:19:25 +00:00
|
|
|
api, err := machine.NewAPIClient()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "machine client")
|
|
|
|
}
|
|
|
|
defer api.Close()
|
|
|
|
|
|
|
|
if !cluster.IsHostRunning(api, profile) {
|
2020-01-31 21:24:30 +00:00
|
|
|
glog.Warningf("%q is not running, writing %s=%v to disk and skipping enablement", profile, name, val)
|
2020-01-31 20:19:25 +00:00
|
|
|
return enableOrDisableAddon(name, val, profile)
|
|
|
|
}
|
|
|
|
|
2019-12-18 19:31:29 +00:00
|
|
|
if enable {
|
|
|
|
// Only StorageClass for 'name' should be marked as default
|
|
|
|
err = storageclass.SetDefaultStorageClass(storagev1, class)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Error making %s the default storage class", class)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Unset the StorageClass as default
|
|
|
|
err := storageclass.DisableDefaultStorageClass(storagev1, class)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Error disabling %s as the default storage class", class)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-19 06:33:00 +00:00
|
|
|
return enableOrDisableAddon(name, val, profile)
|
2019-12-18 19:31:29 +00:00
|
|
|
}
|
2020-01-31 01:46:25 +00:00
|
|
|
|
|
|
|
// Start enables the default addons for a profile, plus any additional
|
2020-02-03 18:49:40 +00:00
|
|
|
func Start(profile string, toEnable map[string]bool, additional []string) {
|
2020-01-31 01:46:25 +00:00
|
|
|
start := time.Now()
|
2020-02-03 18:49:40 +00:00
|
|
|
glog.Infof("enableAddons start: toEnable=%v, additional=%s", toEnable, additional)
|
2020-01-31 01:46:25 +00:00
|
|
|
defer func() {
|
|
|
|
glog.Infof("enableAddons completed in %s", time.Since(start))
|
|
|
|
}()
|
|
|
|
|
2020-02-03 18:49:40 +00:00
|
|
|
// Get the default values of any addons not saved to our config
|
2020-01-31 01:46:25 +00:00
|
|
|
for name, a := range assets.Addons {
|
2020-02-03 18:49:40 +00:00
|
|
|
defaultVal, err := a.IsEnabled(profile)
|
2020-01-31 01:46:25 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("is-enabled failed for %q: %v", a.Name(), err)
|
|
|
|
continue
|
|
|
|
}
|
2020-02-03 18:49:40 +00:00
|
|
|
|
|
|
|
_, exists := toEnable[name]
|
|
|
|
if !exists {
|
|
|
|
toEnable[name] = defaultVal
|
2020-01-31 01:46:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-03 18:49:40 +00:00
|
|
|
// Apply new addons
|
|
|
|
for _, name := range additional {
|
|
|
|
toEnable[name] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
toEnableList := []string{}
|
|
|
|
for k, v := range toEnable {
|
|
|
|
if v {
|
|
|
|
toEnableList = append(toEnableList, k)
|
|
|
|
}
|
2020-01-31 01:46:25 +00:00
|
|
|
}
|
2020-02-03 18:49:40 +00:00
|
|
|
sort.Strings(toEnableList)
|
2020-01-31 01:46:25 +00:00
|
|
|
|
2020-02-03 18:49:40 +00:00
|
|
|
out.T(out.AddonEnable, "Enabling addons: {{.addons}}", out.V{"addons": strings.Join(toEnableList, ", ")})
|
|
|
|
for _, a := range toEnableList {
|
2020-01-31 01:46:25 +00:00
|
|
|
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})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|