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
|
||||
}
|
||||
|
||||
// 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.
|
||||
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() {
|
||||
<-ctx.Done()
|
||||
c.progressListener.Stopping()
|
||||
|
|
|
@ -115,7 +115,7 @@ func TestDelete_NameAndPathExclusivity(t *testing.T) {
|
|||
if err == nil {
|
||||
// TODO should really either parse the output or use typed errors to ensure it's
|
||||
// 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.
|
||||
|
|
|
@ -12,32 +12,41 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
|
||||
fn "knative.dev/func"
|
||||
"knative.dev/func/config"
|
||||
)
|
||||
|
||||
func NewInfoCmd(newClient ClientFactory) *cobra.Command {
|
||||
func NewDescribeCmd(newClient ClientFactory) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "info <name>",
|
||||
Short: "Show details of a function",
|
||||
Long: `Show details of a function
|
||||
Use: "describe <name>",
|
||||
Short: "Describe 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.
|
||||
`,
|
||||
Example: `
|
||||
# Show the details of a function as declared in the local func.yaml
|
||||
{{.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
|
||||
`,
|
||||
SuggestFor: []string{"ifno", "describe", "fino", "get"},
|
||||
SuggestFor: []string{"ifno", "fino", "get"},
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
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.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
return runInfo(cmd, args, newClient)
|
||||
return runDescribe(cmd, args, newClient)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInfo(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
|
||||
config := newInfoConfig(args)
|
||||
func runDescribe(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
|
||||
cfg := newDescribeConfig(args)
|
||||
|
||||
function, err := fn.NewFunction(config.Path)
|
||||
if err != nil {
|
||||
if err = cfg.Validate(cmd); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the function has been initialized
|
||||
if !function.Initialized() {
|
||||
return fmt.Errorf("the given path '%v' does not contain an initialized function", config.Path)
|
||||
var f fn.Function
|
||||
|
||||
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: config.Namespace, Verbose: config.Verbose})
|
||||
client, done := newClient(ClientConfig{Namespace: cfg.Namespace, Verbose: cfg.Verbose})
|
||||
defer done()
|
||||
|
||||
// Get the description
|
||||
d, err := client.Info(cmd.Context(), config.Name, config.Path)
|
||||
// TODO(lkingland): update API to use the above function instance rather than path
|
||||
d, err := client.Describe(cmd.Context(), cfg.Name, f.Root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.Image = function.Image
|
||||
|
||||
write(os.Stdout, info(d), config.Output)
|
||||
write(os.Stdout, info(d), cfg.Output)
|
||||
return
|
||||
}
|
||||
|
||||
// CLI Configuration (parameters)
|
||||
// ------------------------------
|
||||
|
||||
type infoConfig struct {
|
||||
type describeConfig struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Output string
|
||||
|
@ -92,18 +114,24 @@ type infoConfig struct {
|
|||
Verbose bool
|
||||
}
|
||||
|
||||
func newInfoConfig(args []string) infoConfig {
|
||||
var name string
|
||||
if len(args) > 0 {
|
||||
name = args[0]
|
||||
}
|
||||
return infoConfig{
|
||||
Name: deriveName(name, getPathFlag()),
|
||||
func newDescribeConfig(args []string) describeConfig {
|
||||
c := describeConfig{
|
||||
Namespace: viper.GetString("namespace"),
|
||||
Output: viper.GetString("output"),
|
||||
Path: getPathFlag(),
|
||||
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)
|
|
@ -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),
|
||||
NewDeleteCmd(newClient),
|
||||
NewDeployCmd(newClient),
|
||||
NewInfoCmd(newClient),
|
||||
NewDescribeCmd(newClient),
|
||||
NewInvokeCmd(newClient),
|
||||
NewLanguagesCmd(newClient),
|
||||
NewListCmd(newClient),
|
||||
|
|
|
@ -42,7 +42,7 @@ EXAMPLES
|
|||
* [func create](func_create.md) - Create a function project
|
||||
* [func delete](func_delete.md) - Undeploy 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 languages](func_languages.md) - List available function language runtimes
|
||||
* [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