Merge pull request #4811 from medyagh/add_profile_list_cmd

Add a new command (profile list)
pull/4827/head
Medya Ghazizadeh 2019-07-19 15:43:50 -07:00 committed by GitHub
commit 9f42df6209
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 434 additions and 9 deletions

View File

@ -0,0 +1,73 @@
/*
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 config
import (
"fmt"
"os"
"strconv"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/console"
"k8s.io/minikube/pkg/minikube/exit"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)
var profileListCmd = &cobra.Command{
Use: "list",
Short: "Lists all minikube profiles.",
Long: "Lists all minikube profiles.",
Run: func(cmd *cobra.Command, args []string) {
var validData [][]string
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Profile", "VM Driver", "NodeIP", "Node Port", "Kubernetes Version"})
table.SetAutoFormatHeaders(false)
table.SetBorders(tablewriter.Border{Left: true, Top: true, Right: true, Bottom: true})
table.SetCenterSeparator("|")
validProfiles, invalidProfiles, err := config.ListProfiles()
for _, p := range validProfiles {
validData = append(validData, []string{p.Name, p.Config.MachineConfig.VMDriver, p.Config.KubernetesConfig.NodeIP, strconv.Itoa(p.Config.KubernetesConfig.NodePort), p.Config.KubernetesConfig.KubernetesVersion})
}
table.AppendBulk(validData)
table.Render()
if invalidProfiles != nil {
console.OutT(console.WarningType, "Found {{.number}} invalid profile(s) ! ", console.Arg{"number": len(invalidProfiles)})
for _, p := range invalidProfiles {
console.OutT(console.Empty, "\t "+p.Name)
}
console.OutT(console.Tip, "You can delete them using the following command(s): ")
for _, p := range invalidProfiles {
console.Out(fmt.Sprintf("\t $ minikube delete -p %s \n", p.Name))
}
}
if err != nil {
exit.WithCode(exit.Config, fmt.Sprintf("error loading profiles: %v", err))
}
},
}
func init() {
ProfileCmd.AddCommand(profileListCmd)
}

View File

@ -109,7 +109,7 @@ func Load() (*Config, error) {
// Loader loads the kubernetes and machine config based on the machine profile name
type Loader interface {
LoadConfigFromFile(profile string) (*Config, error)
LoadConfigFromFile(profile string, miniHome ...string) (*Config, error)
}
type simpleConfigLoader struct{}
@ -117,10 +117,10 @@ type simpleConfigLoader struct{}
// DefaultLoader is the default config loader
var DefaultLoader Loader = &simpleConfigLoader{}
func (c *simpleConfigLoader) LoadConfigFromFile(profile string) (*Config, error) {
func (c *simpleConfigLoader) LoadConfigFromFile(profile string, miniHome ...string) (*Config, error) {
var cc Config
path := constants.GetProfileFile(profile)
path := constants.GetProfileFile(profile, miniHome...)
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, err

View File

@ -0,0 +1,84 @@
/*
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 config
import (
"io/ioutil"
"path/filepath"
"k8s.io/minikube/pkg/minikube/constants"
)
// isValid checks if the profile has the essential info needed for a profile
func (p *Profile) isValid() bool {
if p.Config.MachineConfig.VMDriver == "" {
return false
}
if p.Config.KubernetesConfig.KubernetesVersion == "" {
return false
}
return true
}
// ListProfiles returns all valid and invalid (if any) minikube profiles
// invalidPs are the profiles that have a directory or config file but not usable
// invalidPs would be suggeted to be deleted
func ListProfiles(miniHome ...string) (validPs []*Profile, inValidPs []*Profile, err error) {
pDirs, err := profileDirs(miniHome...)
if err != nil {
return nil, nil, err
}
for _, n := range pDirs {
p, err := loadProfile(n, miniHome...)
if err != nil {
inValidPs = append(inValidPs, p)
continue
}
if !p.isValid() {
inValidPs = append(inValidPs, p)
continue
}
validPs = append(validPs, p)
}
return validPs, inValidPs, nil
}
// loadProfile loads type Profile based on its name
func loadProfile(name string, miniHome ...string) (*Profile, error) {
cfg, err := DefaultLoader.LoadConfigFromFile(name, miniHome...)
p := &Profile{
Name: name,
Config: cfg,
}
return p, err
}
// profileDirs gets all the folders in the user's profiles folder regardless of valid or invalid config
func profileDirs(miniHome ...string) (dirs []string, err error) {
miniPath := constants.GetMinipath()
if len(miniHome) > 0 {
miniPath = miniHome[0]
}
pRootDir := filepath.Join(miniPath, "profiles")
items, err := ioutil.ReadDir(pRootDir)
for _, f := range items {
if f.IsDir() {
dirs = append(dirs, f.Name())
}
}
return dirs, err
}

View File

@ -0,0 +1,72 @@
/*
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 config
import (
"path/filepath"
"testing"
)
func TestListProfiles(t *testing.T) {
miniDir, err := filepath.Abs("./testdata/.minikube")
if err != nil {
t.Errorf("error getting dir path for ./testdata/.minikube : %v", err)
}
// test cases for valid profiles
var testCasesValidProfs = []struct {
index int
expectName string
vmDriver string
}{
{0, "p1", "hyperkit"},
{1, "p2", "virtualbox"},
}
// test cases for invalid profiles
var testCasesInValidProfs = []struct {
index int
expectName string
vmDriver string
}{
{0, "p3_empty", ""},
{1, "p4_invalid_file", ""},
{2, "p5_partial_config", ""},
}
val, inv, err := ListProfiles(miniDir)
for _, tt := range testCasesValidProfs {
if val[tt.index].Name != tt.expectName {
t.Errorf("expected %s got %v", tt.expectName, val[tt.index].Name)
}
if val[tt.index].Config.MachineConfig.VMDriver != tt.vmDriver {
t.Errorf("expected %s got %v", tt.vmDriver, val[tt.index].Config.MachineConfig.VMDriver)
}
}
// making sure it returns the invalid profiles
for _, tt := range testCasesInValidProfs {
if inv[tt.index].Name != tt.expectName {
t.Errorf("expected %s got %v", tt.expectName, inv[tt.index].Name)
}
}
if err != nil {
t.Errorf("error listing profiles %v", err)
}
}

View File

@ -0,0 +1,50 @@
{
"MachineConfig": {
"KeepContext": false,
"MinikubeISO": "https://storage.googleapis.com/minikube/iso/minikube-v1.2.0.iso",
"Memory": 2000,
"CPUs": 2,
"DiskSize": 20000,
"VMDriver": "hyperkit",
"ContainerRuntime": "docker",
"HyperkitVpnKitSock": "",
"HyperkitVSockPorts": [],
"XhyveDiskDriver": "ahci-hd",
"DockerEnv": null,
"InsecureRegistry": null,
"RegistryMirror": null,
"HostOnlyCIDR": "192.168.99.1/24",
"HypervVirtualSwitch": "",
"KVMNetwork": "default",
"KVMQemuURI": "qemu:///system",
"KVMGPU": false,
"KVMHidden": false,
"DockerOpt": null,
"DisableDriverMounts": false,
"NFSShare": [],
"NFSSharesRoot": "/nfsshares",
"UUID": "",
"NoVTXCheck": false,
"DNSProxy": false,
"HostDNSResolver": true
},
"KubernetesConfig": {
"KubernetesVersion": "v1.15.0",
"NodeIP": "192.168.64.75",
"NodePort": 8443,
"NodeName": "minikube",
"APIServerName": "minikubeCA",
"APIServerNames": null,
"APIServerIPs": null,
"DNSDomain": "cluster.local",
"ContainerRuntime": "docker",
"CRISocket": "",
"NetworkPlugin": "",
"FeatureGates": "",
"ServiceCIDR": "10.96.0.0/12",
"ImageRepository": "",
"ExtraOptions": null,
"ShouldLoadCachedImages": true,
"EnableDefaultCNI": false
}
}

View File

@ -0,0 +1,49 @@
{
"MachineConfig": {
"KeepContext": false,
"MinikubeISO": "https://storage.googleapis.com/minikube/iso/minikube-v1.2.0.iso",
"Memory": 2000,
"CPUs": 2,
"DiskSize": 20000,
"VMDriver": "virtualbox",
"ContainerRuntime": "docker",
"HyperkitVpnKitSock": "",
"HyperkitVSockPorts": [],
"DockerEnv": null,
"InsecureRegistry": null,
"RegistryMirror": null,
"HostOnlyCIDR": "192.168.99.1/24",
"HypervVirtualSwitch": "",
"KVMNetwork": "default",
"KVMQemuURI": "qemu:///system",
"KVMGPU": false,
"KVMHidden": false,
"DockerOpt": null,
"DisableDriverMounts": false,
"NFSShare": [],
"NFSSharesRoot": "/nfsshares",
"UUID": "",
"NoVTXCheck": false,
"DNSProxy": false,
"HostDNSResolver": true
},
"KubernetesConfig": {
"KubernetesVersion": "v1.15.0",
"NodeIP": "192.168.99.136",
"NodePort": 8443,
"NodeName": "minikube",
"APIServerName": "minikubeCA",
"APIServerNames": null,
"APIServerIPs": null,
"DNSDomain": "cluster.local",
"ContainerRuntime": "docker",
"CRISocket": "",
"NetworkPlugin": "",
"FeatureGates": "",
"ServiceCIDR": "10.96.0.0/12",
"ImageRepository": "",
"ExtraOptions": null,
"ShouldLoadCachedImages": true,
"EnableDefaultCNI": false
}
}

View File

@ -0,0 +1 @@
invalid json file :)

View File

@ -0,0 +1,47 @@
{
"MachineConfig": {
"KeepContext": false,
"MinikubeISO": "https://storage.googleapis.com/minikube/iso/minikube-v1.2.0.iso",
"Memory": 2000,
"CPUs": 2,
"DiskSize": 20000,
"ContainerRuntime": "docker",
"HyperkitVpnKitSock": "",
"HyperkitVSockPorts": [],
"XhyveDiskDriver": "ahci-hd",
"DockerEnv": null,
"InsecureRegistry": null,
"RegistryMirror": null,
"HostOnlyCIDR": "192.168.99.1/24",
"HypervVirtualSwitch": "",
"KVMNetwork": "default",
"KVMQemuURI": "qemu:///system",
"KVMGPU": false,
"KVMHidden": false,
"DockerOpt": null,
"DisableDriverMounts": false,
"NFSShare": [],
"NFSSharesRoot": "/nfsshares",
"UUID": "",
"NoVTXCheck": false,
"DNSProxy": false,
"HostDNSResolver": true
},
"KubernetesConfig": {
"NodePort": 8443,
"NodeName": "minikube",
"APIServerName": "minikubeCA",
"APIServerNames": null,
"APIServerIPs": null,
"DNSDomain": "cluster.local",
"ContainerRuntime": "docker",
"CRISocket": "",
"NetworkPlugin": "",
"FeatureGates": "",
"ServiceCIDR": "10.96.0.0/12",
"ImageRepository": "",
"ExtraOptions": null,
"ShouldLoadCachedImages": true,
"EnableDefaultCNI": false
}
}

View File

@ -22,6 +22,12 @@ import (
"k8s.io/minikube/pkg/util"
)
// Profile represents a minikube profile
type Profile struct {
Name string
Config *Config
}
// Config contains machine and k8s config
type Config struct {
MachineConfig MachineConfig

View File

@ -188,13 +188,21 @@ var ConfigFilePath = MakeMiniPath("config")
var ConfigFile = MakeMiniPath("config", "config.json")
// GetProfileFile returns the Minikube profile config file
func GetProfileFile(profile string) string {
return filepath.Join(GetMinipath(), "profiles", profile, "config.json")
func GetProfileFile(profile string, miniHome ...string) string {
miniPath := GetMinipath()
if len(miniHome) > 0 {
miniPath = miniHome[0]
}
return filepath.Join(miniPath, "profiles", profile, "config.json")
}
// GetProfilePath returns the Minikube profile path of config file
func GetProfilePath(profile string) string {
return filepath.Join(GetMinipath(), "profiles", profile)
func GetProfilePath(profile string, miniHome ...string) string {
miniPath := GetMinipath()
if len(miniHome) > 0 {
miniPath = miniHome[0]
}
return filepath.Join(miniPath, "profiles", profile)
}
// AddonsPath is the default path of the addons configuration

View File

@ -86,6 +86,6 @@ type stubConfigLoader struct {
e error
}
func (l *stubConfigLoader) LoadConfigFromFile(profile string) (*config.Config, error) {
func (l *stubConfigLoader) LoadConfigFromFile(profile string, miniHome ...string) (*config.Config, error) {
return l.c, l.e
}

View File

@ -32,7 +32,7 @@ func TestFunctional(t *testing.T) {
// This one is not parallel, and ensures the cluster comes up
// before we run any other tests.
t.Run("Status", testClusterStatus)
t.Run("ProfileList", testProfileList)
t.Run("DNS", testClusterDNS)
t.Run("Logs", testClusterLogs)
t.Run("Addons", testAddons)

View File

@ -0,0 +1,35 @@
// +build integration
/*
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 integration
import (
"strings"
"testing"
)
// testProfileList tests the `minikube profile list` command
func testProfileList(t *testing.T) {
t.Parallel()
profile := "minikube"
mk := NewMinikubeRunner(t, "--wait=false")
out := mk.RunCommand("profile list", true)
if !strings.Contains(out, profile) {
t.Errorf("Error , failed to read profile name (%s) in `profile list` command output : \n %q ", profile, out)
}
}