mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Adds support to a tree hierarchy of kubectl plugins
This commit is contained in:
		@@ -3769,6 +3769,21 @@ __EOF__
 | 
			
		||||
  output_message=$(! KUBECTL_PLUGINS_PATH=test/fixtures/pkg/kubectl/plugins/ kubectl plugin error 2>&1)
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'error: exit status 1'
 | 
			
		||||
 | 
			
		||||
  # plugin tree
 | 
			
		||||
  output_message=$(KUBECTL_PLUGINS_PATH=test/fixtures/pkg/kubectl/plugins kubectl plugin tree 2>&1)
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'Plugin with a tree of commands'
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'child1\s\+The first child of a tree'
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'child2\s\+The second child of a tree'
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'child3\s\+The third child of a tree'
 | 
			
		||||
  output_message=$(KUBECTL_PLUGINS_PATH=test/fixtures/pkg/kubectl/plugins kubectl plugin tree child1 --help 2>&1)
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'The first child of a tree'
 | 
			
		||||
  kube::test::if_has_not_string "${output_message}" 'The second child'
 | 
			
		||||
  kube::test::if_has_not_string "${output_message}" 'child2'
 | 
			
		||||
  output_message=$(KUBECTL_PLUGINS_PATH=test/fixtures/pkg/kubectl/plugins kubectl plugin tree child1 2>&1)
 | 
			
		||||
  kube::test::if_has_string "${output_message}" 'child one'
 | 
			
		||||
  kube::test::if_has_not_string "${output_message}" 'child1'
 | 
			
		||||
  kube::test::if_has_not_string "${output_message}" 'The first child'
 | 
			
		||||
 | 
			
		||||
  #################
 | 
			
		||||
  # Impersonation #
 | 
			
		||||
  #################
 | 
			
		||||
 
 | 
			
		||||
