mirror of https://github.com/knative/func.git
Make `func invoke` print any response it receives. (#881)
* Make `func invoke` print any response it receives. Signed-off-by: Lance Ball <lball@redhat.com> * fixup: improve test Signed-off-by: Lance Ball <lball@redhat.com> * fixup: check Write() return value Signed-off-by: Lance Ball <lball@redhat.com>
This commit is contained in:
parent
5a122c31e6
commit
6d7ab83aed
|
@ -846,7 +846,7 @@ func (c *Client) Remove(ctx context.Context, cfg Function, deleteAll bool) error
|
|||
// See NewInvokeMessage for its defaults.
|
||||
// Functions are invoked in a manner consistent with the settings defined in
|
||||
// their metadata. For example HTTP vs CloudEvent
|
||||
func (c *Client) Invoke(ctx context.Context, root string, target string, m InvokeMessage) (err error) {
|
||||
func (c *Client) Invoke(ctx context.Context, root string, target string, m InvokeMessage) (s string, err error) {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
c.progressListener.Stopping()
|
||||
|
|
|
@ -1099,6 +1099,10 @@ func TestClient_Invoke_HTTP(t *testing.T) {
|
|||
if req.Form.Get("ID") != message.ID {
|
||||
t.Fatalf("expected message ID '%v', got '%v'", message.ID, req.Form.Get("ID"))
|
||||
}
|
||||
_, err := res.Write([]byte("hello world"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Expose the masquarading Function on an OS-chosen port.
|
||||
|
@ -1139,10 +1143,16 @@ func TestClient_Invoke_HTTP(t *testing.T) {
|
|||
defer job.Stop()
|
||||
|
||||
// Invoke the Function, which will use the mock Runner
|
||||
if err := client.Invoke(context.Background(), f.Root, "", message); err != nil {
|
||||
r, err := client.Invoke(context.Background(), f.Root, "", message)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check the response value
|
||||
if r != "hello world" {
|
||||
t.Fatal("Unexpected response from function " + r)
|
||||
}
|
||||
|
||||
// Fail if the Function was never invoked.
|
||||
if !invoked {
|
||||
t.Fatal("Function was not invoked")
|
||||
|
@ -1166,15 +1176,18 @@ func TestClient_Invoke_CloudEvent(t *testing.T) {
|
|||
invoked bool // flag the Function was invoked
|
||||
ctx = context.Background()
|
||||
message = fn.NewInvokeMessage() // message to send to the Function
|
||||
evt *cloudevents.Event // A pointer to the received event
|
||||
)
|
||||
|
||||
// A CloudEvent Receiver which masquarades as a running Function and
|
||||
// verifies the invoker sent the message as a populated CloudEvent.
|
||||
receiver := func(ctx context.Context, event cloudevents.Event) {
|
||||
receiver := func(ctx context.Context, event cloudevents.Event) *cloudevents.Event {
|
||||
invoked = true
|
||||
if event.ID() != message.ID {
|
||||
t.Fatalf("expected event ID '%v', got '%v'", message.ID, event.ID())
|
||||
}
|
||||
evt = &event
|
||||
return evt
|
||||
}
|
||||
|
||||
// A cloudevent receive handler which will expect the HTTP protocol
|
||||
|
@ -1224,10 +1237,14 @@ func TestClient_Invoke_CloudEvent(t *testing.T) {
|
|||
defer job.Stop()
|
||||
|
||||
// Invoke the Function, which will use the mock Runner
|
||||
if err := client.Invoke(context.Background(), f.Root, "", message); err != nil {
|
||||
r, err := client.Invoke(context.Background(), f.Root, "", message)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test the contents of the returned string.
|
||||
if r != evt.String() {
|
||||
t.Fatal("Invoke failed to return a response")
|
||||
}
|
||||
// Fail if the Function was never invoked.
|
||||
if !invoked {
|
||||
t.Fatal("Function was not invoked")
|
||||
|
|
|
@ -160,12 +160,12 @@ func runInvoke(cmd *cobra.Command, args []string, newClient ClientFactory) (err
|
|||
}
|
||||
|
||||
// Invoke
|
||||
err = client.Invoke(cmd.Context(), cfg.Path, cfg.Target, m)
|
||||
s, err := client.Invoke(cmd.Context(), cfg.Path, cfg.Target, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(cmd.OutOrStderr(), "Invoked %v\n", cfg.Target)
|
||||
fmt.Fprintln(cmd.OutOrStderr(), s)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
25
invoke.go
25
invoke.go
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
@ -45,13 +46,13 @@ func NewInvokeMessage() InvokeMessage {
|
|||
|
||||
// invoke the Function instance in the target environment with the
|
||||
// invocation message.
|
||||
func invoke(ctx context.Context, c *Client, f Function, target string, m InvokeMessage) error {
|
||||
func invoke(ctx context.Context, c *Client, f Function, target string, m InvokeMessage) (string, error) {
|
||||
|
||||
// Get the first available route from 'local', 'remote', a named environment
|
||||
// or treat target
|
||||
route, err := invocationRoute(ctx, c, f, target) // choose instance to invoke
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Format" either 'http' or 'cloudevent'
|
||||
|
@ -75,7 +76,7 @@ func invoke(ctx context.Context, c *Client, f Function, target string, m InvokeM
|
|||
case "cloudevent":
|
||||
return sendEvent(ctx, route, m, c.transport)
|
||||
default:
|
||||
return fmt.Errorf("format '%v' not supported.", format)
|
||||
return "", fmt.Errorf("format '%v' not supported.", format)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,7 +131,7 @@ func invocationRoute(ctx context.Context, c *Client, f Function, target string)
|
|||
}
|
||||
|
||||
// sendEvent to the route populated with data in the invoke message.
|
||||
func sendEvent(ctx context.Context, route string, m InvokeMessage, t http.RoundTripper) (err error) {
|
||||
func sendEvent(ctx context.Context, route string, m InvokeMessage, t http.RoundTripper) (resp string, err error) {
|
||||
event := cloudevents.NewEvent()
|
||||
event.SetID(m.ID)
|
||||
event.SetSource(m.Source)
|
||||
|
@ -146,15 +147,18 @@ func sendEvent(ctx context.Context, route string, m InvokeMessage, t http.RoundT
|
|||
return
|
||||
}
|
||||
|
||||
result := c.Send(cloudevents.ContextWithTarget(ctx, route), event)
|
||||
evt, result := c.Request(cloudevents.ContextWithTarget(ctx, route), event)
|
||||
if cloudevents.IsUndelivered(result) {
|
||||
err = fmt.Errorf("unable to invoke: %v", result)
|
||||
} else if evt != nil { // Check for nil in case no event is returned
|
||||
resp = evt.String()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// sendPost to the route populated with data in the invoke message.
|
||||
func sendPost(ctx context.Context, route string, m InvokeMessage, t http.RoundTripper) error {
|
||||
func sendPost(ctx context.Context, route string, m InvokeMessage, t http.RoundTripper) (string, error) {
|
||||
client := http.Client{
|
||||
Transport: t,
|
||||
Timeout: 10 * time.Second,
|
||||
|
@ -167,11 +171,12 @@ func sendPost(ctx context.Context, route string, m InvokeMessage, t http.RoundTr
|
|||
"Data": {m.Data},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("failure invoking '%v' (HTTP %v)", route, resp.StatusCode)
|
||||
if resp.StatusCode > 299 {
|
||||
return "", fmt.Errorf("failure invoking '%v' (HTTP %v)", route, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
return string(b), err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue