vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

50
vendor/k8s.io/kubernetes/pkg/kubectl/plugins/BUILD generated vendored Normal file
View File

@ -0,0 +1,50 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"env.go",
"loader.go",
"plugins.go",
"runner.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/plugins",
deps = [
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = [
"env_test.go",
"loader_test.go",
"plugins_test.go",
"runner_test.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/plugins",
library = ":go_default_library",
deps = ["//vendor/github.com/spf13/pflag:go_default_library"],
)

161
vendor/k8s.io/kubernetes/pkg/kubectl/plugins/env.go generated vendored Normal file
View File

@ -0,0 +1,161 @@
/*
Copyright 2017 The Kubernetes Authors.
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 plugins
import (
"fmt"
"os"
"strings"
"github.com/spf13/pflag"
)
// Env represents an environment variable with its name and value.
type Env struct {
N string
V string
}
// String returns "name=value" string.
func (e Env) String() string {
return fmt.Sprintf("%s=%s", e.N, e.V)
}
// EnvList is a list of Env.
type EnvList []Env
// Slice returns a slice of "name=value" strings.
func (e EnvList) Slice() []string {
envs := []string{}
for _, env := range e {
envs = append(envs, env.String())
}
return envs
}
// Merge converts "name=value" strings into Env values and merges them into e.
func (e EnvList) Merge(s ...string) EnvList {
newList := e
newList = append(newList, fromSlice(s)...)
return newList
}
// EnvProvider provides the environment in which the plugin will run.
type EnvProvider interface {
// Env returns the env list.
Env() (EnvList, error)
}
// MultiEnvProvider satisfies the EnvProvider interface for multiple env providers.
type MultiEnvProvider []EnvProvider
// Env returns the combined env list of multiple env providers, returns on first error.
func (p MultiEnvProvider) Env() (EnvList, error) {
env := EnvList{}
for _, provider := range p {
pEnv, err := provider.Env()
if err != nil {
return EnvList{}, err
}
env = append(env, pEnv...)
}
return env, nil
}
// PluginCallerEnvProvider satisfies the EnvProvider interface.
type PluginCallerEnvProvider struct{}
// Env returns env with the path to the caller binary (usually full path to 'kubectl').
func (p *PluginCallerEnvProvider) Env() (EnvList, error) {
caller, err := os.Executable()
if err != nil {
return EnvList{}, err
}
return EnvList{
{"KUBECTL_PLUGINS_CALLER", caller},
}, nil
}
// PluginDescriptorEnvProvider satisfies the EnvProvider interface.
type PluginDescriptorEnvProvider struct {
Plugin *Plugin
}
// Env returns env with information about the running plugin.
func (p *PluginDescriptorEnvProvider) Env() (EnvList, error) {
if p.Plugin == nil {
return []Env{}, fmt.Errorf("plugin not present to extract env")
}
prefix := "KUBECTL_PLUGINS_DESCRIPTOR_"
env := EnvList{
{prefix + "NAME", p.Plugin.Name},
{prefix + "SHORT_DESC", p.Plugin.ShortDesc},
{prefix + "LONG_DESC", p.Plugin.LongDesc},
{prefix + "EXAMPLE", p.Plugin.Example},
{prefix + "COMMAND", p.Plugin.Command},
}
return env, nil
}
// OSEnvProvider satisfies the EnvProvider interface.
type OSEnvProvider struct{}
// Env returns the current environment from the operating system.
func (p *OSEnvProvider) Env() (EnvList, error) {
return fromSlice(os.Environ()), nil
}
// EmptyEnvProvider satisfies the EnvProvider interface.
type EmptyEnvProvider struct{}
// Env returns an empty environment.
func (p *EmptyEnvProvider) Env() (EnvList, error) {
return EnvList{}, nil
}
// FlagToEnvName converts a flag string into a UNIX like environment variable name.
// e.g --some-flag => "PREFIX_SOME_FLAG"
func FlagToEnvName(flagName, prefix string) string {
envName := strings.TrimPrefix(flagName, "--")
envName = strings.ToUpper(envName)
envName = strings.Replace(envName, "-", "_", -1)
envName = prefix + envName
return envName
}
// FlagToEnv converts a flag and its value into an Env.
// e.g --some-flag some-value => Env{N: "PREFIX_SOME_FLAG", V="SOME_VALUE"}
func FlagToEnv(flag *pflag.Flag, prefix string) Env {
envName := FlagToEnvName(flag.Name, prefix)
return Env{envName, flag.Value.String()}
}
func fromSlice(envs []string) EnvList {
list := EnvList{}
for _, env := range envs {
list = append(list, parseEnv(env))
}
return list
}
func parseEnv(env string) Env {
if !strings.Contains(env, "=") {
env = env + "="
}
parsed := strings.SplitN(env, "=", 2)
return Env{parsed[0], parsed[1]}
}

View File

@ -0,0 +1,162 @@
/*
Copyright 2017 The Kubernetes Authors.
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 plugins
import (
"reflect"
"testing"
"github.com/spf13/pflag"
)
func TestEnv(t *testing.T) {
tests := []struct {
env Env
expected string
}{
{
env: Env{"FOO", "BAR"},
expected: "FOO=BAR",
},
{
env: Env{"FOO", "BAR="},
expected: "FOO=BAR=",
},
{
env: Env{"FOO", ""},
expected: "FOO=",
},
}
for _, test := range tests {
if s := test.env.String(); s != test.expected {
t.Errorf("%v: expected string %q, got %q", test.env, test.expected, s)
}
}
}
func TestEnvListToSlice(t *testing.T) {
tests := []struct {
env EnvList
expected []string
}{
{
env: EnvList{
{"FOO", "BAR"},
{"ZEE", "YO"},
{"ONE", "1"},
{"EQUALS", "=="},
{"EMPTY", ""},
},
expected: []string{"FOO=BAR", "ZEE=YO", "ONE=1", "EQUALS===", "EMPTY="},
},
}
for _, test := range tests {
if s := test.env.Slice(); !reflect.DeepEqual(test.expected, s) {
t.Errorf("%v: expected %v, got %v", test.env, test.expected, s)
}
}
}
func TestAddToEnvList(t *testing.T) {
tests := []struct {
add []string
expected EnvList
}{
{
add: []string{"FOO=BAR", "EMPTY=", "EQUALS===", "JUSTNAME"},
expected: EnvList{
{"FOO", "BAR"},
{"EMPTY", ""},
{"EQUALS", "=="},
{"JUSTNAME", ""},
},
},
}
for _, test := range tests {
env := EnvList{}.Merge(test.add...)
if !reflect.DeepEqual(test.expected, env) {
t.Errorf("%v: expected %v, got %v", test.add, test.expected, env)
}
}
}
func TestFlagToEnv(t *testing.T) {
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
flags.String("test", "ok", "")
flags.String("kube-master", "http://something", "")
flags.String("from-file", "default", "")
flags.Parse([]string{"--from-file=nondefault"})
tests := []struct {
flag *pflag.Flag
prefix string
expected Env
}{
{
flag: flags.Lookup("test"),
expected: Env{"TEST", "ok"},
},
{
flag: flags.Lookup("kube-master"),
expected: Env{"KUBE_MASTER", "http://something"},
},
{
prefix: "KUBECTL_",
flag: flags.Lookup("from-file"),
expected: Env{"KUBECTL_FROM_FILE", "nondefault"},
},
}
for _, test := range tests {
if env := FlagToEnv(test.flag, test.prefix); !reflect.DeepEqual(test.expected, env) {
t.Errorf("%v: expected %v, got %v", test.flag.Name, test.expected, env)
}
}
}
func TestPluginDescriptorEnvProvider(t *testing.T) {
tests := []struct {
plugin *Plugin
expected EnvList
}{
{
plugin: &Plugin{
Description: Description{
Name: "test",
ShortDesc: "Short Description",
Command: "foo --bar",
},
},
expected: EnvList{
{"KUBECTL_PLUGINS_DESCRIPTOR_NAME", "test"},
{"KUBECTL_PLUGINS_DESCRIPTOR_SHORT_DESC", "Short Description"},
{"KUBECTL_PLUGINS_DESCRIPTOR_LONG_DESC", ""},
{"KUBECTL_PLUGINS_DESCRIPTOR_EXAMPLE", ""},
{"KUBECTL_PLUGINS_DESCRIPTOR_COMMAND", "foo --bar"},
},
},
}
for _, test := range tests {
provider := &PluginDescriptorEnvProvider{
Plugin: test.plugin,
}
env, _ := provider.Env()
if !reflect.DeepEqual(test.expected, env) {
t.Errorf("%v: expected %v, got %v", test.plugin.Name, test.expected, env)
}
}
}

View File

@ -0,0 +1,70 @@
#!/usr/bin/env ruby
require 'json'
require 'date'
class Numeric
def duration
secs = self.to_int
mins = secs / 60
hours = mins / 60
days = hours / 24
if days > 0
"#{days} days and #{hours % 24} hours"
elsif hours > 0
"#{hours} hours and #{mins % 60} minutes"
elsif mins > 0
"#{mins} minutes and #{secs % 60} seconds"
elsif secs >= 0
"#{secs} seconds"
end
end
end
namespace = ENV['KUBECTL_PLUGINS_CURRENT_NAMESPACE'] || 'default'
pods_json = `kubectl --namespace #{namespace} get pods -o json`
pods_parsed = JSON.parse(pods_json)
puts "The Magnificent Aging Plugin."
data = Hash.new
max_name_length = 0
max_age = 0
min_age = 0
pods_parsed['items'].each { |pod|
name = pod['metadata']['name']
creation = pod['metadata']['creationTimestamp']
age = Time.now - DateTime.parse(creation).to_time
data[name] = age
if name.length > max_name_length
max_name_length = name.length
end
if age > max_age
max_age = age
end
if age < min_age
min_age = age
end
}
data = data.sort_by{ |name, age| age }
if data.length > 0
puts ""
data.each { |name, age|
output = ""
output += name.rjust(max_name_length, ' ') + ": "
bar_size = (age*80/max_age).ceil
bar_size.times{ output += "" }
output += " " + age.duration
puts output
puts ""
}
else
puts "No pods"
end

View File

@ -0,0 +1,5 @@
name: "aging"
shortDesc: "Aging shows pods by age"
longDesc: >
Aging shows pods from the current namespace by age.
command: ./aging.rb

View File

@ -0,0 +1,3 @@
name: "hello"
shortDesc: "I say hello!"
command: "echo Hello plugins!"

204
vendor/k8s.io/kubernetes/pkg/kubectl/plugins/loader.go generated vendored Normal file
View File

@ -0,0 +1,204 @@
/*
Copyright 2017 The Kubernetes Authors.
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 plugins
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/ghodss/yaml"
"github.com/golang/glog"
"k8s.io/client-go/tools/clientcmd"
)
// PluginDescriptorFilename is the default file name for plugin descriptions.
const PluginDescriptorFilename = "plugin.yaml"
// PluginLoader is capable of loading a list of plugin descriptions.
type PluginLoader interface {
// Load loads the plugin descriptions.
Load() (Plugins, error)
}
// DirectoryPluginLoader is a PluginLoader that loads plugin descriptions
// from a given directory in the filesystem. Plugins are located in subdirs
// under the loader "root", where each subdir must contain, at least, a plugin
// descriptor file called "plugin.yaml" that translates into a PluginDescription.
type DirectoryPluginLoader struct {
Directory string
}
// Load reads the directory the loader holds and loads plugin descriptions.
func (l *DirectoryPluginLoader) Load() (Plugins, error) {
if len(l.Directory) == 0 {
return nil, fmt.Errorf("directory not specified")
}
list := Plugins{}
stat, err := os.Stat(l.Directory)
if err != nil {
return nil, err
}
if !stat.IsDir() {
return nil, fmt.Errorf("not a directory: %s", l.Directory)
}
base, err := filepath.Abs(l.Directory)
if err != nil {
return nil, err
}
// read the base directory tree searching for plugin descriptors
// fails silently (descriptors unable to be read or unmarshalled are logged but skipped)
err = filepath.Walk(base, func(path string, fileInfo os.FileInfo, walkErr error) error {
if walkErr != nil || fileInfo.IsDir() || fileInfo.Name() != PluginDescriptorFilename {
return nil
}
file, err := ioutil.ReadFile(path)
if err != nil {
glog.V(1).Infof("Unable to read plugin descriptor %s: %v", path, err)
return nil
}
plugin := &Plugin{}
if err := yaml.Unmarshal(file, plugin); err != nil {
glog.V(1).Infof("Unable to unmarshal plugin descriptor %s: %v", path, err)
return nil
}
if err := plugin.Validate(); err != nil {
glog.V(1).Infof("%v", err)
return nil
}
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)
return nil
})
return list, err
}
// UserDirPluginLoader returns a PluginLoader that loads plugins from the
// "plugins" directory under the user's kubeconfig dir (usually "~/.kube/plugins/").
func UserDirPluginLoader() PluginLoader {
dir := filepath.Join(clientcmd.RecommendedConfigDir, "plugins")
return &DirectoryPluginLoader{
Directory: dir,
}
}
// PathFromEnvVarPluginLoader returns a PluginLoader that loads plugins from one or more
// directories specified by the provided env var name. In case the env var is not
// set, the PluginLoader just loads nothing. A list of subdirectories can be provided,
// which will be appended to each path specified by the env var.
func PathFromEnvVarPluginLoader(envVarName string, subdirs ...string) PluginLoader {
env := os.Getenv(envVarName)
if len(env) == 0 {
return &DummyPluginLoader{}
}
loader := MultiPluginLoader{}
for _, path := range filepath.SplitList(env) {
dir := append([]string{path}, subdirs...)
loader = append(loader, &DirectoryPluginLoader{
Directory: filepath.Join(dir...),
})
}
return loader
}
// KubectlPluginsPathPluginLoader returns a PluginLoader that loads plugins from one or more
// directories specified by the KUBECTL_PLUGINS_PATH env var.
func KubectlPluginsPathPluginLoader() PluginLoader {
return PathFromEnvVarPluginLoader("KUBECTL_PLUGINS_PATH")
}
// XDGDataDirsPluginLoader returns a PluginLoader that loads plugins from one or more
// directories specified by the XDG system directory structure spec in the
// XDG_DATA_DIRS env var, plus the "kubectl/plugins/" suffix. According to the
// spec, if XDG_DATA_DIRS is not set it defaults to "/usr/local/share:/usr/share".
func XDGDataDirsPluginLoader() PluginLoader {
envVarName := "XDG_DATA_DIRS"
if len(os.Getenv(envVarName)) > 0 {
return PathFromEnvVarPluginLoader(envVarName, "kubectl", "plugins")
}
return TolerantMultiPluginLoader{
&DirectoryPluginLoader{
Directory: "/usr/local/share/kubectl/plugins",
},
&DirectoryPluginLoader{
Directory: "/usr/share/kubectl/plugins",
},
}
}
// MultiPluginLoader is a PluginLoader that can encapsulate multiple plugin loaders,
// a successful loading means every encapsulated loader was able to load without errors.
type MultiPluginLoader []PluginLoader
// Load calls Load() for each of the encapsulated Loaders.
func (l MultiPluginLoader) Load() (Plugins, error) {
plugins := Plugins{}
for _, loader := range l {
loaded, err := loader.Load()
if err != nil {
return nil, err
}
plugins = append(plugins, loaded...)
}
return plugins, nil
}
// TolerantMultiPluginLoader is a PluginLoader than encapsulates multiple plugins loaders,
// but is tolerant to errors while loading from them.
type TolerantMultiPluginLoader []PluginLoader
// Load calls Load() for each of the encapsulated Loaders.
func (l TolerantMultiPluginLoader) Load() (Plugins, error) {
plugins := Plugins{}
for _, loader := range l {
loaded, _ := loader.Load()
if loaded != nil {
plugins = append(plugins, loaded...)
}
}
return plugins, nil
}
// DummyPluginLoader is a noop PluginLoader.
type DummyPluginLoader struct{}
// Load loads nothing.
func (l *DummyPluginLoader) Load() (Plugins, error) {
return Plugins{}, nil
}

View File

@ -0,0 +1,262 @@
/*
Copyright 2017 The Kubernetes Authors.
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 plugins
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)
func TestSuccessfulDirectoryPluginLoader(t *testing.T) {
tmp, err := setupValidPlugins(3, 0)
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 != 3 {
t.Errorf("Unexpected number of loaded plugins, wanted 3, got %d", count)
}
for _, plugin := range plugins {
if m, _ := regexp.MatchString("^plugin[123]$", plugin.Name); !m {
t.Errorf("Unexpected plugin name %s", plugin.Name)
}
if m, _ := regexp.MatchString("^The plugin[123] test plugin$", plugin.ShortDesc); !m {
t.Errorf("Unexpected plugin short desc %s", plugin.ShortDesc)
}
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)
}
}
}
func TestEmptyDirectoryPluginLoader(t *testing.T) {
loader := &DirectoryPluginLoader{}
_, err := loader.Load()
if err == nil {
t.Errorf("Expected error, got none")
}
if m, _ := regexp.MatchString("^directory not specified$", err.Error()); !m {
t.Errorf("Unexpected error %v", err)
}
}
func TestNotDirectoryPluginLoader(t *testing.T) {
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unexpected ioutil.TempDir error: %v", err)
}
defer os.RemoveAll(tmp)
file := filepath.Join(tmp, "test.tmp")
if err := ioutil.WriteFile(file, []byte("test"), 644); err != nil {
t.Fatalf("unexpected ioutil.WriteFile error: %v", err)
}
loader := &DirectoryPluginLoader{
Directory: file,
}
_, err = loader.Load()
if err == nil {
t.Errorf("Expected error, got none")
}
if !strings.Contains(err.Error(), "not a directory") {
t.Errorf("Unexpected error %v", err)
}
}
func TestUnexistentDirectoryPluginLoader(t *testing.T) {
loader := &DirectoryPluginLoader{
Directory: "/hopefully-does-not-exist",
}
_, err := loader.Load()
if err == nil {
t.Errorf("Expected error, got none")
}
if !strings.Contains(err.Error(), "no such file or directory") {
t.Errorf("Unexpected error %v", err)
}
}
func TestKubectlPluginsPathPluginLoader(t *testing.T) {
tmp, err := setupValidPlugins(1, 0)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.RemoveAll(tmp)
env := "KUBECTL_PLUGINS_PATH"
os.Setenv(env, tmp)
defer os.Unsetenv(env)
loader := KubectlPluginsPathPluginLoader()
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)
}
plugin := plugins[0]
if "plugin1" != plugin.Name {
t.Errorf("Unexpected plugin name %s", plugin.Name)
}
if "The plugin1 test plugin" != plugin.ShortDesc {
t.Errorf("Unexpected plugin short desc %s", plugin.ShortDesc)
}
if "echo plugin1" != plugin.Command {
t.Errorf("Unexpected plugin command %s", plugin.Command)
}
}
func TestIncompletePluginDescriptor(t *testing.T) {
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("unexpected ioutil.TempDir error: %v", err)
}
descriptor := `
name: incomplete
shortDesc: The incomplete test plugin`
if err := os.Mkdir(filepath.Join(tmp, "incomplete"), 0755); err != nil {
t.Fatalf("unexpected os.Mkdir error: %v", err)
}
if err := ioutil.WriteFile(filepath.Join(tmp, "incomplete", "plugin.yaml"), []byte(descriptor), 0644); err != nil {
t.Fatalf("unexpected ioutil.WriteFile error: %v", err)
}
defer os.RemoveAll(tmp)
loader := &DirectoryPluginLoader{
Directory: tmp,
}
plugins, err := loader.Load()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if count := len(plugins); count != 0 {
t.Errorf("Unexpected number of loaded plugins, wanted 0, got %d", count)
}
}
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 <= nPlugins; i++ {
name := fmt.Sprintf("plugin%d", i)
descriptor := fmt.Sprintf(`
name: %[1]s
shortDesc: The %[1]s test plugin
command: echo %[1]s
flags:
- name: %[1]s-flag
desc: A flag for %[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)
}
if err := ioutil.WriteFile(filepath.Join(tmp, name, "plugin.yaml"), []byte(descriptor), 0644); err != nil {
return "", fmt.Errorf("unexpected ioutil.WriteFile error: %v", err)
}
}
return tmp, nil
}

123
vendor/k8s.io/kubernetes/pkg/kubectl/plugins/plugins.go generated vendored Normal file
View File

@ -0,0 +1,123 @@
/*
Copyright 2017 The Kubernetes Authors.
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 plugins
import (
"fmt"
"strings"
"unicode"
)
var (
// ErrIncompletePlugin indicates plugin is incomplete.
ErrIncompletePlugin = fmt.Errorf("incomplete plugin descriptor: name, shortDesc and command fields are required")
// ErrInvalidPluginName indicates plugin name is invalid.
ErrInvalidPluginName = fmt.Errorf("plugin name can't contain spaces")
// ErrIncompleteFlag indicates flag is incomplete.
ErrIncompleteFlag = fmt.Errorf("incomplete flag descriptor: name and desc fields are required")
// ErrInvalidFlagName indicates flag name is invalid.
ErrInvalidFlagName = fmt.Errorf("flag name can't contain spaces")
// ErrInvalidFlagShorthand indicates flag shorthand is invalid.
ErrInvalidFlagShorthand = fmt.Errorf("flag shorthand must be only one letter")
)
// Plugin is the representation of a CLI extension (plugin).
type Plugin struct {
Description
Source
Context RunningContext `json:"-"`
}
// Description 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"`
Flags []Flag `json:"flags,omitempty"`
Tree Plugins `json:"tree,omitempty"`
}
// Source holds the location of a given plugin in the filesystem.
type Source struct {
Dir string `json:"-"`
DescriptorName string `json:"-"`
}
// Validate validates plugin data.
func (p Plugin) Validate() error {
if len(p.Name) == 0 || len(p.ShortDesc) == 0 || (len(p.Command) == 0 && len(p.Tree) == 0) {
return ErrIncompletePlugin
}
if strings.Index(p.Name, " ") > -1 {
return ErrInvalidPluginName
}
for _, flag := range p.Flags {
if err := flag.Validate(); err != nil {
return err
}
}
for _, child := range p.Tree {
if err := child.Validate(); err != nil {
return err
}
}
return nil
}
// IsValid returns true if plugin data is valid.
func (p Plugin) IsValid() bool {
return p.Validate() == nil
}
// Plugins is a list of plugins.
type Plugins []*Plugin
// Flag describes a single flag supported by a given plugin.
type Flag struct {
Name string `json:"name"`
Shorthand string `json:"shorthand,omitempty"`
Desc string `json:"desc"`
DefValue string `json:"defValue,omitempty"`
}
// Validate validates flag data.
func (f Flag) Validate() error {
if len(f.Name) == 0 || len(f.Desc) == 0 {
return ErrIncompleteFlag
}
if strings.Index(f.Name, " ") > -1 {
return ErrInvalidFlagName
}
return f.ValidateShorthand()
}
// ValidateShorthand validates flag shorthand data.
func (f Flag) ValidateShorthand() error {
length := len(f.Shorthand)
if length == 0 || (length == 1 && unicode.IsLetter(rune(f.Shorthand[0]))) {
return nil
}
return ErrInvalidFlagShorthand
}
// Shorthanded returns true if flag shorthand data is valid.
func (f Flag) Shorthanded() bool {
return f.ValidateShorthand() == nil
}

View File

@ -0,0 +1,169 @@
/*
Copyright 2017 The Kubernetes Authors.
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 plugins
import "testing"
func TestPlugin(t *testing.T) {
tests := []struct {
plugin *Plugin
expectedErr error
}{
{
plugin: &Plugin{
Description: Description{
Name: "test",
ShortDesc: "The test",
Command: "echo 1",
},
},
},
{
plugin: &Plugin{
Description: Description{
Name: "test",
ShortDesc: "The test",
},
},
expectedErr: ErrIncompletePlugin,
},
{
plugin: &Plugin{},
expectedErr: ErrIncompletePlugin,
},
{
plugin: &Plugin{
Description: Description{
Name: "test spaces",
ShortDesc: "The test",
Command: "echo 1",
},
},
expectedErr: ErrInvalidPluginName,
},
{
plugin: &Plugin{
Description: Description{
Name: "test",
ShortDesc: "The test",
Command: "echo 1",
Flags: []Flag{
{
Name: "aflag",
},
},
},
},
expectedErr: ErrIncompleteFlag,
},
{
plugin: &Plugin{
Description: Description{
Name: "test",
ShortDesc: "The test",
Command: "echo 1",
Flags: []Flag{
{
Name: "a flag",
Desc: "Invalid flag",
},
},
},
},
expectedErr: ErrInvalidFlagName,
},
{
plugin: &Plugin{
Description: Description{
Name: "test",
ShortDesc: "The test",
Command: "echo 1",
Flags: []Flag{
{
Name: "aflag",
Desc: "Invalid shorthand",
Shorthand: "aa",
},
},
},
},
expectedErr: ErrInvalidFlagShorthand,
},
{
plugin: &Plugin{
Description: Description{
Name: "test",
ShortDesc: "The test",
Command: "echo 1",
Flags: []Flag{
{
Name: "aflag",
Desc: "Invalid shorthand",
Shorthand: "2",
},
},
},
},
expectedErr: ErrInvalidFlagShorthand,
},
{
plugin: &Plugin{
Description: Description{
Name: "test",
ShortDesc: "The test",
Command: "echo 1",
Flags: []Flag{
{
Name: "aflag",
Desc: "A flag",
Shorthand: "a",
},
},
Tree: Plugins{
&Plugin{
Description: Description{
Name: "child",
ShortDesc: "The child",
LongDesc: "The child long desc",
Example: "You can use it like this but you're not supposed to",
Command: "echo 1",
Flags: []Flag{
{
Name: "childflag",
Desc: "A child flag",
},
{
Name: "childshorthand",
Desc: "A child shorthand flag",
Shorthand: "s",
},
},
},
},
},
},
},
},
}
for _, test := range tests {
err := test.plugin.Validate()
if err != test.expectedErr {
t.Errorf("%s: expected error %v, got %v", test.plugin.Name, test.expectedErr, err)
}
}
}

74
vendor/k8s.io/kubernetes/pkg/kubectl/plugins/runner.go generated vendored Normal file
View File

@ -0,0 +1,74 @@
/*
Copyright 2017 The Kubernetes Authors.
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 plugins
import (
"io"
"os"
"os/exec"
"strings"
"github.com/golang/glog"
)
// PluginRunner is capable of running a plugin in a given running context.
type PluginRunner interface {
Run(plugin *Plugin, ctx RunningContext) error
}
// RunningContext holds the context in which a given plugin is running - the
// in, out, and err streams, arguments and environment passed to it, and the
// working directory.
type RunningContext struct {
In io.Reader
Out io.Writer
ErrOut io.Writer
Args []string
EnvProvider EnvProvider
WorkingDir string
}
// ExecPluginRunner is a PluginRunner that uses Go's os/exec to run plugins.
type ExecPluginRunner struct{}
// Run takes a given plugin and runs it in a given context using os/exec, returning
// any error found while running.
func (r *ExecPluginRunner) Run(plugin *Plugin, ctx RunningContext) error {
command := strings.Split(os.ExpandEnv(plugin.Command), " ")
base := command[0]
args := []string{}
if len(command) > 1 {
args = command[1:]
}
args = append(args, ctx.Args...)
cmd := exec.Command(base, args...)
cmd.Stdin = ctx.In
cmd.Stdout = ctx.Out
cmd.Stderr = ctx.ErrOut
env, err := ctx.EnvProvider.Env()
if err != nil {
return err
}
cmd.Env = env.Slice()
cmd.Dir = ctx.WorkingDir
glog.V(9).Infof("Running plugin %q as base command %q with args %v", plugin.Name, base, args)
return cmd.Run()
}

View File

@ -0,0 +1,81 @@
/*
Copyright 2017 The Kubernetes Authors.
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 plugins
import (
"bytes"
"os"
"testing"
)
func TestExecRunner(t *testing.T) {
tests := []struct {
name string
command string
expectedMsg string
expectedErr string
}{
{
name: "success",
command: "echo test ok",
expectedMsg: "test ok\n",
},
{
name: "invalid",
command: "false",
expectedErr: "exit status 1",
},
{
name: "env",
command: "echo $KUBECTL_PLUGINS_TEST",
expectedMsg: "ok\n",
},
}
os.Setenv("KUBECTL_PLUGINS_TEST", "ok")
defer os.Unsetenv("KUBECTL_PLUGINS_TEST")
for _, test := range tests {
outBuf := bytes.NewBuffer([]byte{})
plugin := &Plugin{
Description: Description{
Name: test.name,
ShortDesc: "Test Runner Plugin",
Command: test.command,
},
}
ctx := RunningContext{
Out: outBuf,
WorkingDir: ".",
EnvProvider: &EmptyEnvProvider{},
}
runner := &ExecPluginRunner{}
err := runner.Run(plugin, ctx)
if outBuf.String() != test.expectedMsg {
t.Errorf("%s: unexpected output: %q", test.name, outBuf.String())
}
if err != nil && err.Error() != test.expectedErr {
t.Errorf("%s: unexpected err output: %v", test.name, err)
}
}
}