Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ the current directory or from the directory specified with --path.
Aliases: []string{"info", "desc"},
PreRunE: bindEnv("output", "path", "namespace", "verbose"),
RunE: func(cmd *cobra.Command, args []string) error {
return runDescribe(cmd, args, newClient)
return wrapDescribeError(runDescribe(cmd, args, newClient))
},
}

Expand Down
27 changes: 27 additions & 0 deletions cmd/describe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,30 @@ func TestDescribe_NameAndPathExclusivity(t *testing.T) {
t.Fatal("describer was invoked when conflicting flags were provided")
}
}

//TestDescribe_WrapsNotInitialized ensures that describe command wraps
//ErrNotInitialized with CLI-specific guidance via wrapDescribeError.

func TestDescribe_WrapsNotInitialized(t *testing.T) {
_ = FromTempDirectory(t)
describer := mock.NewDescriber()

cmd := NewDescribeCmd(NewTestClient(fn.WithDescribers(describer)))
cmd.SetArgs([]string{})
err := cmd.Execute()

if err == nil {
t.Fatal("expected an error when describing a non-existent function")
}
var cliNotInit *ErrNotInitialized
if !errors.As(err, &cliNotInit) {
t.Fatalf("expected ErrNotInitialized, got %T: %v", err, err)
}
if cliNotInit.Cmd != "describe" {
t.Fatalf("expected Cmd 'describe', got '%v'", cliNotInit.Cmd)
}
if !strings.Contains(err.Error(), "func describe") {
t.Fatalf("expected error to contain 'func describe' guidance, got: %v", err)
}

}
125 changes: 122 additions & 3 deletions cmd/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,46 @@ func wrapDeleteError(err error) error {
return err
}

// wrapDescribeError wraps errors from describe command with CLI-specific guidance
func wrapDescribeError(err error) error {
if err == nil {
return nil
}

var cliNotInit *ErrNotInitialized
if errors.As(err, &cliNotInit) {
return err
}

var coreNotInit *fn.ErrNotInitialized
if errors.As(err, &coreNotInit) {
return NewErrNotInitialized(err, "describe")
}

if errors.Is(err, fn.ErrClusterNotAccessible) {
return NewErrDescribeClusterConnection(err)
}

return err
}

func wrapInvokeError(err error) error {
if err == nil {
return nil
}

var cliNotInit *ErrNotInitialized
if errors.As(err, &cliNotInit) {
return NewErrNotInitialized(err, "invoke")
}

if errors.Is(err, fn.ErrNotRunning) {
return NewErrInvokeNotRunning(err)
}

return err
}

// ---------------------------- TYPES AND METHODS --------------------------- //

type ErrPlatformNotSupported struct {
Expand Down Expand Up @@ -400,18 +440,33 @@ You need to be in a function directory (or use --path).

Try this:
func create --language go myfunction Create a new function
cd myfunction Go into the function directory
cd myfunction Go into the function directory
func delete Delete the deployed function

Or if you have an existing function:
cd path/to/your/function Go to your function directory
func delete Delete the deployed function
cd path/to/your/function Go to your function directory
func delete Delete the deployed function

Or use --path to delete from anywhere:
func delete --path /path/to/function

For more options, run 'func delete --help'`, e.Err)

case "invoke":
return fmt.Sprintf(`%v
No function found in provided path.
You need to be inside a function directory to invoke it (or use --path).

Try this:
func create --language go myfunction Create a new function
cd myfunction Go into the function directory
func invoke Invoke the function

Of if you have an existing function:
cd path/to/you/function Go to your function directory
func invoke Invoke the function
For more options, run 'func invoke --help'`, e.Err)

default:
return e.Err.Error()
}
Expand Down Expand Up @@ -707,3 +762,67 @@ Installation guide: https://knative.dev/docs/serving/#installation`, e.Err)
func (e *ErrListClusterConnection) Unwrap() error {
return e.Err
}

// -------------------------------------------------------------------------- //

type ErrDescribeClusterConnection struct {
Err error
}

func NewErrDescribeClusterConnection(err error) error {
return &ErrDescribeClusterConnection{Err: err}
}

func (e *ErrDescribeClusterConnection) Error() string {
return fmt.Sprintf(`%v
Cannot connect to Knative cluster

The 'func describe' command shows details about a deployed function.

To use this command, you need:
1. A running Kubernetes cluster
2. Knative Serving installed on the cluster
3. kubectl configured to access your cluster

Troubleshooting:
kubectl cluster-info Verify cluster is accessible
kubectl config current-context Verify cluster connection
kubectl get ksvc --all-namespaces List deployed Knative services

For more options, run 'func describe --help'`, e.Err)
}

