mirror of https://github.com/knative/func.git
feat: describe command namespace (#1381)
* fix: describe function - Fixes error describing by name - Adds ability to specify namespace - Fixes inconsistency between Describe and Info * fix misspelling * clear test cmd args * remove old doc file * docs cleanup * test describe with no kubeconfig
This commit is contained in:
parent
796e02984d
commit
e9fb274969
|
@ -761,9 +761,9 @@ func (c *Client) Run(ctx context.Context, root string) (job *Job, err error) {
|
||||||
return job, nil
|
return job, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info for a function. Name takes precedence. If no name is provided,
|
// Describe a function. Name takes precedence. If no name is provided,
|
||||||
// the function defined at root is used.
|
// the function defined at root is used.
|
||||||
func (c *Client) Info(ctx context.Context, name, root string) (d Instance, err error) {
|
func (c *Client) Describe(ctx context.Context, name, root string) (d Instance, err error) {
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
c.progressListener.Stopping()
|
c.progressListener.Stopping()
|
||||||
|
|
|
@ -115,7 +115,7 @@ func TestDelete_NameAndPathExclusivity(t *testing.T) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO should really either parse the output or use typed errors to ensure it's
|
// TODO should really either parse the output or use typed errors to ensure it's
|
||||||
// failing for the expected reason.
|
// failing for the expected reason.
|
||||||
t.Fatal(err)
|
t.Fatalf("expected error on conflicting flags not received")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also fail if remover's .Remove is invoked.
|
// Also fail if remover's .Remove is invoked.
|
||||||
|
|
|
@ -12,32 +12,41 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
fn "knative.dev/func"
|
fn "knative.dev/func"
|
||||||
|
"knative.dev/func/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewInfoCmd(newClient ClientFactory) *cobra.Command {
|
func NewDescribeCmd(newClient ClientFactory) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "info <name>",
|
Use: "describe <name>",
|
||||||
Short: "Show details of a function",
|
Short: "Describe a Function",
|
||||||
Long: `Show details of a function
|
Long: `Describe a Function
|
||||||
|
|
||||||
Prints the name, route and any event subscriptions for a deployed function in
|
Prints the name, route and event subscriptions for a deployed function in
|
||||||
the current directory or from the directory specified with --path.
|
the current directory or from the directory specified with --path.
|
||||||
`,
|
`,
|
||||||
Example: `
|
Example: `
|
||||||
# Show the details of a function as declared in the local func.yaml
|
# Show the details of a function as declared in the local func.yaml
|
||||||
{{.Name}} info
|
{{.Name}} info
|
||||||
|
|
||||||
# Show the details of the function in the myotherfunc directory with yaml output
|
# Show the details of the function in the directory with yaml output
|
||||||
{{.Name}} info --output yaml --path myotherfunc
|
{{.Name}} info --output yaml --path myotherfunc
|
||||||
`,
|
`,
|
||||||
SuggestFor: []string{"ifno", "describe", "fino", "get"},
|
SuggestFor: []string{"ifno", "fino", "get"},
|
||||||
|
|
||||||
ValidArgsFunction: CompleteFunctionList,
|
ValidArgsFunction: CompleteFunctionList,
|
||||||
PreRunE: bindEnv("output", "path"),
|
Aliases: []string{"info", "desc"},
|
||||||
|
PreRunE: bindEnv("output", "path", "namespace"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config
|
||||||
|
cfg, err := config.NewDefault()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(cmd.OutOrStdout(), "error loading config at '%v'. %v\n", config.File(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flags
|
// Flags
|
||||||
cmd.Flags().StringP("output", "o", "human", "Output format (human|plain|json|xml|yaml|url) (Env: $FUNC_OUTPUT)")
|
cmd.Flags().StringP("output", "o", "human", "Output format (human|plain|json|xml|yaml|url) (Env: $FUNC_OUTPUT)")
|
||||||
cmd.Flags().StringP("namespace", "n", "", "The namespace in which to look for the named function. (Env: $FUNC_NAMESPACE)")
|
cmd.Flags().StringP("namespace", "n", cfg.Namespace, "The namespace in which to look for the named function. (Env: $FUNC_NAMESPACE)")
|
||||||
setPathFlag(cmd)
|
setPathFlag(cmd)
|
||||||
|
|
||||||
if err := cmd.RegisterFlagCompletionFunc("output", CompleteOutputFormatList); err != nil {
|
if err := cmd.RegisterFlagCompletionFunc("output", CompleteOutputFormatList); err != nil {
|
||||||
|
@ -47,44 +56,57 @@ the current directory or from the directory specified with --path.
|
||||||
cmd.SetHelpFunc(defaultTemplatedHelp)
|
cmd.SetHelpFunc(defaultTemplatedHelp)
|
||||||
|
|
||||||
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||||
return runInfo(cmd, args, newClient)
|
return runDescribe(cmd, args, newClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInfo(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
|
func runDescribe(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
|
||||||
config := newInfoConfig(args)
|
cfg := newDescribeConfig(args)
|
||||||
|
|
||||||
function, err := fn.NewFunction(config.Path)
|
if err = cfg.Validate(cmd); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the function has been initialized
|
var f fn.Function
|
||||||
if !function.Initialized() {
|
|
||||||
return fmt.Errorf("the given path '%v' does not contain an initialized function", config.Path)
|
if cfg.Name == "" {
|
||||||
|
if f, err = fn.NewFunction(cfg.Path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !f.Initialized() {
|
||||||
|
return fmt.Errorf("the given path '%v' does not contain an initialized function.", cfg.Path)
|
||||||
|
}
|
||||||
|
// Use Function's Namespace with precidence
|
||||||
|
//
|
||||||
|
// Unless the namespace flag was explicitly provided (not the default),
|
||||||
|
// use the function's current namespace.
|
||||||
|
//
|
||||||
|
// TODO(lkingland): this stanza can be removed when Global Config: Function
|
||||||
|
// Context is merged.
|
||||||
|
if !cmd.Flags().Changed("namespace") {
|
||||||
|
cfg.Namespace = f.Deploy.Namespace
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a client
|
client, done := newClient(ClientConfig{Namespace: cfg.Namespace, Verbose: cfg.Verbose})
|
||||||
client, done := newClient(ClientConfig{Namespace: config.Namespace, Verbose: config.Verbose})
|
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
// Get the description
|
// TODO(lkingland): update API to use the above function instance rather than path
|
||||||
d, err := client.Info(cmd.Context(), config.Name, config.Path)
|
d, err := client.Describe(cmd.Context(), cfg.Name, f.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.Image = function.Image
|
|
||||||
|
|
||||||
write(os.Stdout, info(d), config.Output)
|
write(os.Stdout, info(d), cfg.Output)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLI Configuration (parameters)
|
// CLI Configuration (parameters)
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
||||||
type infoConfig struct {
|
type describeConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Namespace string
|
Namespace string
|
||||||
Output string
|
Output string
|
||||||
|
@ -92,18 +114,24 @@ type infoConfig struct {
|
||||||
Verbose bool
|
Verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInfoConfig(args []string) infoConfig {
|
func newDescribeConfig(args []string) describeConfig {
|
||||||
var name string
|
c := describeConfig{
|
||||||
if len(args) > 0 {
|
|
||||||
name = args[0]
|
|
||||||
}
|
|
||||||
return infoConfig{
|
|
||||||
Name: deriveName(name, getPathFlag()),
|
|
||||||
Namespace: viper.GetString("namespace"),
|
Namespace: viper.GetString("namespace"),
|
||||||
Output: viper.GetString("output"),
|
Output: viper.GetString("output"),
|
||||||
Path: getPathFlag(),
|
Path: getPathFlag(),
|
||||||
Verbose: viper.GetBool("verbose"),
|
Verbose: viper.GetBool("verbose"),
|
||||||
}
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
c.Name = args[0]
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c describeConfig) Validate(cmd *cobra.Command) (err error) {
|
||||||
|
if c.Name != "" && c.Path != "" && cmd.Flags().Changed("path") {
|
||||||
|
return fmt.Errorf("Only one of --path or [NAME] should be provided")
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output Formatting (serializers)
|
// Output Formatting (serializers)
|
|
@ -0,0 +1,145 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
fn "knative.dev/func"
|
||||||
|
"knative.dev/func/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDescribe_ByName ensures that describing a function by name invokes
|
||||||
|
// the describer appropriately.
|
||||||
|
func TestDescribe_ByName(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testname = "testname"
|
||||||
|
describer = mock.NewDescriber()
|
||||||
|
)
|
||||||
|
|
||||||
|
describer.DescribeFn = func(n string) (fn.Instance, error) {
|
||||||
|
if n != testname {
|
||||||
|
t.Fatalf("expected describe name '%v', got '%v'", testname, n)
|
||||||
|
}
|
||||||
|
return fn.Instance{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := NewDescribeCmd(NewClientFactory(func() *fn.Client {
|
||||||
|
return fn.New(fn.WithDescriber(describer))
|
||||||
|
}))
|
||||||
|
cmd.SetArgs([]string{testname})
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !describer.DescribeInvoked {
|
||||||
|
t.Fatal("Describer not invoked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDescribe_ByProject ensures that describing the currently active project
|
||||||
|
// (func created in the current working directory) invokes the describer with
|
||||||
|
// its name correctly.
|
||||||
|
func TestDescribe_ByProject(t *testing.T) {
|
||||||
|
root := fromTempDirectory(t)
|
||||||
|
|
||||||
|
err := fn.New().Create(fn.Function{
|
||||||
|
Name: "testname",
|
||||||
|
Runtime: "go",
|
||||||
|
Registry: TestRegistry,
|
||||||
|
Root: root,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
describer := mock.NewDescriber()
|
||||||
|
describer.DescribeFn = func(n string) (i fn.Instance, err error) {
|
||||||
|
if n != "testname" {
|
||||||
|
t.Fatalf("expected describer to receive name 'testname', got '%v'", n)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd := NewDescribeCmd(NewClientFactory(func() *fn.Client {
|
||||||
|
return fn.New(fn.WithDescriber(describer))
|
||||||
|
}))
|
||||||
|
cmd.SetArgs([]string{})
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDescribe_NameAndPathExclusivity ensures that providing both a name
|
||||||
|
// and a path will generate an error.
|
||||||
|
func TestDescribe_NameAndPathExclusivity(t *testing.T) {
|
||||||
|
d := mock.NewDescriber()
|
||||||
|
cmd := NewDescribeCmd(NewClientFactory(func() *fn.Client {
|
||||||
|
return fn.New(fn.WithDescriber(d))
|
||||||
|
}))
|
||||||
|
cmd.SetArgs([]string{"-p", "./testpath", "testname"})
|
||||||
|
if err := cmd.Execute(); err == nil {
|
||||||
|
// TODO(lkingland): use a typed error
|
||||||
|
t.Fatalf("expected error on conflicting flags not received")
|
||||||
|
}
|
||||||
|
if d.DescribeInvoked {
|
||||||
|
t.Fatal("describer was invoked when conflicting flags were provided")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDescribe_Namespace ensures that the namespace provided to the client
|
||||||
|
// for use when describing a function is set
|
||||||
|
// 1. The flag /env variable if provided
|
||||||
|
// 2. The namespace of the function at path if provided
|
||||||
|
// 3. The user's current active namespace
|
||||||
|
func TestDescribe_Namespace(t *testing.T) {
|
||||||
|
root := fromTempDirectory(t)
|
||||||
|
|
||||||
|
client := fn.New(fn.WithDescriber(mock.NewDescriber()))
|
||||||
|
|
||||||
|
// Ensure that the default is "default" when no context can be identified
|
||||||
|
t.Setenv("KUBECONFIG", filepath.Join(cwd(), "nonexistent"))
|
||||||
|
cmd := NewDescribeCmd(func(cc ClientConfig, _ ...fn.Option) (*fn.Client, func()) {
|
||||||
|
if cc.Namespace != "default" {
|
||||||
|
t.Fatalf("expected 'default', got '%v'", cc.Namespace)
|
||||||
|
}
|
||||||
|
return client, func() {}
|
||||||
|
})
|
||||||
|
cmd.SetArgs([]string{"somefunc"}) // by name such that no f need be created
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the extant function's namespace is used
|
||||||
|
f := fn.Function{
|
||||||
|
Root: root,
|
||||||
|
Runtime: "go",
|
||||||
|
Deploy: fn.DeploySpec{
|
||||||
|
Namespace: "deployed",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := client.Create(f); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cmd = NewDescribeCmd(func(cc ClientConfig, _ ...fn.Option) (*fn.Client, func()) {
|
||||||
|
if cc.Namespace != "deployed" {
|
||||||
|
t.Fatalf("expected 'deployed', got '%v'", cc.Namespace)
|
||||||
|
}
|
||||||
|
return client, func() {}
|
||||||
|
})
|
||||||
|
cmd.SetArgs([]string{})
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure an explicit namespace is plumbed through
|
||||||
|
cmd = NewDescribeCmd(func(cc ClientConfig, _ ...fn.Option) (*fn.Client, func()) {
|
||||||
|
if cc.Namespace != "ns" {
|
||||||
|
t.Fatalf("expected 'ns', got '%v'", cc.Namespace)
|
||||||
|
}
|
||||||
|
return client, func() {}
|
||||||
|
})
|
||||||
|
cmd.SetArgs([]string{"--namespace", "ns"})
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -97,7 +97,7 @@ EXAMPLES
|
||||||
NewCreateCmd(newClient),
|
NewCreateCmd(newClient),
|
||||||
NewDeleteCmd(newClient),
|
NewDeleteCmd(newClient),
|
||||||
NewDeployCmd(newClient),
|
NewDeployCmd(newClient),
|
||||||
NewInfoCmd(newClient),
|
NewDescribeCmd(newClient),
|
||||||
NewInvokeCmd(newClient),
|
NewInvokeCmd(newClient),
|
||||||
NewLanguagesCmd(newClient),
|
NewLanguagesCmd(newClient),
|
||||||
NewListCmd(newClient),
|
NewListCmd(newClient),
|
||||||
|
|
|
@ -42,7 +42,7 @@ EXAMPLES
|
||||||
* [func create](func_create.md) - Create a function project
|
* [func create](func_create.md) - Create a function project
|
||||||
* [func delete](func_delete.md) - Undeploy a function
|
* [func delete](func_delete.md) - Undeploy a function
|
||||||
* [func deploy](func_deploy.md) - Deploy a Function
|
* [func deploy](func_deploy.md) - Deploy a Function
|
||||||
* [func info](func_info.md) - Show details of a function
|
* [func describe](func_describe.md) - Describe a Function
|
||||||
* [func invoke](func_invoke.md) - Invoke a function
|
* [func invoke](func_invoke.md) - Invoke a function
|
||||||
* [func languages](func_languages.md) - List available function language runtimes
|
* [func languages](func_languages.md) - List available function language runtimes
|
||||||
* [func list](func_list.md) - List functions
|
* [func list](func_list.md) - List functions
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
## func describe
|
||||||
|
|
||||||
|
Describe a Function
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Describe a Function
|
||||||
|
|
||||||
|
Prints the name, route and event subscriptions for a deployed function in
|
||||||
|
the current directory or from the directory specified with --path.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
func describe <name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# Show the details of a function as declared in the local func.yaml
|
||||||
|
func info
|
||||||
|
|
||||||
|
# Show the details of the function in the directory with yaml output
|
||||||
|
func info --output yaml --path myotherfunc
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for describe
|
||||||
|
-n, --namespace string The namespace in which to look for the named function. (Env: $FUNC_NAMESPACE) (default "default")
|
||||||
|
-o, --output string Output format (human|plain|json|xml|yaml|url) (Env: $FUNC_OUTPUT) (default "human")
|
||||||
|
-p, --path string Path to the project directory (Env: $FUNC_PATH) (default ".")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
-v, --verbose Print verbose logs ($FUNC_VERBOSE)
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [func](func.md) - Serverless functions
|
||||||
|
|
Loading…
Reference in New Issue