Repository: knative-sandbox/kn-plugin-func
Pull request #881 (2022-03-04 18:25:38)

Make `func invoke` print any response it receives.
# Changes

- :gift: Make `func invoke` print any response it receives

/kind enhancement

**Release Note**

```release-note
`func invoke` now prints any response it receives from the invoked function
```



knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative-sandbox/kn-plugin-func/pull/881#" title="Author self-approved">lance</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative-sandbox%2Fkn-plugin-func).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative-sandbox/kn-plugin-func/blob/main/OWNERS)~~ [lance]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) Report
> Merging [#881](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) (9322264) into [main](https://codecov.io/gh/knative-sandbox/kn-plugin-func/commit/1e4d52be33f68df2137d63e57475994f1353428f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) (1e4d52b) will **decrease** coverage by `0.00%`.
> The diff coverage is `25.00%`.

[![Impacted file tree graph](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881/graphs/tree.svg?width=650&height=150&src=pr&token=da1B9bMsQ9&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox)](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox)

```diff
@@            Coverage Diff             @@
##             main     #881      +/-   ##
==========================================
- Coverage   45.18%   45.17%   -0.01%     
==========================================
  Files          48       48              
  Lines        4818     4821       +3     
==========================================
+ Hits         2177     2178       +1     
- Misses       2342     2343       +1     
- Partials      299      300       +1     
```


| [Impacted Files](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) | Coverage Δ | |
|---|---|---|
| [client.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y2xpZW50Lmdv) | `60.33% <ø> (ø)` | |
| [cmd/invoke.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2ludm9rZS5nbw==) | `35.90% <0.00%> (ø)` | |
| [invoke.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-aW52b2tlLmdv) | `48.78% <30.00%> (-0.59%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox). Last update [1e4d52b...9322264](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/881?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox).


Patch:
diff --git a/buildpacks/builder.go b/buildpacks/builder.go
index 0612ade0..fb6cbd26 100644
--- a/buildpacks/builder.go
+++ b/buildpacks/builder.go
@@ -24,12 +24,12 @@ import (
 //Builder holds the configuration that will be passed to
 //Buildpack builder
 type Builder struct {
-	Verbose bool
+	verbose bool
 }
 
 //NewBuilder builds the new Builder configuration
-func NewBuilder() *Builder {
-	return &Builder{}
+func NewBuilder(verbose bool) *Builder {
+	return &Builder{verbose: verbose}
 }
 
 var v330 = semver.MustParse("v3.3.0")
@@ -57,7 +57,7 @@ func (builder *Builder) Build(ctx context.Context, f fn.Function) (err error) {
 
 	// log output is either STDOUt or kept in a buffer to be printed on error.
 	var logWriter io.Writer
-	if builder.Verbose {
+	if builder.verbose {
 		// pass stdout as non-closeable writer
 		// otherwise pack client would close it which is bad
 		logWriter = stdoutWrapper{os.Stdout}
@@ -130,7 +130,7 @@ func (builder *Builder) Build(ctx context.Context, f fn.Function) (err error) {
 		if ctx.Err() != nil {
 			// received SIGINT
 			return
-		} else if !builder.Verbose {
+		} else if !builder.verbose {
 			// If the builder was not showing logs, embed the full logs in the error.
 			err = fmt.Errorf("%v\noutput: %s\n", err, logWriter.(*bytes.Buffer).String())
 		}
diff --git a/client.go b/client.go
index 9d4f0a83..dde8e477 100644
--- a/client.go
+++ b/client.go
@@ -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()
diff --git a/cmd/client.go b/cmd/client.go
index f2877d92..ed793faf 100644
--- a/cmd/client.go
+++ b/cmd/client.go
@@ -52,11 +52,10 @@ func NewDefaultClientFactory() (newClient ClientFactory, cleanUp func() error) {
 	}
 
 	newClient = func(clientOptions ClientOptions) *fn.Client {
-		builder := buildpacks.NewBuilder()
-		builder.Verbose = clientOptions.Verbose
+		verbose := clientOptions.Verbose
+		builder := buildpacks.NewBuilder(verbose)
 
-		progressListener := progress.New()
-		progressListener.Verbose = clientOptions.Verbose
+		progressListener := progress.New(verbose)
 
 		credentialsProvider := creds.NewCredentialsProvider(
 			creds.WithPromptForCredentials(newPromptForCredentials()),
@@ -64,37 +63,30 @@ func NewDefaultClientFactory() (newClient ClientFactory, cleanUp func() error) {
 			creds.WithTransport(transport),
 			creds.WithAdditionalCredentialLoaders(additionalCredLoaders...))
 
-		pusher := docker.NewPusher(
+		pusher := docker.NewPusher(verbose,
 			docker.WithCredentialsProvider(credentialsProvider),
 			docker.WithProgressListener(progressListener),
 			docker.WithTransport(transport))
-		pusher.Verbose = clientOptions.Verbose
 
-		deployer := knative.NewDeployer(clientOptions.Namespace)
-		deployer.Verbose = clientOptions.Verbose
+		deployer := knative.NewDeployer(clientOptions.Namespace, verbose)
 
-		pipelinesProvider := tekton.NewPipelinesProvider(
+		pipelinesProvider := tekton.NewPipelinesProvider(verbose,
 			tekton.WithNamespace(clientOptions.Namespace),
 			tekton.WithProgressListener(progressListener),
 			tekton.WithCredentialsProvider(credentialsProvider))
-		pipelinesProvider.Verbose = clientOptions.Verbose
 
-		remover := knative.NewRemover(clientOptions.Namespace)
-		remover.Verbose = clientOptions.Verbose
+		remover := knative.NewRemover(clientOptions.Namespace, verbose)
 
-		describer := knative.NewDescriber(clientOptions.Namespace)
-		describer.Verbose = clientOptions.Verbose
+		describer := knative.NewDescriber(clientOptions.Namespace, verbose)
 
-		lister := knative.NewLister(clientOptions.Namespace)
-		lister.Verbose = clientOptions.Verbose
+		lister := knative.NewLister(clientOptions.Namespace, verbose)
 
-		runner := docker.NewRunner()
-		runner.Verbose = clientOptions.Verbose
+		runner := docker.NewRunner(verbose)
 
 		opts := []fn.Option{
 			fn.WithRepository(clientOptions.Repository), // URI of repository override
 			fn.WithRegistry(clientOptions.Registry),
-			fn.WithVerbose(clientOptions.Verbose),
+			fn.WithVerbose(verbose),
 			fn.WithTransport(transport),
 			fn.WithProgressListener(progressListener),
 			fn.WithBuilder(builder),
diff --git a/cmd/completion_util.go b/cmd/completion_util.go
index 4f1943b1..11bf0c82 100644
--- a/cmd/completion_util.go
+++ b/cmd/completion_util.go
@@ -15,7 +15,7 @@ import (
 )
 
 func CompleteFunctionList(cmd *cobra.Command, args []string, toComplete string) (strings []string, directive cobra.ShellCompDirective) {
-	lister := knative.NewLister("")
+	lister := knative.NewLister("", false)
 
 	list, err := lister.List(cmd.Context())
 	if err != nil {
diff --git a/cmd/invoke.go b/cmd/invoke.go
index 7fecc9d8..a1128985 100644
--- a/cmd/invoke.go
+++ b/cmd/invoke.go
@@ -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
 }
 
diff --git a/cmd/root.go b/cmd/root.go
index d30a97ac..deb75ce7 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -62,7 +62,7 @@ EXAMPLES
 	// Environment Variables
 	// Evaluated first after static defaults, set all flags to be associated with
 	// a version prefixed by "FUNC_"
-	viper.AutomaticEnv()       // read in environment variables that match
+	viper.AutomaticEnv()       // read in environment variables for FUNC_<flag>
 	viper.SetEnvPrefix("func") // ensure thay all have the prefix
 
 	// Flags
@@ -366,7 +366,7 @@ func (v Version) String() string {
 	// value v0.0.0 as the default indicating there is no version information
 	// available.
 	if strings.HasPrefix(v.Vers, "v") {
-		// TODO: this is the naieve approach, perhaps consider actually parse it
+		// TODO: this is the naive approach, perhaps consider actually parse it
 		// using the semver lib
 		return v.Vers
 	}
diff --git a/cmd/run.go b/cmd/run.go
index 25cb7279..43795a24 100644
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -17,14 +17,11 @@ import (
 
 func newRunClient(cfg runConfig) *fn.Client {
 	bc := newBuildConfig()
-	runner := docker.NewRunner()
-	runner.Verbose = cfg.Verbose
+	runner := docker.NewRunner(cfg.Verbose)
 
 	// builder fields
-	builder := buildpacks.NewBuilder()
-	listener := progress.New()
-	builder.Verbose = cfg.Verbose
-	listener.Verbose = cfg.Verbose
+	builder := buildpacks.NewBuilder(cfg.Verbose)
+	listener := progress.New(cfg.Verbose)
 	return fn.New(
 		fn.WithBuilder(builder),
 		fn.WithProgressListener(listener),
diff --git a/cmd/version.go b/cmd/version.go
index 2d3bcc56..2d08bf36 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -2,7 +2,6 @@ package cmd
 
 import (
 	"fmt"
-	"text/template"
 
 	"github.com/ory/viper"
 	"github.com/spf13/cobra"
@@ -35,13 +34,12 @@ DESCRIPTION
 	}
 
 	// Help Action
-	cmd.SetHelpFunc(runVersionHelp)
+	cmd.SetHelpFunc(defaultTemplatedHelp)
 
 	// Run Action
 	cmd.Run = func(cmd *cobra.Command, args []string) {
 		runVersion(cmd, args, version)
 	}
-	cmd.SetHelpFunc(defaultTemplatedHelp)
 
 	return cmd
 }
@@ -51,22 +49,3 @@ func runVersion(cmd *cobra.Command, args []string, version Version) {
 	version.Verbose = viper.GetBool("verbose")
 	fmt.Fprintf(cmd.OutOrStdout(), "%v\n", version)
 }
-
-// Help
-func runVersionHelp(cmd *cobra.Command, args []string) {
-	var (
-		body = cmd.Long + "\n\n" + cmd.UsageString()
-		t    = template.New("version")
-		tpl  = template.Must(t.Parse(body))
-	)
-
-	var data = struct {
-		Name string
-	}{
-		Name: cmd.Root().Name(),
-	}
-
-	if err := tpl.Execute(cmd.OutOrStdout(), data); err != nil {
-		fmt.Fprintf(cmd.ErrOrStderr(), "unable to display help text: %v", err)
-	}
-}
diff --git a/docker/pusher.go b/docker/pusher.go
index ac693cb2..8e5c8514 100644
--- a/docker/pusher.go
+++ b/docker/pusher.go
@@ -47,8 +47,7 @@ type PusherDockerClientFactory func() (PusherDockerClient, error)
 
 // Pusher of images from local to remote registry.
 type Pusher struct {
-	// Verbose logging.
-	Verbose             bool
+	verbose             bool // verbose logging.
 	credentialsProvider CredentialsProvider
 	progressListener    fn.ProgressListener
 	transport           http.RoundTripper
@@ -84,9 +83,8 @@ func EmptyCredentialsProvider(ctx context.Context, registry string) (Credentials
 }
 
 // NewPusher creates an instance of a docker-based image pusher.
-func NewPusher(opts ...Opt) *Pusher {
+func NewPusher(verbose bool, opts ...Opt) *Pusher {
 	result := &Pusher{
-		Verbose:             false,
 		credentialsProvider: EmptyCredentialsProvider,
 		progressListener:    &fn.NoopProgressListener{},
 		transport:           http.DefaultTransport,
@@ -94,6 +92,7 @@ func NewPusher(opts ...Opt) *Pusher {
 			c, _, err := NewClient(client.DefaultDockerHost)
 			return c, err
 		},
+		verbose: verbose,
 	}
 	for _, opt := range opts {
 		opt(result)
@@ -116,7 +115,7 @@ func (n *Pusher) Push(ctx context.Context, f fn.Function) (digest string, err er
 
 	var output io.Writer
 
-	if n.Verbose {
+	if n.verbose {
 		output = os.Stderr
 	} else {
 		output = io.Discard
diff --git a/docker/runner.go b/docker/runner.go
index 0c368dbb..e686c0f0 100644
--- a/docker/runner.go
+++ b/docker/runner.go
@@ -36,13 +36,12 @@ const (
 
 // Runner starts and stops Functions as local contaieners.
 type Runner struct {
-	// Verbose logging
-	Verbose bool
+	verbose bool // Verbose logging
 }
 
 // NewRunner creates an instance of a docker-backed runner.
-func NewRunner() *Runner {
-	return &Runner{}
+func NewRunner(verbose bool) *Runner {
+	return &Runner{verbose: verbose}
 }
 
 // Run the Function.
@@ -69,7 +68,7 @@ func (n *Runner) Run(ctx context.Context, f fn.Function) (job *fn.Job, err error
 	if c, _, err = NewClient(client.DefaultDockerHost); err != nil {
 		return job, errors.Wrap(err, "failed to create Docker API client")
 	}
-	if id, err = newContainer(ctx, c, f, port, n.Verbose); err != nil {
+	if id, err = newContainer(ctx, c, f, port, n.verbose); err != nil {
 		return job, errors.Wrap(err, "runner unable to create container")
 	}
 	if conn, err = copyStdio(ctx, c, id, copyErrCh); err != nil {
diff --git a/invoke.go b/invoke.go
index 62f3bbc8..8e863b86 100644
--- a/invoke.go
+++ b/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
 }
diff --git a/knative/deployer.go b/knative/deployer.go
index b50cf40e..8824bf45 100644
--- a/knative/deployer.go
+++ b/knative/deployer.go
@@ -32,13 +32,14 @@ type Deployer struct {
 	// Namespace with which to override that set on the default configuration (such as the ~/.kube/config).
 	// If left blank, deployment will commence to the configured namespace.
 	Namespace string
-	// Verbose logging enablement flag.
-	Verbose bool
+	// verbose logging enablement flag.
+	verbose bool
 }
 
-func NewDeployer(namespaceOverride string) *Deployer {
+func NewDeployer(namespaceOverride string, verbose bool) *Deployer {
 	return &Deployer{
 		Namespace: namespaceOverride,
+		verbose:   verbose,
 	}
 }
 
@@ -110,7 +111,7 @@ func (d *Deployer) Deploy(ctx context.Context, f fn.Function) (result fn.Deploym
 				return fn.DeploymentResult{}, err
 			}
 
-			if d.Verbose {
+			if d.verbose {
 				fmt.Println("Waiting for Knative Service to become ready")
 			}
 			chprivate := make(chan bool)
@@ -159,7 +160,7 @@ func (d *Deployer) Deploy(ctx context.Context, f fn.Function) (result fn.Deploym
 				return fn.DeploymentResult{}, err
 			}
 
-			if d.Verbose {
+			if d.verbose {
 				fmt.Println("Function deployed at URL: " + route.Status.URL.String())
 			}
 			return fn.DeploymentResult{
diff --git a/knative/describer.go b/knative/describer.go
index d1105be3..22e81df2 100644
--- a/knative/describer.go
+++ b/knative/describer.go
@@ -12,13 +12,14 @@ import (
 )
 
 type Describer struct {
-	Verbose   bool
 	namespace string
+	verbose   bool
 }
 
-func NewDescriber(namespaceOverride string) *Describer {
+func NewDescriber(namespaceOverride string, verbose bool) *Describer {
 	return &Describer{
 		namespace: namespaceOverride,
+		verbose:   verbose,
 	}
 }
 
diff --git a/knative/lister.go b/knative/lister.go
index 5f908083..167eaef6 100644
--- a/knative/lister.go
+++ b/knative/lister.go
@@ -13,13 +13,14 @@ import (
 )
 
 type Lister struct {
-	Verbose   bool
 	Namespace string
+	verbose   bool
 }
 
-func NewLister(namespaceOverride string) *Lister {
+func NewLister(namespaceOverride string, verbose bool) *Lister {
 	return &Lister{
 		Namespace: namespaceOverride,
+		verbose:   verbose,
 	}
 }
 
diff --git a/knative/remover.go b/knative/remover.go
index c26dfc5d..ad516f88 100644
--- a/knative/remover.go
+++ b/knative/remover.go
@@ -10,15 +10,16 @@ import (
 
 const RemoveTimeout = 120 * time.Second
 
-func NewRemover(namespaceOverride string) *Remover {
+func NewRemover(namespaceOverride string, verbose bool) *Remover {
 	return &Remover{
 		Namespace: namespaceOverride,
+		verbose:   verbose,
 	}
 }
 
 type Remover struct {
 	Namespace string
-	Verbose   bool
+	verbose   bool
 }
 
 func (remover *Remover) Remove(ctx context.Context, name string) (err error) {
diff --git a/pipelines/tekton/pipeplines_provider.go b/pipelines/tekton/pipeplines_provider.go
index 5d048c12..3530fca7 100644
--- a/pipelines/tekton/pipeplines_provider.go
+++ b/pipelines/tekton/pipeplines_provider.go
@@ -30,7 +30,7 @@ type PipelinesProvider struct {
 	// namespace with which to override that set on the default configuration (such as the ~/.kube/config).
 	// If left blank, pipeline creation/run will commence to the configured namespace.
 	namespace           string
-	Verbose             bool
+	verbose             bool
 	progressListener    fn.ProgressListener
 	credentialsProvider docker.CredentialsProvider
 }
@@ -53,8 +53,8 @@ func WithCredentialsProvider(credentialsProvider docker.CredentialsProvider) Opt
 	}
 }
 
-func NewPipelinesProvider(opts ...Opt) *PipelinesProvider {
-	pp := &PipelinesProvider{}
+func NewPipelinesProvider(verbose bool, opts ...Opt) *PipelinesProvider {
+	pp := &PipelinesProvider{verbose: verbose}
 
 	for _, opt := range opts {
 		opt(pp)
diff --git a/progress/progress.go b/progress/progress.go
index ddc07e0d..2b77c243 100644
--- a/progress/progress.go
+++ b/progress/progress.go
@@ -43,7 +43,7 @@ type Bar struct {
 
 	// verbose mode disables progress spinner and line overwrites, instead
 	// printing single, full line updates.
-	Verbose bool
+	verbose bool
 
 	// print verbose-style updates even when not attached to an interactive terminal.
 	printWhileHeadless bool
@@ -78,9 +78,10 @@ func WithPrintStepCounter(s bool) Option {
 	}
 }
 
-func New(options ...Option) *Bar {
+func New(verbose bool, options ...Option) *Bar {
 	b := &Bar{
-		out: os.Stdout,
+		out:     os.Stdout,
+		verbose: verbose,
 	}
 	for _, o := range options {
 		o(b)
@@ -110,7 +111,7 @@ func (b *Bar) Increment(text string) {
 	}
 
 	// If we're in verbose mode, do a simple write
-	if b.Verbose {
+	if b.verbose {
 		b.write()
 		return
 	}
@@ -144,7 +145,7 @@ func (b *Bar) Complete(text string) {
 	}
 
 	// If we're interactive, but in verbose mode do a simple write
-	if b.Verbose {
+	if b.verbose {
 		b.write()
 		return
 	}
@@ -213,7 +214,7 @@ func (b *Bar) String() string {
 
 // Write a spinner at the beginning of the previous line.
 func (b *Bar) spin(ch <-chan time.Time) {
-	if b.Verbose {
+	if b.verbose {
 		return
 	}
 	// Various options for spinners.


Test patch:
diff --git a/client_int_test.go b/client_int_test.go
index fe1f8034..73139ece 100644
--- a/client_int_test.go
+++ b/client_int_test.go
@@ -59,7 +59,7 @@ func TestList(t *testing.T) {
 	verbose := true
 
 	// Assemble
-	lister := knative.NewLister(DefaultNamespace)
+	lister := knative.NewLister(DefaultNamespace, verbose)
 
 	client := fn.New(
 		fn.WithLister(lister),
@@ -203,20 +203,11 @@ func TestRemoteRepositories(t *testing.T) {
 // newClient creates an instance of the func client whose concrete impls
 // match those created by the kn func plugin CLI.
 func newClient(verbose bool) *fn.Client {
-	builder := buildpacks.NewBuilder()
-	builder.Verbose = verbose
-
-	pusher := docker.NewPusher()
-	pusher.Verbose = verbose
-
-	deployer := knative.NewDeployer(DefaultNamespace)
-	deployer.Verbose = verbose
-
-	remover := knative.NewRemover(DefaultNamespace)
-	remover.Verbose = verbose
-
-	lister := knative.NewLister(DefaultNamespace)
-	lister.Verbose = verbose
+	builder := buildpacks.NewBuilder(verbose)
+	pusher := docker.NewPusher(verbose)
+	deployer := knative.NewDeployer(DefaultNamespace, verbose)
+	remover := knative.NewRemover(DefaultNamespace, verbose)
+	lister := knative.NewLister(DefaultNamespace, verbose)
 
 	return fn.New(
 		fn.WithRegistry(DefaultRegistry),
diff --git a/client_test.go b/client_test.go
index 94026d34..010f42de 100644
--- a/client_test.go
+++ b/client_test.go
@@ -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")
diff --git a/docker/pusher_test.go b/docker/pusher_test.go
index 5b73fed9..2a6dd190 100644
--- a/docker/pusher_test.go
+++ b/docker/pusher_test.go
@@ -105,7 +105,7 @@ func TestDaemonPush(t *testing.T) {
 	dockerClientFactory := func() (docker.PusherDockerClient, error) {
 		return dockerClient, nil
 	}
-	pusher := docker.NewPusher(
+	pusher := docker.NewPusher(false,
 		docker.WithCredentialsProvider(testCredProvider),
 		docker.WithPusherDockerClientFactory(dockerClientFactory))
 
@@ -182,7 +182,7 @@ func TestNonDaemonPush(t *testing.T) {
 		return dockerClient, nil
 	}
 
-	pusher := docker.NewPusher(
+	pusher := docker.NewPusher(false,
 		docker.WithTransport(transport),
 		docker.WithCredentialsProvider(testCredProvider),
 		docker.WithPusherDockerClientFactory(dockerClientFactory))
diff --git a/docker/runner_test.go b/docker/runner_test.go
index 20b9a5f3..f39d03ec 100644
--- a/docker/runner_test.go
+++ b/docker/runner_test.go
@@ -35,8 +35,7 @@ func TestDockerRun(t *testing.T) {
 
 	// NOTE: test requires that the image be built already.
 
-	runner := docker.NewRunner()
-	runner.Verbose = true
+	runner := docker.NewRunner(true)
 	if _, err = runner.Run(context.Background(), f); err != nil {
 		t.Fatal(err)
 	}
@@ -48,7 +47,7 @@ func TestDockerRun(t *testing.T) {
 }
 
 func TestDockerRunImagelessError(t *testing.T) {
-	runner := docker.NewRunner()
+	runner := docker.NewRunner(true)
 	f := fn.NewFunctionWith(fn.Function{})
 
 	_, err := runner.Run(context.Background(), f)