mirror of https://github.com/knative/client.git
Implements Kn plugins re-using some code from kubectl plugins. (#249)
This version contains the following: 1. wraps the main root Kn command to support plugin 2. plugins are any executable in kn's config new pluginDir variable which defaults to $PATH 3. plugins must have name kn-* 4. 'kn plugin list' sub-command to list found kn plugins 5. skips any kn plugins found with name that match core commands, e.g., kn-service would be ignored 6. can execute any valid kn plugins found, e.g., `kn valid` where the plugin file `kn-valid` is in path specified in 2. 7. unit tests (using gotest.tools) And is missing: 1. integration tests 2. plugin install command 3. plugin repository command 4. plugin / Knative server version negotiation 5. anything else we agree on in plugin req doc I plan to create issues for the things missing so we don't end up with an even bigger PR. It's already big as is but is a good MVP as per plugins requirement doc.
This commit is contained in:
parent
df816e63fe
commit
59b2855d04
|
|
@ -19,12 +19,26 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/knative/client/pkg/kn/core"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
core.InitializeConfig()
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
func main() {
|
||||
err := core.NewKnCommand().Execute()
|
||||
defer cleanup()
|
||||
err = core.NewDefaultKnCommand().Execute()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
if err == nil {
|
||||
viper.WriteConfig()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,16 @@ Manage your Knative building blocks:
|
|||
### Options
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
-h, --help help for kn
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
--lookup-plugins-in-path look for kn plugins in $PATH
|
||||
--plugins-dir string kn plugins directory (default "~/.kn/plugins")
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [kn plugin](kn_plugin.md) - Plugin command group
|
||||
* [kn revision](kn_revision.md) - Revision command group
|
||||
* [kn route](kn_route.md) - Route command group
|
||||
* [kn service](kn_service.md) - Service command group
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
## kn plugin
|
||||
|
||||
Plugin command group
|
||||
|
||||
### Synopsis
|
||||
|
||||
Provides utilities for interacting and managing with kn plugins.
|
||||
|
||||
Plugins provide extended functionality that is not part of the core kn command-line distribution.
|
||||
Please refer to the documentation and examples for more information about how write your own plugins.
|
||||
|
||||
```
|
||||
kn plugin [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for plugin
|
||||
--lookup-plugins-in-path look for kn plugins in $PATH
|
||||
--plugins-dir string kn plugins directory (default "~/.kn/plugins")
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [kn](kn.md) - Knative client
|
||||
* [kn plugin list](kn_plugin_list.md) - List all visible plugin executables
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
## kn plugin list
|
||||
|
||||
List all visible plugin executables
|
||||
|
||||
### Synopsis
|
||||
|
||||
List all visible plugin executables.
|
||||
|
||||
Available plugin files are those that are:
|
||||
- executable
|
||||
- begin with "kn-
|
||||
- anywhere on the path specified in Kn's config pluginDir variable, which:
|
||||
* can be overridden with the --plugin-dir flag
|
||||
|
||||
```
|
||||
kn plugin list [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for list
|
||||
--lookup-plugins-in-path look for kn plugins in $PATH
|
||||
--name-only If true, display only the binary name of each plugin, rather than its full path
|
||||
--plugins-dir string kn plugins directory (default "~/.kn/plugins")
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [kn plugin](kn_plugin.md) - Plugin command group
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ kn revision [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ kn revision delete NAME [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ kn revision describe NAME [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ kn revision list [name] [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ kn route [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ kn route describe NAME [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ kn route list NAME [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ kn service [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ kn service create NAME --image IMAGE [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ kn service delete NAME [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ kn service describe NAME [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ kn service list [name] [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ kn service update NAME [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ kn version [flags]
|
|||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--config string config file (default is $HOME/.kn/config.yaml)
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// PathVerifier receives a path and determines if it is valid or not
|
||||
type PathVerifier interface {
|
||||
// Verify determines if a given path is valid
|
||||
Verify(path string) []error
|
||||
}
|
||||
|
||||
// CommandOverrideVerifier verifies that existing kn commands are not overriden
|
||||
type CommandOverrideVerifier struct {
|
||||
Root *cobra.Command
|
||||
SeenPlugins map[string]string
|
||||
}
|
||||
|
||||
// Verify implements PathVerifier and determines if a given path
|
||||
// is valid depending on whether or not it overwrites an existing
|
||||
// kn command path, or a previously seen plugin.
|
||||
func (v *CommandOverrideVerifier) Verify(path string) []error {
|
||||
if v.Root == nil {
|
||||
return []error{fmt.Errorf("unable to verify path with nil root")}
|
||||
}
|
||||
|
||||
// extract the plugin binary name
|
||||
segs := strings.Split(path, string(os.PathSeparator))
|
||||
binName := segs[len(segs)-1]
|
||||
|
||||
cmdPath := strings.Split(binName, "-")
|
||||
if len(cmdPath) > 1 {
|
||||
// the first argument is always "kn" for a plugin binary
|
||||
cmdPath = cmdPath[1:]
|
||||
}
|
||||
|
||||
errors := []error{}
|
||||
isExec, err := isExecutable(path)
|
||||
if err == nil && !isExec {
|
||||
errors = append(errors, fmt.Errorf("warning: %s identified as a kn plugin, but it is not executable", path))
|
||||
} else if err != nil {
|
||||
errors = append(errors, fmt.Errorf("error: unable to identify %s as an executable file: %v", path, err))
|
||||
}
|
||||
|
||||
if existingPath, ok := v.SeenPlugins[binName]; ok {
|
||||
errors = append(errors, fmt.Errorf("warning: %s is overshadowed by a similarly named plugin: %s", path, existingPath))
|
||||
} else {
|
||||
v.SeenPlugins[binName] = path
|
||||
}
|
||||
|
||||
cmd, _, err := v.Root.Find(cmdPath)
|
||||
if err == nil {
|
||||
errors = append(errors, fmt.Errorf("warning: %s overwrites existing command: %q", binName, cmd.CommandPath()))
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
// Private functions
|
||||
|
||||
func isExecutable(fullPath string) (bool, error) {
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
fileExt := strings.ToLower(filepath.Ext(fullPath))
|
||||
|
||||
switch fileExt {
|
||||
case ".bat", ".cmd", ".com", ".exe", ".ps1":
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/knative/client/pkg/kn/commands"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestCommandOverrideVerifier(t *testing.T) {
|
||||
var (
|
||||
pluginPath string
|
||||
rootCmd *cobra.Command
|
||||
verifier *CommandOverrideVerifier
|
||||
)
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
knParams := &commands.KnParams{}
|
||||
rootCmd, _, _ = commands.CreateTestKnCommand(NewPluginCommand(knParams), knParams)
|
||||
verifier = &CommandOverrideVerifier{
|
||||
Root: rootCmd,
|
||||
SeenPlugins: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
cleanup := func(t *testing.T) {
|
||||
if pluginPath != "" {
|
||||
DeleteTestPlugin(t, pluginPath)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("with nil root command", func(t *testing.T) {
|
||||
t.Run("returns error verifying path", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
verifier.Root = nil
|
||||
|
||||
errs := verifier.Verify(pluginPath)
|
||||
assert.Assert(t, len(errs) == 1)
|
||||
assert.Assert(t, errs[0] != nil)
|
||||
assert.Assert(t, strings.Contains(errs[0].Error(), "unable to verify path with nil root"))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with root command", func(t *testing.T) {
|
||||
t.Run("when plugin in path not executable", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
pluginPath = CreateTestPlugin(t, KnTestPluginName, KnTestPluginScript, FileModeReadable)
|
||||
|
||||
t.Run("fails with not executable error", func(t *testing.T) {
|
||||
errs := verifier.Verify(pluginPath)
|
||||
assert.Assert(t, len(errs) == 1)
|
||||
assert.Assert(t, errs[0] != nil)
|
||||
errorMsg := fmt.Sprintf("warning: %s identified as a kn plugin, but it is not executable", pluginPath)
|
||||
assert.Assert(t, strings.Contains(errs[0].Error(), errorMsg))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when kn plugin in path is executable", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
pluginPath = CreateTestPlugin(t, KnTestPluginName, KnTestPluginScript, FileModeExecutable)
|
||||
|
||||
t.Run("when kn plugin in path shadows another", func(t *testing.T) {
|
||||
var shadowPluginPath = CreateTestPlugin(t, KnTestPluginName, KnTestPluginScript, FileModeExecutable)
|
||||
verifier.SeenPlugins[KnTestPluginName] = pluginPath
|
||||
defer DeleteTestPlugin(t, shadowPluginPath)
|
||||
|
||||
t.Run("fails with overshadowed error", func(t *testing.T) {
|
||||
errs := verifier.Verify(shadowPluginPath)
|
||||
assert.Assert(t, len(errs) == 1)
|
||||
assert.Assert(t, errs[0] != nil)
|
||||
errorMsg := fmt.Sprintf("warning: %s is overshadowed by a similarly named plugin: %s", shadowPluginPath, pluginPath)
|
||||
assert.Assert(t, strings.Contains(errs[0].Error(), errorMsg))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when kn plugin in path overwrites existing command", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
var overwritingPluginPath = CreateTestPlugin(t, "kn-plugin", KnTestPluginScript, FileModeExecutable)
|
||||
defer DeleteTestPlugin(t, overwritingPluginPath)
|
||||
|
||||
t.Run("fails with overwrites error", func(t *testing.T) {
|
||||
errs := verifier.Verify(overwritingPluginPath)
|
||||
assert.Assert(t, len(errs) == 1)
|
||||
assert.Assert(t, errs[0] != nil)
|
||||
errorMsg := fmt.Sprintf("warning: %s overwrites existing command: %q", "kn-plugin", "kn plugin")
|
||||
assert.Assert(t, strings.Contains(errs[0].Error(), errorMsg))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"github.com/knative/client/pkg/kn/commands"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func NewPluginCommand(p *commands.KnParams) *cobra.Command {
|
||||
pluginCmd := &cobra.Command{
|
||||
Use: "plugin",
|
||||
Short: "Plugin command group",
|
||||
Long: `Provides utilities for interacting and managing with kn plugins.
|
||||
|
||||
Plugins provide extended functionality that is not part of the core kn command-line distribution.
|
||||
Please refer to the documentation and examples for more information about how write your own plugins.`,
|
||||
}
|
||||
|
||||
AddPluginFlags(pluginCmd)
|
||||
BindPluginsFlagToViper(pluginCmd)
|
||||
|
||||
pluginCmd.AddCommand(NewPluginListCommand(p))
|
||||
|
||||
return pluginCmd
|
||||
}
|
||||
|
||||
// AddPluginFlags plugins-dir and lookup-plugins-in-path to cmd
|
||||
func AddPluginFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringVar(&commands.Cfg.PluginsDir, "plugins-dir", "~/.kn/plugins", "kn plugins directory")
|
||||
cmd.Flags().BoolVar(&commands.Cfg.LookupPluginsInPath, "lookup-plugins-in-path", false, "look for kn plugins in $PATH")
|
||||
}
|
||||
|
||||
// BindPluginsFlagToViper bind and set default with viper for plugins flags
|
||||
func BindPluginsFlagToViper(cmd *cobra.Command) {
|
||||
viper.BindPFlag("pluginsDir", cmd.Flags().Lookup("plugins-dir"))
|
||||
viper.BindPFlag("lookupPluginsInPath", cmd.Flags().Lookup("lookup-plugins-in-path"))
|
||||
|
||||
viper.SetDefault("pluginsDir", "~/.kn/plugins")
|
||||
viper.SetDefault("lookupPluginsInPath", false)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
)
|
||||
|
||||
// PluginFlags contains all PLugin commands flags
|
||||
type PluginFlags struct {
|
||||
NameOnly bool
|
||||
|
||||
Verifier PathVerifier
|
||||
PluginPaths []string
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
// AddPluginFlags adds the various flags to plugin command
|
||||
func (p *PluginFlags) AddPluginFlags(command *cobra.Command) {
|
||||
command.Flags().BoolVar(&p.NameOnly, "name-only", false, "If true, display only the binary name of each plugin, rather than its full path")
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestAddPluginFlags(t *testing.T) {
|
||||
var (
|
||||
pluginFlags *PluginFlags
|
||||
cmd *cobra.Command
|
||||
)
|
||||
|
||||
setup := func() {
|
||||
pluginFlags = &PluginFlags{}
|
||||
|
||||
cmd = &cobra.Command{}
|
||||
}
|
||||
|
||||
t.Run("adds plugin flag", func(t *testing.T) {
|
||||
setup()
|
||||
pluginFlags.AddPluginFlags(cmd)
|
||||
|
||||
assert.Assert(t, pluginFlags != nil)
|
||||
assert.Assert(t, cmd.Flags() != nil)
|
||||
|
||||
nameOnly, err := cmd.Flags().GetBool("name-only")
|
||||
assert.Assert(t, err == nil)
|
||||
assert.Assert(t, nameOnly == false)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright © 2019 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// PluginHandler is capable of parsing command line arguments
|
||||
// and performing executable filename lookups to search
|
||||
// for valid plugin files, and execute found plugins.
|
||||
type PluginHandler interface {
|
||||
// exists at the given filename, or a boolean false.
|
||||
// Lookup will iterate over a list of given prefixes
|
||||
// in order to recognize valid plugin filenames.
|
||||
// The first filepath to match a prefix is returned.
|
||||
Lookup(name string) (string, bool)
|
||||
// Execute receives an executable's filepath, a slice
|
||||
// of arguments, and a slice of environment variables
|
||||
// to relay to the executable.
|
||||
Execute(executablePath string, cmdArgs, environment []string) error
|
||||
}
|
||||
|
||||
// DefaultPluginHandler implements PluginHandler
|
||||
type DefaultPluginHandler struct {
|
||||
ValidPrefixes []string
|
||||
PluginsDir string
|
||||
LookupPluginsInPath bool
|
||||
}
|
||||
|
||||
// NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of
|
||||
// given filename prefixes used to identify valid plugin filenames.
|
||||
func NewDefaultPluginHandler(validPrefixes []string, pluginsDir string, lookupPluginsInPath bool) *DefaultPluginHandler {
|
||||
return &DefaultPluginHandler{
|
||||
ValidPrefixes: validPrefixes,
|
||||
PluginsDir: pluginsDir,
|
||||
LookupPluginsInPath: lookupPluginsInPath,
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup implements PluginHandler
|
||||
func (h *DefaultPluginHandler) Lookup(name string) (string, bool) {
|
||||
for _, prefix := range h.ValidPrefixes {
|
||||
pluginPath := fmt.Sprintf("%s-%s", prefix, name)
|
||||
|
||||
// Try to find plugin in pluginsDir
|
||||
pluginDir, err := ExpandPath(h.PluginsDir)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
pluginDirPluginPath := filepath.Join(pluginDir, pluginPath)
|
||||
_, err = os.Stat(pluginDirPluginPath)
|
||||
if !os.IsNotExist(err) {
|
||||
return pluginDirPluginPath, true
|
||||
}
|
||||
|
||||
// No plugins found in pluginsDir, try in PATH of that's an option
|
||||
if h.LookupPluginsInPath {
|
||||
pluginPath, err = exec.LookPath(pluginPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if pluginPath != "" {
|
||||
return pluginPath, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Execute implements PluginHandler
|
||||
func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {
|
||||
return syscall.Exec(executablePath, cmdArgs, environment)
|
||||
}
|
||||
|
||||
// HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find
|
||||
// a plugin executable that satisfies the given arguments.
|
||||
func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string) error {
|
||||
remainingArgs := []string{}
|
||||
|
||||
for idx := range cmdArgs {
|
||||
if strings.HasPrefix(cmdArgs[idx], "-") {
|
||||
continue
|
||||
}
|
||||
remainingArgs = append(remainingArgs, strings.Replace(cmdArgs[idx], "-", "_", -1))
|
||||
}
|
||||
|
||||
foundBinaryPath := ""
|
||||
|
||||
// attempt to find binary, starting at longest possible name with given cmdArgs
|
||||
for len(remainingArgs) > 0 {
|
||||
path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-"))
|
||||
if !found {
|
||||
remainingArgs = remainingArgs[:len(remainingArgs)-1]
|
||||
continue
|
||||
}
|
||||
|
||||
foundBinaryPath = path
|
||||
break
|
||||
}
|
||||
|
||||
if len(foundBinaryPath) == 0 {
|
||||
return errors.New("Could not find plugin to execute")
|
||||
}
|
||||
|
||||
// invoke cmd binary relaying the current environment and args given
|
||||
// remainingArgs will always have at least one element.
|
||||
// execve will make remainingArgs[0] the "binary name".
|
||||
err := pluginHandler.Execute(foundBinaryPath, append([]string{foundBinaryPath}, cmdArgs[len(remainingArgs):]...), os.Environ())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestPluginHandler(t *testing.T) {
|
||||
var (
|
||||
pluginHandler, tPluginHandler PluginHandler
|
||||
pluginPath, pluginName, tmpPathDir, pluginsDir string
|
||||
lookupPluginsInPath bool
|
||||
err error
|
||||
)
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
tmpPathDir, err = ioutil.TempDir("", "plugin_list")
|
||||
assert.Assert(t, err == nil)
|
||||
pluginsDir = tmpPathDir
|
||||
}
|
||||
|
||||
cleanup := func(t *testing.T) {
|
||||
err = os.RemoveAll(tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
}
|
||||
|
||||
beforeEach := func(t *testing.T) {
|
||||
pluginName = "fake"
|
||||
pluginPath = CreateTestPluginInPath(t, "kn-"+pluginName, KnTestPluginScript, FileModeExecutable, tmpPathDir)
|
||||
assert.Assert(t, pluginPath != "")
|
||||
|
||||
pluginHandler = &DefaultPluginHandler{
|
||||
ValidPrefixes: []string{"kn"},
|
||||
PluginsDir: pluginsDir,
|
||||
LookupPluginsInPath: lookupPluginsInPath,
|
||||
}
|
||||
assert.Assert(t, pluginHandler != nil)
|
||||
|
||||
tPluginHandler = NewTestPluginHandler(pluginHandler)
|
||||
assert.Assert(t, tPluginHandler != nil)
|
||||
}
|
||||
|
||||
t.Run("#NewDefaultPluginHandler", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
|
||||
pHandler := NewDefaultPluginHandler([]string{"kn"}, pluginPath, false)
|
||||
assert.Assert(t, pHandler != nil)
|
||||
})
|
||||
|
||||
t.Run("#Lookup", func(t *testing.T) {
|
||||
t.Run("when plugin in pluginsDir", func(t *testing.T) {
|
||||
t.Run("returns the first filepath matching prefix", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
|
||||
path, exists := pluginHandler.Lookup(pluginName)
|
||||
assert.Assert(t, path != "", fmt.Sprintf("no path when Lookup(%s)", pluginName))
|
||||
assert.Assert(t, exists == true, fmt.Sprintf("could not Lookup(%s)", pluginName))
|
||||
})
|
||||
|
||||
t.Run("returns empty filepath when no matching prefix found", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
|
||||
path, exists := pluginHandler.Lookup("bogus-plugin-name")
|
||||
assert.Assert(t, path == "", fmt.Sprintf("unexpected plugin: kn-bogus-plugin-name"))
|
||||
assert.Assert(t, exists == false, fmt.Sprintf("unexpected plugin: kn-bogus-plugin-name"))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when plugin is in $PATH", func(t *testing.T) {
|
||||
t.Run("--lookup-plugins-in-path=true", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
|
||||
pluginsDir = filepath.Join(tmpPathDir, "bogus")
|
||||
err = os.Setenv("PATH", tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
lookupPluginsInPath = true
|
||||
|
||||
beforeEach(t)
|
||||
|
||||
path, exists := pluginHandler.Lookup(pluginName)
|
||||
assert.Assert(t, path != "", fmt.Sprintf("no path when Lookup(%s)", pluginName))
|
||||
assert.Assert(t, exists == true, fmt.Sprintf("could not Lookup(%s)", pluginName))
|
||||
})
|
||||
|
||||
t.Run("--lookup-plugins-in-path=false", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
|
||||
pluginsDir = filepath.Join(tmpPathDir, "bogus")
|
||||
err = os.Setenv("PATH", tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
lookupPluginsInPath = false
|
||||
|
||||
beforeEach(t)
|
||||
|
||||
path, exists := pluginHandler.Lookup(pluginName)
|
||||
assert.Assert(t, path == "")
|
||||
assert.Assert(t, exists == false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("#Execute", func(t *testing.T) {
|
||||
t.Run("fails executing bogus plugin name", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
|
||||
bogusPath := filepath.Join(filepath.Dir(pluginPath), "kn-bogus-plugin-name")
|
||||
err = pluginHandler.Execute(bogusPath, []string{bogusPath}, os.Environ())
|
||||
assert.Assert(t, err != nil, fmt.Sprintf("bogus plugin in path %s unexpectedly executed OK", bogusPath))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("HandlePluginCommand", func(t *testing.T) {
|
||||
t.Run("sucess handling", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
|
||||
err = HandlePluginCommand(tPluginHandler, []string{pluginName})
|
||||
assert.Assert(t, err == nil, fmt.Sprintf("test plugin %s failed executing", fmt.Sprintf("kn-%s", pluginName)))
|
||||
})
|
||||
|
||||
t.Run("fails handling", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
|
||||
err = HandlePluginCommand(tPluginHandler, []string{"bogus"})
|
||||
assert.Assert(t, err != nil, fmt.Sprintf("test plugin %s expected to fail executing", "bogus"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// TestPluginHandler - needed to mock Execute() call
|
||||
|
||||
type testPluginHandler struct {
|
||||
pluginHandler PluginHandler
|
||||
}
|
||||
|
||||
func NewTestPluginHandler(pluginHandler PluginHandler) PluginHandler {
|
||||
return &testPluginHandler{
|
||||
pluginHandler: pluginHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (tHandler *testPluginHandler) Lookup(name string) (string, bool) {
|
||||
return tHandler.pluginHandler.Lookup(name)
|
||||
}
|
||||
|
||||
func (tHandler *testPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {
|
||||
// Always success (avoids doing syscall.Exec which exits tests framework)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
// Copyright © 2019 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/knative/client/pkg/kn/commands"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// ValidPluginFilenamePrefixes controls the prefix for all kn plugins
|
||||
var ValidPluginFilenamePrefixes = []string{"kn"}
|
||||
|
||||
// NewPluginListCommand creates a new `kn plugin list` command
|
||||
func NewPluginListCommand(p *commands.KnParams) *cobra.Command {
|
||||
pluginFlags := PluginFlags{
|
||||
IOStreams: genericclioptions.IOStreams{
|
||||
In: os.Stdin,
|
||||
Out: os.Stdout,
|
||||
ErrOut: os.Stderr,
|
||||
},
|
||||
}
|
||||
|
||||
pluginListCommand := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all visible plugin executables",
|
||||
Long: `List all visible plugin executables.
|
||||
|
||||
Available plugin files are those that are:
|
||||
- executable
|
||||
- begin with "kn-
|
||||
- anywhere on the path specified in Kn's config pluginDir variable, which:
|
||||
* can be overridden with the --plugin-dir flag`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := pluginFlags.complete(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = pluginFlags.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
AddPluginFlags(pluginListCommand)
|
||||
BindPluginsFlagToViper(pluginListCommand)
|
||||
|
||||
pluginFlags.AddPluginFlags(pluginListCommand)
|
||||
|
||||
return pluginListCommand
|
||||
}
|
||||
|
||||
// ExpandPath to a canonical path (need to see if Golang has a better option)
|
||||
func ExpandPath(path string) (string, error) {
|
||||
if strings.Contains(path, "~") {
|
||||
var err error
|
||||
path, err = expandHomeDir(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
func (o *PluginFlags) complete(cmd *cobra.Command) error {
|
||||
o.Verifier = &CommandOverrideVerifier{
|
||||
Root: cmd.Root(),
|
||||
SeenPlugins: make(map[string]string, 0),
|
||||
}
|
||||
|
||||
pluginPath, err := ExpandPath(commands.Cfg.PluginsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if commands.Cfg.LookupPluginsInPath {
|
||||
pluginPath = pluginPath + string(os.PathListSeparator) + os.Getenv("PATH")
|
||||
}
|
||||
|
||||
o.PluginPaths = filepath.SplitList(pluginPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *PluginFlags) run() error {
|
||||
pluginsFound := false
|
||||
isFirstFile := true
|
||||
pluginErrors := []error{}
|
||||
pluginWarnings := 0
|
||||
|
||||
for _, dir := range uniquePathsList(o.PluginPaths) {
|
||||
if dir == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); ok {
|
||||
fmt.Fprintf(o.ErrOut, "Unable read directory '%s' from your plugins path: %v. Skipping...", dir, err)
|
||||
continue
|
||||
}
|
||||
|
||||
pluginErrors = append(pluginErrors, fmt.Errorf("error: unable to read directory '%s' from your plugin path: %v", dir, err))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !hasValidPrefix(f.Name(), ValidPluginFilenamePrefixes) {
|
||||
continue
|
||||
}
|
||||
|
||||
if isFirstFile {
|
||||
fmt.Fprintf(o.ErrOut, "The following compatible plugins are available, using options:\n")
|
||||
fmt.Fprintf(o.ErrOut, " - plugins dir: '%s'\n", commands.Cfg.PluginsDir)
|
||||
fmt.Fprintf(o.ErrOut, " - lookup plugins in path: '%t'\n\n", commands.Cfg.LookupPluginsInPath)
|
||||
pluginsFound = true
|
||||
isFirstFile = false
|
||||
}
|
||||
|
||||
pluginPath := f.Name()
|
||||
if !o.NameOnly {
|
||||
pluginPath = filepath.Join(dir, pluginPath)
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.Out, "%s\n", pluginPath)
|
||||
if errs := o.Verifier.Verify(filepath.Join(dir, f.Name())); len(errs) != 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Fprintf(o.ErrOut, " - %s\n", err)
|
||||
pluginWarnings++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !pluginsFound {
|
||||
pluginErrors = append(pluginErrors, fmt.Errorf("warning: unable to find any kn plugins in your plugin path: '%s'", o.PluginPaths))
|
||||
}
|
||||
|
||||
if pluginWarnings > 0 {
|
||||
if pluginWarnings == 1 {
|
||||
pluginErrors = append(pluginErrors, fmt.Errorf("error: one plugin warning was found"))
|
||||
} else {
|
||||
pluginErrors = append(pluginErrors, fmt.Errorf("error: %v plugin warnings were found", pluginWarnings))
|
||||
}
|
||||
}
|
||||
if len(pluginErrors) > 0 {
|
||||
fmt.Fprintln(o.ErrOut)
|
||||
errs := bytes.NewBuffer(nil)
|
||||
for _, e := range pluginErrors {
|
||||
fmt.Fprintln(errs, e)
|
||||
}
|
||||
return fmt.Errorf("%s", errs.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
// expandHomeDir replaces the ~ with the home directory value
|
||||
func expandHomeDir(path string) (string, error) {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.Replace(path, "~", home, -1), nil
|
||||
}
|
||||
|
||||
// uniquePathsList deduplicates a given slice of strings without
|
||||
// sorting or otherwise altering its order in any way.
|
||||
func uniquePathsList(paths []string) []string {
|
||||
seen := map[string]bool{}
|
||||
newPaths := []string{}
|
||||
for _, p := range paths {
|
||||
if seen[p] {
|
||||
continue
|
||||
}
|
||||
seen[p] = true
|
||||
newPaths = append(newPaths, p)
|
||||
}
|
||||
return newPaths
|
||||
}
|
||||
|
||||
func hasValidPrefix(filepath string, validPrefixes []string) bool {
|
||||
for _, prefix := range validPrefixes {
|
||||
if !strings.HasPrefix(filepath, prefix+"-") {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/knative/client/pkg/kn/commands"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestPluginList(t *testing.T) {
|
||||
var (
|
||||
rootCmd, pluginCmd, pluginListCmd *cobra.Command
|
||||
tmpPathDir, pluginsDir, pluginsDirFlag string
|
||||
err error
|
||||
)
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
knParams := &commands.KnParams{}
|
||||
pluginCmd = NewPluginCommand(knParams)
|
||||
assert.Assert(t, pluginCmd != nil)
|
||||
|
||||
rootCmd, _, _ = commands.CreateTestKnCommand(pluginCmd, knParams)
|
||||
assert.Assert(t, rootCmd != nil)
|
||||
|
||||
pluginListCmd = FindSubCommand(t, pluginCmd, "list")
|
||||
assert.Assert(t, pluginListCmd != nil)
|
||||
|
||||
tmpPathDir, err = ioutil.TempDir("", "plugin_list")
|
||||
assert.Assert(t, err == nil)
|
||||
|
||||
pluginsDir = filepath.Join(tmpPathDir, "plugins")
|
||||
pluginsDirFlag = fmt.Sprintf("--plugins-dir=%s", pluginsDir)
|
||||
}
|
||||
|
||||
cleanup := func(t *testing.T) {
|
||||
err = os.RemoveAll(tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
}
|
||||
|
||||
t.Run("creates a new cobra.Command", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
|
||||
assert.Assert(t, pluginListCmd != nil)
|
||||
assert.Assert(t, pluginListCmd.Use == "list")
|
||||
assert.Assert(t, pluginListCmd.Short == "List all visible plugin executables")
|
||||
assert.Assert(t, strings.Contains(pluginListCmd.Long, "List all visible plugin executables"))
|
||||
assert.Assert(t, pluginListCmd.Flags().Lookup("plugins-dir") != nil)
|
||||
assert.Assert(t, pluginListCmd.RunE != nil)
|
||||
})
|
||||
|
||||
t.Run("when pluginsDir does not include any plugins", func(t *testing.T) {
|
||||
t.Run("when --lookup-plugins-in-path is true", func(t *testing.T) {
|
||||
var pluginPath string
|
||||
|
||||
beforeEach := func(t *testing.T) {
|
||||
err = os.Setenv("PATH", tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
}
|
||||
|
||||
t.Run("no plugins installed", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
|
||||
t.Run("warns user that no plugins found", func(t *testing.T) {
|
||||
rootCmd.SetArgs([]string{"plugin", "list", "--lookup-plugins-in-path=true", pluginsDirFlag})
|
||||
err = rootCmd.Execute()
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, strings.Contains(err.Error(), "warning: unable to find any kn plugins in your plugin path:"))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("plugins installed", func(t *testing.T) {
|
||||
t.Run("with valid plugin in $PATH", func(t *testing.T) {
|
||||
beforeEach := func(t *testing.T) {
|
||||
pluginPath = CreateTestPluginInPath(t, KnTestPluginName, KnTestPluginScript, FileModeExecutable, tmpPathDir)
|
||||
assert.Assert(t, pluginPath != "")
|
||||
|
||||
err = os.Setenv("PATH", tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
}
|
||||
|
||||
t.Run("list plugins in $PATH", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
|
||||
commands.CaptureStdout(t)
|
||||
rootCmd.SetArgs([]string{"plugin", "list", "--lookup-plugins-in-path=true", pluginsDirFlag})
|
||||
err = rootCmd.Execute()
|
||||
assert.Assert(t, err == nil)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with non-executable plugin", func(t *testing.T) {
|
||||
beforeEach := func(t *testing.T) {
|
||||
pluginPath = CreateTestPluginInPath(t, KnTestPluginName, KnTestPluginScript, FileModeReadable, tmpPathDir)
|
||||
assert.Assert(t, pluginPath != "")
|
||||
}
|
||||
|
||||
t.Run("warns user plugin invalid", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
|
||||
rootCmd.SetArgs([]string{"plugin", "list", "--lookup-plugins-in-path=true", pluginsDirFlag})
|
||||
err = rootCmd.Execute()
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, strings.Contains(err.Error(), "warning: unable to find any kn plugins in your plugin path:"))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with plugins with same name", func(t *testing.T) {
|
||||
var tmpPathDir2 string
|
||||
|
||||
beforeEach := func(t *testing.T) {
|
||||
pluginPath = CreateTestPluginInPath(t, KnTestPluginName, KnTestPluginScript, FileModeExecutable, tmpPathDir)
|
||||
assert.Assert(t, pluginPath != "")
|
||||
|
||||
tmpPathDir2, err = ioutil.TempDir("", "plugins_list")
|
||||
assert.Assert(t, err == nil)
|
||||
|
||||
err = os.Setenv("PATH", tmpPathDir+string(os.PathListSeparator)+tmpPathDir2)
|
||||
assert.Assert(t, err == nil)
|
||||
|
||||
pluginPath = CreateTestPluginInPath(t, KnTestPluginName, KnTestPluginScript, FileModeExecutable, tmpPathDir2)
|
||||
assert.Assert(t, pluginPath != "")
|
||||
}
|
||||
|
||||
afterEach := func(t *testing.T) {
|
||||
err = os.RemoveAll(tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
|
||||
err = os.RemoveAll(tmpPathDir2)
|
||||
assert.Assert(t, err == nil)
|
||||
}
|
||||
|
||||
t.Run("warns user about second (in $PATH) plugin shadowing first", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
defer afterEach(t)
|
||||
|
||||
rootCmd.SetArgs([]string{"plugin", "list", "--lookup-plugins-in-path=true", pluginsDirFlag})
|
||||
err = rootCmd.Execute()
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, strings.Contains(err.Error(), "error: one plugin warning was found"))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with plugins with name of existing command", func(t *testing.T) {
|
||||
var fakeCmd *cobra.Command
|
||||
|
||||
beforeEach := func(t *testing.T) {
|
||||
fakeCmd = &cobra.Command{
|
||||
Use: "fake",
|
||||
}
|
||||
rootCmd.AddCommand(fakeCmd)
|
||||
|
||||
pluginPath = CreateTestPluginInPath(t, "kn-fake", KnTestPluginScript, FileModeExecutable, tmpPathDir)
|
||||
assert.Assert(t, pluginPath != "")
|
||||
|
||||
err = os.Setenv("PATH", tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
}
|
||||
|
||||
afterEach := func(t *testing.T) {
|
||||
rootCmd.RemoveCommand(fakeCmd)
|
||||
}
|
||||
|
||||
t.Run("warns user about overwritting exising command", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
defer afterEach(t)
|
||||
|
||||
rootCmd.SetArgs([]string{"plugin", "list", "--lookup-plugins-in-path=true", pluginsDirFlag})
|
||||
err = rootCmd.Execute()
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, strings.Contains(err.Error(), "error: one plugin warning was found"))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when pluginsDir has plugins", func(t *testing.T) {
|
||||
var pluginPath string
|
||||
|
||||
beforeEach := func(t *testing.T) {
|
||||
pluginPath = CreateTestPluginInPath(t, KnTestPluginName, KnTestPluginScript, FileModeExecutable, tmpPathDir)
|
||||
assert.Assert(t, pluginPath != "")
|
||||
|
||||
err = os.Setenv("PATH", "")
|
||||
assert.Assert(t, err == nil)
|
||||
|
||||
pluginsDirFlag = fmt.Sprintf("--plugins-dir=%s", tmpPathDir)
|
||||
}
|
||||
|
||||
t.Run("list plugins in --plugins-dir", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
beforeEach(t)
|
||||
|
||||
rootCmd.SetArgs([]string{"plugin", "list", pluginsDirFlag})
|
||||
err = rootCmd.Execute()
|
||||
assert.Assert(t, err == nil)
|
||||
})
|
||||
|
||||
t.Run("no plugins installed", func(t *testing.T) {
|
||||
setup(t)
|
||||
defer cleanup(t)
|
||||
|
||||
rootCmd.SetArgs([]string{"plugin", "list", pluginsDirFlag})
|
||||
err = rootCmd.Execute()
|
||||
assert.Assert(t, err != nil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/knative/client/pkg/kn/commands"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
const PluginCommandUsage = `Provides utilities for interacting and managing with kn plugins.
|
||||
|
||||
Plugins provide extended functionality that is not part of the core kn command-line distribution.
|
||||
Please refer to the documentation and examples for more information about how write your own plugins.
|
||||
|
||||
Usage:
|
||||
kn plugin [flags]
|
||||
kn plugin [command]
|
||||
|
||||
Available Commands:
|
||||
list List all visible plugin executables
|
||||
|
||||
Flags:
|
||||
-h, --help help for plugin
|
||||
--lookup-plugins-in-path look for kn plugins in $PATH
|
||||
--plugins-dir string kn plugins directory (default "~/.kn/plugins")
|
||||
|
||||
Global Flags:
|
||||
--config string kn config file (default is $HOME/.kn/config.yaml)
|
||||
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
|
||||
|
||||
Use "kn plugin [command] --help" for more information about a command.`
|
||||
|
||||
func TestNewPluginCommand(t *testing.T) {
|
||||
var (
|
||||
rootCmd, pluginCmd *cobra.Command
|
||||
)
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
knParams := &commands.KnParams{}
|
||||
pluginCmd = NewPluginCommand(knParams)
|
||||
assert.Assert(t, pluginCmd != nil)
|
||||
|
||||
rootCmd, _, _ = commands.CreateTestKnCommand(pluginCmd, knParams)
|
||||
assert.Assert(t, rootCmd != nil)
|
||||
}
|
||||
|
||||
t.Run("creates a new cobra.Command", func(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
assert.Assert(t, pluginCmd != nil)
|
||||
assert.Assert(t, pluginCmd.Use == "plugin")
|
||||
assert.Assert(t, pluginCmd.Short == "Plugin command group")
|
||||
assert.Assert(t, strings.Contains(pluginCmd.Long, "Provides utilities for interacting and managing with kn plugins."))
|
||||
assert.Assert(t, pluginCmd.Flags().Lookup("plugins-dir") != nil)
|
||||
assert.Assert(t, pluginCmd.Flags().Lookup("lookup-plugins-in-path") != nil)
|
||||
assert.Assert(t, pluginCmd.Args == nil)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright © 2018 The Knative 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 plugin
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
KnTestPluginName = "kn-test"
|
||||
KnTestPluginScript = `#!/bin/bash
|
||||
|
||||
echo "I am a test Kn plugin"
|
||||
exit 0
|
||||
`
|
||||
FileModeReadable = 0644
|
||||
FileModeExecutable = 0777
|
||||
)
|
||||
|
||||
// FindSubCommand return the sub-command by name
|
||||
func FindSubCommand(t *testing.T, rootCmd *cobra.Command, name string) *cobra.Command {
|
||||
for _, subCmd := range rootCmd.Commands() {
|
||||
if subCmd.Name() == name {
|
||||
return subCmd
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTestPlugin with name, script, and fileMode and return the tmp random path
|
||||
func CreateTestPlugin(t *testing.T, name, script string, fileMode os.FileMode) string {
|
||||
path, err := ioutil.TempDir("", "plugin")
|
||||
assert.Assert(t, err == nil)
|
||||
|
||||
return CreateTestPluginInPath(t, name, script, fileMode, path)
|
||||
}
|
||||
|
||||
// CreateTestPluginInPath with name, path, script, and fileMode and return the tmp random path
|
||||
func CreateTestPluginInPath(t *testing.T, name, script string, fileMode os.FileMode, path string) string {
|
||||
err := ioutil.WriteFile(filepath.Join(path, name), []byte(script), fileMode)
|
||||
assert.Assert(t, err == nil)
|
||||
|
||||
return filepath.Join(path, name)
|
||||
}
|
||||
|
||||
// DeleteTestPlugin with path
|
||||
func DeleteTestPlugin(t *testing.T, path string) {
|
||||
err := os.RemoveAll(filepath.Dir(path))
|
||||
assert.Assert(t, err == nil)
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/knative/client/pkg/serving/v1alpha1"
|
||||
"github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"gotest.tools/assert"
|
||||
client_testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
|
@ -113,6 +114,15 @@ Eventing: Manage event subscriptions and channels. Connect up event sources.`,
|
|||
rootCmd.PersistentFlags().StringVar(&CfgFile, "config", "", "config file (default is $HOME/.kn.yaml)")
|
||||
rootCmd.PersistentFlags().StringVar(¶ms.KubeCfgPath, "kubeconfig", "", "kubectl config file (default is $HOME/.kube/config)")
|
||||
|
||||
rootCmd.Flags().StringVar(&Cfg.PluginsDir, "plugins-dir", "~/.kn/plugins", "kn plugins directory")
|
||||
rootCmd.Flags().BoolVar(&Cfg.LookupPluginsInPath, "lookup-plugins-in-path", false, "look for kn plugins in $PATH")
|
||||
|
||||
viper.BindPFlag("pluginsDir", rootCmd.Flags().Lookup("plugins-dir"))
|
||||
viper.BindPFlag("lookupPluginsInPath", rootCmd.Flags().Lookup("lookup-plugins-in-path"))
|
||||
|
||||
viper.SetDefault("pluginsDir", "~/.kn/plugins")
|
||||
viper.SetDefault("lookupPluginsInPath", false)
|
||||
|
||||
rootCmd.AddCommand(subCommand)
|
||||
|
||||
// For glog parse error.
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright © 2018 The Knative 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 commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestCreateTestKnCommand(t *testing.T) {
|
||||
var (
|
||||
knCmd *cobra.Command
|
||||
serving *fake.FakeServingV1alpha1
|
||||
buffer *bytes.Buffer
|
||||
)
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
knParams := &KnParams{}
|
||||
knCmd, serving, buffer = CreateTestKnCommand(&cobra.Command{Use: "fake"}, knParams)
|
||||
assert.Assert(t, knCmd != nil)
|
||||
assert.Assert(t, len(knCmd.Commands()) == 1)
|
||||
assert.Assert(t, knCmd.Commands()[0].Use == "fake")
|
||||
assert.Assert(t, serving != nil)
|
||||
assert.Assert(t, buffer != nil)
|
||||
}
|
||||
|
||||
t.Run("creates a new kn cobra.Command", func(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
assert.Assert(t, knCmd != nil)
|
||||
assert.Assert(t, knCmd.Use == "kn")
|
||||
assert.Assert(t, knCmd.Short == "Knative client")
|
||||
assert.Assert(t, strings.Contains(knCmd.Long, "Manage your Knative building blocks:"))
|
||||
assert.Assert(t, knCmd.RunE == nil)
|
||||
assert.Assert(t, knCmd.DisableAutoGenTag == true)
|
||||
assert.Assert(t, knCmd.SilenceUsage == true)
|
||||
assert.Assert(t, knCmd.SilenceErrors == true)
|
||||
})
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
serving_kn_v1alpha1 "github.com/knative/client/pkg/serving/v1alpha1"
|
||||
|
||||
serving_v1alpha1_client "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
|
@ -30,6 +29,15 @@ import (
|
|||
// CfgFile is Kn's config file is the path for the Kubernetes config
|
||||
var CfgFile string
|
||||
|
||||
// Cfg is Kn's configuration values
|
||||
var Cfg Config
|
||||
|
||||
// Config contains the variables for the Kn config
|
||||
type Config struct {
|
||||
PluginsDir string
|
||||
LookupPluginsInPath bool
|
||||
}
|
||||
|
||||
// Parameters for creating commands. Useful for inserting mocks for testing.
|
||||
type KnParams struct {
|
||||
Output io.Writer
|
||||
|
|
|
|||
|
|
@ -18,24 +18,74 @@ import (
|
|||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/knative/client/pkg/kn/commands"
|
||||
"github.com/knative/client/pkg/kn/commands/plugin"
|
||||
"github.com/knative/client/pkg/kn/commands/revision"
|
||||
"github.com/knative/client/pkg/kn/commands/route"
|
||||
"github.com/knative/client/pkg/kn/commands/service"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
var kubeCfgFile string
|
||||
// NewDefaultKnCommand creates the default `kn` command with a default plugin handler
|
||||
func NewDefaultKnCommand() *cobra.Command {
|
||||
rootCmd := NewKnCommand()
|
||||
|
||||
// NewKnCommand creates new rootCmd represents the base command when called without any subcommands
|
||||
// Needed since otherwise --plugins-dir and --lookup-plugins-in-path
|
||||
// will not be accounted for since the plugin is not a Cobra command
|
||||
// and will not be parsed
|
||||
pluginsDir, lookupPluginsInPath, err := extractKnPluginFlags(os.Args)
|
||||
if err != nil {
|
||||
panic("Invalid plugin flag value")
|
||||
}
|
||||
|
||||
pluginHandler := plugin.NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes,
|
||||
pluginsDir, lookupPluginsInPath)
|
||||
|
||||
return NewDefaultKnCommandWithArgs(rootCmd, pluginHandler,
|
||||
os.Args, os.Stdin,
|
||||
os.Stdout, os.Stderr)
|
||||
}
|
||||
|
||||
// NewDefaultKnCommandWithArgs creates the `kn` command with arguments
|
||||
func NewDefaultKnCommandWithArgs(rootCmd *cobra.Command,
|
||||
pluginHandler plugin.PluginHandler,
|
||||
args []string,
|
||||
in io.Reader,
|
||||
out,
|
||||
errOut io.Writer) *cobra.Command {
|
||||
if pluginHandler == nil {
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
cmdPathPieces := args[1:]
|
||||
cmdPathPieces = removeKnPluginFlags(cmdPathPieces) // Plugin does not need these flags
|
||||
|
||||
// only look for suitable extension executables if
|
||||
// the specified command does not already exist
|
||||
if _, _, err := rootCmd.Find(cmdPathPieces); err != nil {
|
||||
err := plugin.HandlePluginCommand(pluginHandler, cmdPathPieces)
|
||||
if err != nil {
|
||||
fmt.Fprintf(errOut, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
// NewKnCommand creates the rootCmd which is the base command when called without any subcommands
|
||||
func NewKnCommand(params ...commands.KnParams) *cobra.Command {
|
||||
var p *commands.KnParams
|
||||
if len(params) == 0 {
|
||||
|
|
@ -67,11 +117,18 @@ func NewKnCommand(params ...commands.KnParams) *cobra.Command {
|
|||
if p.Output != nil {
|
||||
rootCmd.SetOutput(p.Output)
|
||||
}
|
||||
rootCmd.PersistentFlags().StringVar(&commands.CfgFile, "config", "", "config file (default is $HOME/.kn/config.yaml)")
|
||||
|
||||
// Persistent flags
|
||||
rootCmd.PersistentFlags().StringVar(&commands.CfgFile, "config", "", "kn config file (default is $HOME/.kn/config.yaml)")
|
||||
rootCmd.PersistentFlags().StringVar(&p.KubeCfgPath, "kubeconfig", "", "kubectl config file (default is $HOME/.kube/config)")
|
||||
|
||||
plugin.AddPluginFlags(rootCmd)
|
||||
plugin.BindPluginsFlagToViper(rootCmd)
|
||||
|
||||
// root child commands
|
||||
rootCmd.AddCommand(service.NewServiceCommand(p))
|
||||
rootCmd.AddCommand(revision.NewRevisionCommand(p))
|
||||
rootCmd.AddCommand(plugin.NewPluginCommand(p))
|
||||
rootCmd.AddCommand(route.NewRouteCommand(p))
|
||||
rootCmd.AddCommand(commands.NewCompletionCommand(p))
|
||||
rootCmd.AddCommand(commands.NewVersionCommand(p))
|
||||
|
|
@ -81,9 +138,15 @@ func NewKnCommand(params ...commands.KnParams) *cobra.Command {
|
|||
|
||||
// For glog parse error.
|
||||
flag.CommandLine.Parse([]string{})
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
// InitializeConfig initializes the kubeconfig used by all commands
|
||||
func InitializeConfig() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
}
|
||||
|
||||
// EmptyAndUnknownSubCommands adds a RunE to all commands that are groups to
|
||||
// deal with errors when called with empty or unknown sub command
|
||||
func EmptyAndUnknownSubCommands(cmd *cobra.Command) {
|
||||
|
|
@ -106,6 +169,8 @@ func EmptyAndUnknownSubCommands(cmd *cobra.Command) {
|
|||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if commands.CfgFile != "" {
|
||||
|
|
@ -119,7 +184,7 @@ func initConfig() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Search config in home directory with name ".kn" (without extension).
|
||||
// Search config in home directory with name ".kn" (without extension)
|
||||
viper.AddConfigPath(path.Join(home, ".kn"))
|
||||
viper.SetConfigName("config")
|
||||
}
|
||||
|
|
@ -127,7 +192,51 @@ func initConfig() {
|
|||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
||||
err := viper.ReadInConfig()
|
||||
if err == nil {
|
||||
fmt.Fprintln(os.Stderr, "Using kn config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
||||
|
||||
func extractKnPluginFlags(args []string) (string, bool, error) {
|
||||
pluginsDir := "~/.kn/plugins"
|
||||
lookupPluginsInPath := false
|
||||
for _, arg := range args {
|
||||
if strings.Contains(arg, "--plugins-dir") {
|
||||
values := strings.Split(arg, "=")
|
||||
if len(values) < 1 {
|
||||
return "", false, errors.New("Invalid --plugins-dir flag value")
|
||||
}
|
||||
pluginsDir = values[1]
|
||||
}
|
||||
|
||||
if strings.Contains(arg, "--lookup-plugins-in-path") {
|
||||
values := strings.Split(arg, "=")
|
||||
if len(values) < 1 {
|
||||
return "", false, errors.New("Invalid --lookup-plugins-in-path flag value")
|
||||
}
|
||||
|
||||
boolValue, err := strconv.ParseBool(values[1])
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
lookupPluginsInPath = boolValue
|
||||
}
|
||||
}
|
||||
return pluginsDir, lookupPluginsInPath, nil
|
||||
}
|
||||
|
||||
func removeKnPluginFlags(args []string) []string {
|
||||
var remainingArgs []string
|
||||
for _, arg := range args {
|
||||
if strings.Contains(arg, "--plugins-dir") ||
|
||||
strings.Contains(arg, "--lookup-plugins-in-path") {
|
||||
continue
|
||||
} else {
|
||||
remainingArgs = append(remainingArgs, arg)
|
||||
}
|
||||
}
|
||||
|
||||
return remainingArgs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,52 +15,123 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/knative/client/pkg/kn/commands"
|
||||
"github.com/knative/client/pkg/kn/commands/plugin"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestNewDefaultKnCommand(t *testing.T) {
|
||||
var rootCmd *cobra.Command
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
rootCmd = NewDefaultKnCommand()
|
||||
}
|
||||
|
||||
t.Run("returns a valid root command", func(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
checkRootCmd(t, rootCmd)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewDefaultKnCommandWithArgs(t *testing.T) {
|
||||
var (
|
||||
rootCmd *cobra.Command
|
||||
pluginHandler plugin.PluginHandler
|
||||
args []string
|
||||
)
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
rootCmd = NewDefaultKnCommandWithArgs(NewKnCommand(), pluginHandler, args, os.Stdin, os.Stdout, os.Stderr)
|
||||
}
|
||||
|
||||
t.Run("when pluginHandler is nil", func(t *testing.T) {
|
||||
args = []string{}
|
||||
setup(t)
|
||||
|
||||
t.Run("returns a valid root command", func(t *testing.T) {
|
||||
checkRootCmd(t, rootCmd)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when pluginHandler is not nil", func(t *testing.T) {
|
||||
t.Run("when args empty", func(t *testing.T) {
|
||||
args = []string{}
|
||||
setup(t)
|
||||
|
||||
t.Run("returns a valid root command", func(t *testing.T) {
|
||||
checkRootCmd(t, rootCmd)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when args not empty", func(t *testing.T) {
|
||||
var (
|
||||
pluginName, pluginPath, tmpPathDir string
|
||||
err error
|
||||
)
|
||||
|
||||
beforeEach := func(t *testing.T) {
|
||||
tmpPathDir, err = ioutil.TempDir("", "plugin_list")
|
||||
assert.Assert(t, err == nil)
|
||||
|
||||
pluginName = "fake-plugin-name"
|
||||
pluginPath = plugin.CreateTestPluginInPath(t, "kn-"+pluginName, plugin.KnTestPluginScript, plugin.FileModeExecutable, tmpPathDir)
|
||||
}
|
||||
|
||||
afterEach := func(t *testing.T) {
|
||||
err = os.RemoveAll(tmpPathDir)
|
||||
assert.Assert(t, err == nil)
|
||||
}
|
||||
|
||||
beforeEach(t)
|
||||
args = []string{pluginPath, pluginName}
|
||||
setup(t)
|
||||
defer afterEach(t)
|
||||
|
||||
t.Run("tries to handle args[1:] as plugin and return valid root command", func(t *testing.T) {
|
||||
checkRootCmd(t, rootCmd)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewKnCommand(t *testing.T) {
|
||||
var rootCmd *cobra.Command
|
||||
|
||||
setup := func() {
|
||||
setup := func(t *testing.T) {
|
||||
rootCmd = NewKnCommand(commands.KnParams{})
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
t.Run("returns a valid root command", func(t *testing.T) {
|
||||
assert.Assert(t, rootCmd != nil)
|
||||
|
||||
assert.Equal(t, rootCmd.Name(), "kn")
|
||||
assert.Equal(t, rootCmd.Short, "Knative client")
|
||||
assert.Assert(t, strings.Contains(rootCmd.Long, "Manage your Knative building blocks:"))
|
||||
|
||||
assert.Assert(t, rootCmd.DisableAutoGenTag)
|
||||
assert.Assert(t, rootCmd.SilenceUsage)
|
||||
assert.Assert(t, rootCmd.SilenceErrors)
|
||||
|
||||
assert.Assert(t, rootCmd.RunE == nil)
|
||||
setup(t)
|
||||
checkRootCmd(t, rootCmd)
|
||||
})
|
||||
|
||||
t.Run("sets the output params", func(t *testing.T) {
|
||||
setup(t)
|
||||
assert.Assert(t, rootCmd.OutOrStdout() != nil)
|
||||
})
|
||||
|
||||
t.Run("sets the config and kubeconfig global flags", func(t *testing.T) {
|
||||
setup(t)
|
||||
assert.Assert(t, rootCmd.PersistentFlags().Lookup("config") != nil)
|
||||
assert.Assert(t, rootCmd.PersistentFlags().Lookup("kubeconfig") != nil)
|
||||
})
|
||||
|
||||
t.Run("adds the top level commands: version and completion", func(t *testing.T) {
|
||||
setup(t)
|
||||
checkCommand(t, "version", rootCmd)
|
||||
checkCommand(t, "completion", rootCmd)
|
||||
})
|
||||
|
||||
t.Run("adds the top level group commands", func(t *testing.T) {
|
||||
setup(t)
|
||||
checkCommandGroup(t, "service", rootCmd)
|
||||
checkCommandGroup(t, "revision", rootCmd)
|
||||
})
|
||||
|
|
@ -69,7 +140,7 @@ func TestNewKnCommand(t *testing.T) {
|
|||
func TestEmptyAndUnknownSubCommands(t *testing.T) {
|
||||
var rootCmd, fakeCmd, fakeSubCmd *cobra.Command
|
||||
|
||||
setup := func() {
|
||||
setup := func(t *testing.T) {
|
||||
rootCmd = NewKnCommand(commands.KnParams{})
|
||||
fakeCmd = &cobra.Command{
|
||||
Use: "fake-cmd-name",
|
||||
|
|
@ -84,9 +155,8 @@ func TestEmptyAndUnknownSubCommands(t *testing.T) {
|
|||
assert.Assert(t, fakeSubCmd.RunE == nil)
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
t.Run("deals with empty and unknown sub-commands for all group commands", func(t *testing.T) {
|
||||
setup(t)
|
||||
EmptyAndUnknownSubCommands(rootCmd)
|
||||
checkCommand(t, "fake-sub-cmd-name", fakeCmd)
|
||||
checkCommandGroup(t, "fake-cmd-name", rootCmd)
|
||||
|
|
@ -95,6 +165,23 @@ func TestEmptyAndUnknownSubCommands(t *testing.T) {
|
|||
|
||||
// Private
|
||||
|
||||
func checkRootCmd(t *testing.T, rootCmd *cobra.Command) {
|
||||
assert.Assert(t, rootCmd != nil)
|
||||
|
||||
assert.Equal(t, rootCmd.Name(), "kn")
|
||||
assert.Equal(t, rootCmd.Short, "Knative client")
|
||||
assert.Assert(t, strings.Contains(rootCmd.Long, "Manage your Knative building blocks:"))
|
||||
|
||||
assert.Assert(t, rootCmd.DisableAutoGenTag)
|
||||
assert.Assert(t, rootCmd.SilenceUsage)
|
||||
assert.Assert(t, rootCmd.SilenceErrors)
|
||||
|
||||
assert.Assert(t, rootCmd.Flags().Lookup("plugins-dir") != nil)
|
||||
assert.Assert(t, rootCmd.Flags().Lookup("lookup-plugins-in-path") != nil)
|
||||
|
||||
assert.Assert(t, rootCmd.RunE == nil)
|
||||
}
|
||||
|
||||
func checkCommand(t *testing.T, name string, rootCmd *cobra.Command) {
|
||||
cmd, _, err := rootCmd.Find([]string{"version"})
|
||||
assert.Assert(t, err == nil)
|
||||
|
|
|
|||
|
|
@ -139,9 +139,9 @@ golang.org/x/oauth2/jwt
|
|||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
# golang.org/x/text v0.3.0
|
||||
golang.org/x/text/encoding/unicode
|
||||
golang.org/x/text/transform
|
||||
golang.org/x/text/unicode/norm
|
||||
golang.org/x/text/encoding/unicode
|
||||
golang.org/x/text/encoding
|
||||
golang.org/x/text/encoding/internal
|
||||
golang.org/x/text/encoding/internal/identifier
|
||||
|
|
|
|||
Loading…
Reference in New Issue