func (e *ErrDescribeClusterConnection) Unwrap() error {
return e.Err
}

// -------------------------------------------------------------------------- //

type ErrInvokeNotRunning struct {
Err error
}

func NewErrInvokeNotRunning(err error) error {
return &ErrInvokeNotRunning{Err: err}
}

func (e *ErrInvokeNotRunning) Error() string {
return fmt.Sprintf(`%v
No running function instance found to invoke.

The 'func invoke' command sends test data to a running function.

Try this:
func run Start the function locally
func invoke Then invoke it

Or deploy and invoke remote:
func deploy --registry <registry> Deploy to cluster
func invoke --target=remote Invoke the remote instance

For more options, run 'func invoke --help'`, e.Err)
}

func (e *ErrInvokeNotRunning) Unwrap() error {
return e.Err
}
4 changes: 2 additions & 2 deletions cmd/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ EXAMPLES
"data", "content-type", "request-type", "file", "insecure",
"confirm", "verbose"),
RunE: func(cmd *cobra.Command, args []string) error {
return runInvoke(cmd, args, newClient)
return wrapInvokeError(runInvoke(cmd, args, newClient))
},
}

Expand Down Expand Up @@ -162,7 +162,7 @@ func runInvoke(cmd *cobra.Command, _ []string, newClient ClientFactory) (err err
}

if !f.Initialized() {
return fmt.Errorf("no function found in current directory.\nYou need to be inside a function directory to invoke it.\n\nTry this:\n func create --language go myfunction Create a new function\n cd myfunction Go into the function directory\n func invoke Now you can invoke it\n\nOr if you have an existing function:\n cd path/to/your/function Go to your function directory\n func invoke Invoke the function")
return NewErrNotInitializedFromPath(f.Root, "invoke")
}

// Client instance from env vars, flags, args and user prompts (if --confirm)
Expand Down
24 changes: 24 additions & 0 deletions cmd/invoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net"
"net/http"
"os"
"strings"
"sync/atomic"
"testing"
"time"
Expand Down Expand Up @@ -78,3 +79,26 @@ func TestInvoke(t *testing.T) {
t.Fatal("function was not invoked")
}
}

// TestInvoke_WrapsNotInitalized ensures invoke wraps uninitialized errors
// through the CLI error wrapping layer instead of inline fmt.Errorf.

func TestInvoke_WrapsNotInitialized(t *testing.T) {
_ = FromTempDirectory(t) // empty dir, no function
cmd := NewInvokeCmd(NewTestClient())
cmd.SetArgs([]string{})
err := cmd.Execute()
if err == nil {
t.Fatal("expected error when invoking from empty directory")
}
var cliNotInit *ErrNotInitialized
if !errors.As(err, &cliNotInit) {
t.Fatalf("expected ErrNotInitialized, got %T: %v", err, err)
}
if cliNotInit.Cmd != "invoke" {
t.Fatalf("expected Cmd 'invoke', got '%v'", cliNotInit.Cmd)
}
if !strings.Contains(err.Error(), "func invoke") {
t.Fatalf("expected error to contain 'func invoke' guidance, got: %v", err)
}
}
2 changes: 2 additions & 0 deletions pkg/mcp/tools_healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func (s *Server) healthcheckHandler(ctx context.Context, r *mcp.CallToolRequest,
output = HealthcheckOutput{
Status: "ok",
Message: "The MCP server is running!",
Version: version,
}
return
}
Expand All @@ -33,4 +34,5 @@ type HealthcheckInput struct{}
type HealthcheckOutput struct {
Status string `json:"status" jsonschema:"Status of the server (ok)"`
Message string `json:"message" jsonschema:"Healthcheck message"`
Version string `json:"version" jsonschema:"Version of the MCP server"`
}
4 changes: 4 additions & 0 deletions pkg/mcp/tools_healthcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,8 @@ func TestTool_Healthcheck(t *testing.T) {
if !strings.Contains(output.Message, "running") {
t.Errorf("expected message to contain 'running', got %q", output.Message)
}

if output.Version == "" {
t.Error("expected non-empty version")
}
}
Binary file added test_output.txt
Binary file not shown.
Loading