@@ -88,8 +88,15 @@ func (l *DirectoryPluginLoader) Load() (Plugins, error) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		plugin.Dir = filepath.Dir(path)
 | 
			
		||||
		plugin.DescriptorName = fileInfo.Name()
 | 
			
		||||
		var setSource func(path string, fileInfo os.FileInfo, p *Plugin)
 | 
			
		||||
		setSource = func(path string, fileInfo os.FileInfo, p *Plugin) {
 | 
			
		||||
			p.Dir = filepath.Dir(path)
 | 
			
		||||
			p.DescriptorName = fileInfo.Name()
 | 
			
		||||
			for _, child := range p.Tree {
 | 
			
		||||
				setSource(path, fileInfo, child)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		setSource(path, fileInfo, plugin)
 | 
			
		||||
 | 
			
		||||
		glog.V(6).Infof("Plugin loaded: %s", plugin.Name)
 | 
			
		||||
		list = append(list, plugin)
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSuccessfulDirectoryPluginLoader(t *testing.T) {
 | 
			
		||||
	tmp, err := setupValidPlugins(3)
 | 
			
		||||
	tmp, err := setupValidPlugins(3, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -55,6 +55,9 @@ func TestSuccessfulDirectoryPluginLoader(t *testing.T) {
 | 
			
		||||
		if m, _ := regexp.MatchString("^echo plugin[123]$", plugin.Command); !m {
 | 
			
		||||
			t.Errorf("Unexpected plugin command %s", plugin.Command)
 | 
			
		||||
		}
 | 
			
		||||
		if count := len(plugin.Tree); count != 0 {
 | 
			
		||||
			t.Errorf("Unexpected number of loaded child plugins, wanted 0, got %d", count)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -107,7 +110,7 @@ func TestUnexistentDirectoryPluginLoader(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPluginsEnvVarPluginLoader(t *testing.T) {
 | 
			
		||||
	tmp, err := setupValidPlugins(1)
 | 
			
		||||
	tmp, err := setupValidPlugins(1, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -172,19 +175,78 @@ shortDesc: The incomplete test plugin`
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupValidPlugins(count int) (string, error) {
 | 
			
		||||
func TestDirectoryTreePluginLoader(t *testing.T) {
 | 
			
		||||
	tmp, err := setupValidPlugins(1, 2)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.RemoveAll(tmp)
 | 
			
		||||
 | 
			
		||||
	loader := &DirectoryPluginLoader{
 | 
			
		||||
		Directory: tmp,
 | 
			
		||||
	}
 | 
			
		||||
	plugins, err := loader.Load()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Unexpected error loading plugins: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if count := len(plugins); count != 1 {
 | 
			
		||||
		t.Errorf("Unexpected number of loaded plugins, wanted 1, got %d", count)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, plugin := range plugins {
 | 
			
		||||
		if m, _ := regexp.MatchString("^plugin1$", plugin.Name); !m {
 | 
			
		||||
			t.Errorf("Unexpected plugin name %s", plugin.Name)
 | 
			
		||||
		}
 | 
			
		||||
		if m, _ := regexp.MatchString("^The plugin1 test plugin$", plugin.ShortDesc); !m {
 | 
			
		||||
			t.Errorf("Unexpected plugin short desc %s", plugin.ShortDesc)
 | 
			
		||||
		}
 | 
			
		||||
		if m, _ := regexp.MatchString("^echo plugin1$", plugin.Command); !m {
 | 
			
		||||
			t.Errorf("Unexpected plugin command %s", plugin.Command)
 | 
			
		||||
		}
 | 
			
		||||
		if count := len(plugin.Tree); count != 2 {
 | 
			
		||||
			t.Errorf("Unexpected number of loaded child plugins, wanted 2, got %d", count)
 | 
			
		||||
		}
 | 
			
		||||
		for _, child := range plugin.Tree {
 | 
			
		||||
			if m, _ := regexp.MatchString("^child[12]$", child.Name); !m {
 | 
			
		||||
				t.Errorf("Unexpected plugin child name %s", child.Name)
 | 
			
		||||
			}
 | 
			
		||||
			if m, _ := regexp.MatchString("^The child[12] test plugin child of plugin1 of House Targaryen$", child.ShortDesc); !m {
 | 
			
		||||
				t.Errorf("Unexpected plugin child short desc %s", child.ShortDesc)
 | 
			
		||||
			}
 | 
			
		||||
			if m, _ := regexp.MatchString("^echo child[12]$", child.Command); !m {
 | 
			
		||||
				t.Errorf("Unexpected plugin child command %s", child.Command)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupValidPlugins(nPlugins, nChildren int) (string, error) {
 | 
			
		||||
	tmp, err := ioutil.TempDir("", "")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("unexpected ioutil.TempDir error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 1; i <= count; i++ {
 | 
			
		||||
	for i := 1; i <= nPlugins; i++ {
 | 
			
		||||
		name := fmt.Sprintf("plugin%d", i)
 | 
			
		||||
		descriptor := fmt.Sprintf(`
 | 
			
		||||
name: %[1]s
 | 
			
		||||
shortDesc: The %[1]s test plugin
 | 
			
		||||
command: echo %[1]s`, name)
 | 
			
		||||
 | 
			
		||||
		if nChildren > 0 {
 | 
			
		||||
			descriptor += `
 | 
			
		||||
tree:`
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for j := 1; j <= nChildren; j++ {
 | 
			
		||||
			child := fmt.Sprintf("child%d", i)
 | 
			
		||||
			descriptor += fmt.Sprintf(`
 | 
			
		||||
  - name: %[1]s
 | 
			
		||||
    shortDesc: The %[1]s test plugin child of %[2]s of House Targaryen
 | 
			
		||||
    command: echo %[1]s`, child, name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := os.Mkdir(filepath.Join(tmp, name), 0755); err != nil {
 | 
			
		||||
			return "", fmt.Errorf("unexpected os.Mkdir error: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,10 @@ limitations under the License.
 | 
			
		||||
 | 
			
		||||
package plugins
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Plugin is the representation of a CLI extension (plugin).
 | 
			
		||||
type Plugin struct {
 | 
			
		||||
@@ -28,11 +31,12 @@ type Plugin struct {
 | 
			
		||||
// PluginDescription holds everything needed to register a
 | 
			
		||||
// plugin as a command. Usually comes from a descriptor file.
 | 
			
		||||
type Description struct {
 | 
			
		||||
	Name      string `json:"name"`
 | 
			
		||||
	ShortDesc string `json:"shortDesc"`
 | 
			
		||||
	LongDesc  string `json:"longDesc,omitempty"`
 | 
			
		||||
	Example   string `json:"example,omitempty"`
 | 
			
		||||
	Command   string `json:"command"`
 | 
			
		||||
	Name      string    `json:"name"`
 | 
			
		||||
	ShortDesc string    `json:"shortDesc"`
 | 
			
		||||
	LongDesc  string    `json:"longDesc,omitempty"`
 | 
			
		||||
	Example   string    `json:"example,omitempty"`
 | 
			
		||||
	Command   string    `json:"command"`
 | 
			
		||||
	Tree      []*Plugin `json:"tree,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PluginSource holds the location of a given plugin in the filesystem.
 | 
			
		||||
@@ -41,12 +45,23 @@ type Source struct {
 | 
			
		||||
	DescriptorName string `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var IncompleteError = fmt.Errorf("incomplete plugin descriptor: name, shortDesc and command fields are required")
 | 
			
		||||
var (
 | 
			
		||||
	IncompleteError  = fmt.Errorf("incomplete plugin descriptor: name, shortDesc and command fields are required")
 | 
			
		||||
	InvalidNameError = fmt.Errorf("plugin name can't contain spaces")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (p Plugin) Validate() error {
 | 
			
		||||
	if len(p.Name) == 0 || len(p.ShortDesc) == 0 || len(p.Command) == 0 {
 | 
			
		||||
	if len(p.Name) == 0 || len(p.ShortDesc) == 0 || (len(p.Command) == 0 && len(p.Tree) == 0) {
 | 
			
		||||
		return IncompleteError
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Index(p.Name, " ") > -1 {
 | 
			
		||||
		return InvalidNameError
 | 
			
		||||
	}
 | 
			
		||||
	for _, child := range p.Tree {
 | 
			
		||||
		if err := child.Validate(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								test/fixtures/pkg/kubectl/plugins/tree/plugin.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								test/fixtures/pkg/kubectl/plugins/tree/plugin.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
name: "tree"
 | 
			
		||||
shortDesc: "Plugin with a tree of commands"
 | 
			
		||||
tree:
 | 
			
		||||
  - name: "child1"
 | 
			
		||||
    shortDesc: "The first child of a tree"
 | 
			
		||||
    command: echo child1
 | 
			
		||||
  - name: "child2"
 | 
			
		||||
    shortDesc: "The second child of a tree"
 | 
			
		||||
    command: echo child2
 | 
			
		||||
  - name: "child3"
 | 
			
		||||
    shortDesc: "The third child of a tree"
 | 
			
		||||
    command: echo child3
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user