Repository: knative-sandbox/kn-plugin-func
Pull request #824 (2022-02-08 22:21:31)

Single common `fn.Client` factory for all commands
Added single common factory function of `fn.Client` for all commands.
This allows to set all important settings at one place.

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/824#" title="Author self-approved">matejvasek</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)~~ [matejvasek]

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":[]} -->
matejvasek: @lkingland this is how it could look like.
codecov[bot]: # [Codecov](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) Report
> Merging [#824](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) (77a226c) into [main](https://codecov.io/gh/knative-sandbox/kn-plugin-func/commit/a2abfca3d3e4115561a64536a8e6c1226bdb1030?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) (a2abfca) will **increase** coverage by `1.31%`.
> The diff coverage is `72.82%`.

[![Impacted file tree graph](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/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/824?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     #824      +/-   ##
==========================================
+ Coverage   43.02%   44.34%   +1.31%     
==========================================
  Files          47       48       +1     
  Lines        4720     4666      -54     
==========================================
+ Hits         2031     2069      +38     
+ Misses       2398     2306      -92     
  Partials      291      291              
```


| [Impacted Files](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) | Coverage Δ | |
|---|---|---|
| [cmd/deploy.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2RlcGxveS5nbw==) | `16.91% <0.00%> (+2.99%)` | :arrow_up: |
| [cmd/info.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2luZm8uZ28=) | `27.53% <0.00%> (+2.86%)` | :arrow_up: |
| [cmd/invoke.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2ludm9rZS5nbw==) | `34.36% <0.00%> (+1.58%)` | :arrow_up: |
| [cmd/list.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2xpc3QuZ28=) | `33.92% <0.00%> (+4.24%)` | :arrow_up: |
| [cmd/create.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2NyZWF0ZS5nbw==) | `47.89% <41.17%> (+1.68%)` | :arrow_up: |
| [cmd/root.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL3Jvb3QuZ28=) | `66.90% <84.61%> (-0.02%)` | :arrow_down: |
| [cmd/build.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2J1aWxkLmdv) | `61.46% <100.00%> (+1.76%)` | :arrow_up: |
| [cmd/client.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2NsaWVudC5nbw==) | `100.00% <100.00%> (ø)` | |
| [cmd/delete.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-Y21kL2RlbGV0ZS5nbw==) | `60.49% <100.00%> (+9.95%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824?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/824?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox). Last update [a2abfca...77a226c](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/824?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).

matejvasek: >I also have some lingering concern about how we will address additional build strategies (e.g. local or s2i). I wouldn't expect you to have given a lot of thought to that, as it's only recently been discussed.

Indeed I didn't think much about it, but I think these changes shouldn't affect it much.
lance: /lgtm

Patch:
diff --git a/cmd/build.go b/cmd/build.go
index 05ca3c3e..f7c426f1 100644
--- a/cmd/build.go
+++ b/cmd/build.go
@@ -3,9 +3,6 @@ package cmd
 import (
 	"errors"
 	"fmt"
-	"net/http"
-
-	fnhttp "knative.dev/kn-plugin-func/http"
 
 	"github.com/AlecAivazis/survey/v2"
 	"github.com/AlecAivazis/survey/v2/terminal"
@@ -13,45 +10,16 @@ import (
 	"github.com/spf13/cobra"
 
 	fn "knative.dev/kn-plugin-func"
-	"knative.dev/kn-plugin-func/buildpacks"
-	"knative.dev/kn-plugin-func/docker"
-	"knative.dev/kn-plugin-func/docker/creds"
-	"knative.dev/kn-plugin-func/progress"
 )
 
-func newBuildClient(cfg buildConfig) (*fn.Client, error) {
-	builder := buildpacks.NewBuilder()
-	listener := progress.New()
-
-	var (
-		pusher *docker.Pusher
-	)
-	if cfg.Push {
-		credentialsProvider := creds.NewCredentialsProvider(
-			creds.WithPromptForCredentials(newPromptForCredentials()),
-			creds.WithPromptForCredentialStore(newPromptForCredentialStore()),
-			creds.WithTransport(cfg.Transport))
-		pusher = docker.NewPusher(
-			docker.WithCredentialsProvider(credentialsProvider),
-			docker.WithProgressListener(listener),
-			docker.WithTransport(cfg.Transport))
-		pusher.Verbose = cfg.Verbose
+func buildConfigToClientOptions(cfg buildConfig) ClientOptions {
+	return ClientOptions{
+		Registry: cfg.Registry,
+		Verbose:  cfg.Verbose,
 	}
-
-	builder.Verbose = cfg.Verbose
-	listener.Verbose = cfg.Verbose
-
-	return fn.New(
-		fn.WithBuilder(builder),
-		fn.WithPusher(pusher),
-		fn.WithProgressListener(listener),
-		fn.WithRegistry(cfg.Registry), // for image name when --image not provided
-		fn.WithVerbose(cfg.Verbose)), nil
 }
 
-type buildClientFn func(buildConfig) (*fn.Client, error)
-
-func NewBuildCmd(clientFn buildClientFn) *cobra.Command {
+func NewBuildCmd(newClient ClientFactory) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "build",
 		Short: "Build a function project as a container image",
@@ -94,7 +62,7 @@ kn func build --builder cnbs/sample-builder:bionic
 	}
 
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
-		return runBuild(cmd, args, clientFn)
+		return runBuild(cmd, args, newClient)
 	}
 
 	return cmd
@@ -117,7 +85,7 @@ func ValidNamespaceAndRegistry(path string) survey.Validator {
 	}
 }
 
-func runBuild(cmd *cobra.Command, _ []string, clientFn buildClientFn) (err error) {
+func runBuild(cmd *cobra.Command, _ []string, clientFn ClientFactory) (err error) {
 	config, err := newBuildConfig().Prompt()
 	if err != nil {
 		if err == terminal.InterruptErr {
@@ -181,14 +149,7 @@ func runBuild(cmd *cobra.Command, _ []string, clientFn buildClientFn) (err error
 		config.Registry = ""
 	}
 
-	rt := fnhttp.NewRoundTripper()
-	defer rt.Close()
-	config.Transport = rt
-
-	client, err := clientFn(config)
-	if err != nil {
-		return err
-	}
+	client := clientFn(buildConfigToClientOptions(config))
 
 	err = client.Build(cmd.Context(), config.Path)
 	if err == nil && config.Push {
@@ -220,8 +181,6 @@ type buildConfig struct {
 	// with interactive prompting (only applicable when attached to a TTY).
 	Confirm bool
 	Builder string
-
-	Transport http.RoundTripper
 }
 
 func newBuildConfig() buildConfig {
diff --git a/cmd/client.go b/cmd/client.go
new file mode 100644
index 00000000..f255a301
--- /dev/null
+++ b/cmd/client.go
@@ -0,0 +1,87 @@
+package cmd
+
+import (
+	"net/http"
+
+	fn "knative.dev/kn-plugin-func"
+	"knative.dev/kn-plugin-func/buildpacks"
+	"knative.dev/kn-plugin-func/docker"
+	"knative.dev/kn-plugin-func/docker/creds"
+	"knative.dev/kn-plugin-func/knative"
+	"knative.dev/kn-plugin-func/pipelines/tekton"
+	"knative.dev/kn-plugin-func/progress"
+)
+
+type ClientOptions struct {
+	Namespace    string
+	Registry     string
+	Repository   string
+	Repositories string
+	Verbose      bool
+}
+
+type ClientFactory func(opts ClientOptions) *fn.Client
+
+func NewClientFactory(transport http.RoundTripper) ClientFactory {
+	return func(clientOptions ClientOptions) *fn.Client {
+		builder := buildpacks.NewBuilder()
+		builder.Verbose = clientOptions.Verbose
+
+		progressListener := progress.New()
+		progressListener.Verbose = clientOptions.Verbose
+
+		credentialsProvider := creds.NewCredentialsProvider(
+			creds.WithPromptForCredentials(newPromptForCredentials()),
+			creds.WithPromptForCredentialStore(newPromptForCredentialStore()),
+			creds.WithTransport(transport))
+
+		pusher := docker.NewPusher(
+			docker.WithCredentialsProvider(credentialsProvider),
+			docker.WithProgressListener(progressListener),
+			docker.WithTransport(transport))
+		pusher.Verbose = clientOptions.Verbose
+
+		deployer := knative.NewDeployer(clientOptions.Namespace)
+		deployer.Verbose = clientOptions.Verbose
+
+		pipelinesProvider := tekton.NewPipelinesProvider(
+			tekton.WithNamespace(clientOptions.Namespace),
+			tekton.WithProgressListener(progressListener),
+			tekton.WithCredentialsProvider(credentialsProvider))
+		pipelinesProvider.Verbose = clientOptions.Verbose
+
+		remover := knative.NewRemover(clientOptions.Namespace)
+		remover.Verbose = clientOptions.Verbose
+
+		describer := knative.NewDescriber(clientOptions.Namespace)
+		describer.Verbose = clientOptions.Verbose
+
+		lister := knative.NewLister(clientOptions.Namespace)
+		lister.Verbose = clientOptions.Verbose
+
+		runner := docker.NewRunner()
+		runner.Verbose = clientOptions.Verbose
+
+		opts := []fn.Option{
+			fn.WithRepository(clientOptions.Repository), // URI of repository override
+			fn.WithRegistry(clientOptions.Registry),
+			fn.WithVerbose(clientOptions.Verbose),
+			fn.WithTransport(transport),
+			fn.WithProgressListener(progressListener),
+			fn.WithBuilder(builder),
+			fn.WithPipelinesProvider(pipelinesProvider),
+			fn.WithRemover(remover),
+			fn.WithDescriber(describer),
+			fn.WithLister(lister),
+			fn.WithRunner(runner),
+			fn.WithDeployer(deployer),
+			fn.WithPusher(pusher),
+		}
+
+		if clientOptions.Repositories != "" {
+			opts = append(opts, fn.WithRepositories(clientOptions.Repositories)) // path to repositories in disk
+		}
+
+		return fn.New(opts...)
+	}
+}
diff --git a/cmd/create.go b/cmd/create.go
index 8ef2d740..c074a119 100644
--- a/cmd/create.go
+++ b/cmd/create.go
@@ -25,23 +25,16 @@ type ErrInvalidRuntime error
 // ErrInvalidTemplate indicates that the passed template was invalid.
 type ErrInvalidTemplate error
 
-// createClientFn is a factory function which returns a Client suitable for
-// use with the Create command.
-type createClientFn func(createConfig) *fn.Client
-
-// newCreateClient returns an instance of fn.Client for the "Create" command.
-// The createClientFn is a client factory which creates a new Client for use by
-// the create command during normal execution (see tests for alternative client
-// factories which return clients with various mocks).
-func newCreateClient(cfg createConfig) *fn.Client {
-	return fn.New(
-		fn.WithRepositories(cfg.Repositories), // path to repositories in disk
-		fn.WithRepository(cfg.Repository),     // URI of repository override
-		fn.WithVerbose(cfg.Verbose))           // verbose logging
+func createConfigToClientOptions(cfg createConfig) ClientOptions {
+	return ClientOptions{
+		Repositories: cfg.Repositories,
+		Repository:   cfg.Repository,
+		Verbose:      cfg.Verbose,
+	}
 }
 
 // NewCreateCmd creates a create command using the given client creator.
-func NewCreateCmd(clientFn createClientFn) *cobra.Command {
+func NewCreateCmd(newClient ClientFactory) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "create",
 		Short: "Create a Function Project",
@@ -96,19 +89,19 @@ EXAMPLES
 
 	// Help Action
 	cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
-		runCreateHelp(cmd, args, clientFn)
+		runCreateHelp(cmd, args, newClient)
 	})
 
 	// Run Action
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
-		return runCreate(cmd, args, clientFn)
+		return runCreate(cmd, args, newClient)
 	}
 
 	// Tab Completion
-	if err := cmd.RegisterFlagCompletionFunc("language", newRuntimeCompletionFunc(clientFn)); err != nil {
+	if err := cmd.RegisterFlagCompletionFunc("language", newRuntimeCompletionFunc(newClient)); err != nil {
 		fmt.Fprintf(os.Stderr, "unable to provide language runtime suggestions: %v", err)
 	}
-	if err := cmd.RegisterFlagCompletionFunc("template", newTemplateCompletionFunc(clientFn)); err != nil {
+	if err := cmd.RegisterFlagCompletionFunc("template", newTemplateCompletionFunc(newClient)); err != nil {
 		fmt.Fprintf(os.Stderr, "unable to provide template suggestions: %v", err)
 	}
 
@@ -116,11 +109,11 @@ EXAMPLES
 }
 
 // Run Create
-func runCreate(cmd *cobra.Command, args []string, clientFn createClientFn) (err error) {
+func runCreate(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
 	// Config
-	// Create a config based on args.  Also uses the clientFn to create a
+	// Create a config based on args.  Also uses the newClient to create a
 	// temporary client for completing options such as available runtimes.
-	cfg, err := newCreateConfig(args, clientFn)
+	cfg, err := newCreateConfig(args, newClient)
 	if err != nil {
 		return
 	}
@@ -128,7 +121,7 @@ func runCreate(cmd *cobra.Command, args []string, clientFn createClientFn) (err
 	// Client
 	// From environment variables, flags, arguments, and user prompts if --confirm
 	// (in increasing levels of precidence)
-	client := clientFn(cfg)
+	client := newClient(createConfigToClientOptions(cfg))
 
 	// Validate - a deeper validation than that which is performed when
 	// instantiating the client with the raw config above.
@@ -153,7 +146,7 @@ func runCreate(cmd *cobra.Command, args []string, clientFn createClientFn) (err
 }
 
 // Run Help
-func runCreateHelp(cmd *cobra.Command, args []string, clientFn createClientFn) {
+func runCreateHelp(cmd *cobra.Command, args []string, newClient ClientFactory) {
 	// Error-tolerant implementataion:
 	// Help can not fail when creating the client config (such as on invalid
 	// flag values) because help text is needed in that situation.   Therefore
@@ -166,10 +159,10 @@ func runCreateHelp(cmd *cobra.Command, args []string, clientFn createClientFn) {
 
 	tpl := createHelpTemplate(cmd)
 
-	cfg, err := newCreateConfig(args, clientFn)
+	cfg, err := newCreateConfig(args, newClient)
 	failSoft(err)
 
-	client := clientFn(cfg)
+	client := newClient(createConfigToClientOptions(cfg))
 
 	options, err := runtimeTemplateOptions(client) // human-friendly
 	failSoft(err)
@@ -217,7 +210,7 @@ type createConfig struct {
 // The client constructor function is used to create a transient client for
 // accessing things like the current valid templates list, and uses the
 // current value of the config at time of prompting.
-func newCreateConfig(args []string, clientFn createClientFn) (cfg createConfig, err error) {
+func newCreateConfig(args []string, newClient ClientFactory) (cfg createConfig, err error) {
 	var (
 		path         string
 		dirName      string
@@ -262,7 +255,7 @@ func newCreateConfig(args []string, clientFn createClientFn) (cfg createConfig,
 
 	// Create a tempoarary client for use by the following prompts to complete
 	// runtime/template suggestions etc
-	client := clientFn(cfg)
+	client := newClient(createConfigToClientOptions(cfg))
 
 	// IN confirm mode.  If also in an interactive terminal, run prompts.
 	if interactiveTerminal() {
@@ -482,24 +475,24 @@ func (c createConfig) prompt(client *fn.Client) (createConfig, error) {
 
 type flagCompletionFunc func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)
 
-func newRuntimeCompletionFunc(clientFn createClientFn) flagCompletionFunc {
+func newRuntimeCompletionFunc(newClient ClientFactory) flagCompletionFunc {
 	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		cfg, err := newCreateConfig(args, clientFn)
+		cfg, err := newCreateConfig(args, newClient)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "error creating client config for flag completion: %v", err)
 		}
-		client := clientFn(cfg)
+		client := newClient(createConfigToClientOptions(cfg))
 		return CompleteRuntimeList(cmd, args, toComplete, client)
 	}
 }
 
-func newTemplateCompletionFunc(clientFn createClientFn) flagCompletionFunc {
+func newTemplateCompletionFunc(newClient ClientFactory) flagCompletionFunc {
 	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		cfg, err := newCreateConfig(args, clientFn)
+		cfg, err := newCreateConfig(args, newClient)
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "error creating client config for flag completion: %v", err)
 		}
-		client := clientFn(cfg)
+		client := newClient(createConfigToClientOptions(cfg))
 		return CompleteTemplateList(cmd, args, toComplete, client)
 	}
 }
diff --git a/cmd/delete.go b/cmd/delete.go
index a15ff9f7..0286f8ee 100644
--- a/cmd/delete.go
+++ b/cmd/delete.go
@@ -9,37 +9,9 @@ import (
 	"github.com/spf13/cobra"
 
 	fn "knative.dev/kn-plugin-func"
-	"knative.dev/kn-plugin-func/knative"
-	"knative.dev/kn-plugin-func/pipelines/tekton"
-	"knative.dev/kn-plugin-func/progress"
 )
 
-// newDeleteClient returns an instance of a Client using the
-// final config state.
-// Testing note: This method is swapped out during testing to allow
-// mocking the remover or the client itself to fabricate test states.
-func newDeleteClient(cfg deleteConfig) (*fn.Client, error) {
-	listener := progress.New()
-	remover := knative.NewRemover(cfg.Namespace)
-
-	pipelinesProvider := tekton.NewPipelinesProvider(
-		tekton.WithNamespace(cfg.Namespace))
-
-	listener.Verbose = cfg.Verbose
-	remover.Verbose = cfg.Verbose
-	pipelinesProvider.Verbose = cfg.Verbose
-
-	return fn.New(
-		fn.WithProgressListener(listener),
-		fn.WithRemover(remover),
-		fn.WithPipelinesProvider(pipelinesProvider),
-		fn.WithVerbose(cfg.Verbose)), nil
-}
-
-// A deleteClientFn is a function which yields a Client instance from a config
-type deleteClientFn func(deleteConfig) (*fn.Client, error)
-
-func NewDeleteCmd(clientFn deleteClientFn) *cobra.Command {
+func NewDeleteCmd(newClient ClientFactory) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "delete [NAME]",
 		Short: "Undeploy a function",
@@ -69,13 +41,13 @@ kn func delete -n apps myfunc
 	setPathFlag(cmd)
 
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
-		return runDelete(cmd, args, clientFn)
+		return runDelete(cmd, args, newClient)
 	}
 
 	return cmd
 }
 
-func runDelete(cmd *cobra.Command, args []string, clientFn deleteClientFn) (err error) {
+func runDelete(cmd *cobra.Command, args []string, clientFn ClientFactory) (err error) {
 	config, err := newDeleteConfig(args).Prompt()
 	if err != nil {
 		if err == terminal.InterruptErr {
@@ -113,10 +85,10 @@ func runDelete(cmd *cobra.Command, args []string, clientFn deleteClientFn) (err
 	}
 
 	// Create a client instance from the now-final config
-	client, err := clientFn(config)
-	if err != nil {
-		return err
-	}
+	client := clientFn(ClientOptions{
+		Namespace: config.Namespace,
+		Verbose:   config.Verbose,
+	})
 
 	// Invoke remove using the concrete client impl
 	return client.Remove(cmd.Context(), function, config.DeleteAll)
diff --git a/cmd/deploy.go b/cmd/deploy.go
index f570966d..fd847ad9 100644
--- a/cmd/deploy.go
+++ b/cmd/deploy.go
@@ -3,12 +3,9 @@ package cmd
 import (
 	"errors"
 	"fmt"
-	"net/http"
 	"os"
 	"strings"
 
-	fnhttp "knative.dev/kn-plugin-func/http"
-
 	"github.com/AlecAivazis/survey/v2"
 	"github.com/AlecAivazis/survey/v2/terminal"
 	"github.com/ory/viper"
@@ -16,61 +13,19 @@ import (
 	"knative.dev/client/pkg/util"
 
 	fn "knative.dev/kn-plugin-func"
-	"knative.dev/kn-plugin-func/buildpacks"
 	"knative.dev/kn-plugin-func/docker"
 	"knative.dev/kn-plugin-func/docker/creds"
-	"knative.dev/kn-plugin-func/knative"
-	"knative.dev/kn-plugin-func/pipelines/tekton"
-	"knative.dev/kn-plugin-func/progress"
 )
 
-func newDeployClient(cfg deployConfig) (*fn.Client, error) {
-	listener := progress.New()
-	builder := buildpacks.NewBuilder()
-
-	var (
-		pusher *docker.Pusher
-	)
-
-	credentialsProvider := creds.NewCredentialsProvider(
-		creds.WithPromptForCredentials(newPromptForCredentials()),
-		creds.WithPromptForCredentialStore(newPromptForCredentialStore()),
-		creds.WithTransport(cfg.Transport))
-
-	if cfg.Push {
-		pusher = docker.NewPusher(
-			docker.WithCredentialsProvider(credentialsProvider),
-			docker.WithProgressListener(listener),
-			docker.WithTransport(cfg.Transport))
-		pusher.Verbose = cfg.Verbose
+func deployConfigToClientOptions(cfg deployConfig) ClientOptions {
+	return ClientOptions{
+		Namespace: cfg.Namespace,
+		Verbose:   cfg.Verbose,
+		Registry:  cfg.Registry,
 	}
-
-	deployer := knative.NewDeployer(cfg.Namespace)
-
-	pipelinesProvider := tekton.NewPipelinesProvider(
-		tekton.WithNamespace(cfg.Namespace),
-		tekton.WithProgressListener(listener),
-		tekton.WithCredentialsProvider(credentialsProvider))
-
-	listener.Verbose = cfg.Verbose
-	builder.Verbose = cfg.Verbose
-	deployer.Verbose = cfg.Verbose
-	pipelinesProvider.Verbose = cfg.Verbose
-
-	return fn.New(
-		fn.WithProgressListener(listener),
-		fn.WithBuilder(builder),
-		fn.WithPusher(pusher),
-		fn.WithDeployer(deployer),
-		fn.WithPipelinesProvider(pipelinesProvider),
-		fn.WithRegistry(cfg.Registry), // for deriving name when no --image value
-		fn.WithVerbose(cfg.Verbose),
-	), nil
 }
 
-type deployClientFn func(deployConfig) (*fn.Client, error)
-
-func NewDeployCmd(clientFn deployClientFn) *cobra.Command {
+func NewDeployCmd(newClient ClientFactory) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "deploy",
 		Short: "Deploy a function",
@@ -115,13 +70,13 @@ kn func deploy --image quay.io/myuser/myfunc -n myns
 	}
 
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
-		return runDeploy(cmd, args, clientFn)
+		return runDeploy(cmd, args, newClient)
 	}
 
 	return cmd
 }
 
-func runDeploy(cmd *cobra.Command, _ []string, clientFn deployClientFn) (err error) {
+func runDeploy(cmd *cobra.Command, _ []string, newClient ClientFactory) (err error) {
 	config, err := newDeployConfig(cmd)
 	if err != nil {
 		return
@@ -210,17 +165,7 @@ func runDeploy(cmd *cobra.Command, _ []string, clientFn deployClientFn) (err err
 		config.Registry = ""
 	}
 
-	rt := fnhttp.NewRoundTripper()
-	defer rt.Close()
-	config.Transport = rt
-
-	client, err := clientFn(config)
-	if err != nil {
-		if err == terminal.InterruptErr {
-			return nil
-		}
-		return err
-	}
+	client := newClient(deployConfigToClientOptions(config))
 
 	switch currentBuildType {
 	case fn.BuildTypeLocal, "":
@@ -337,8 +282,6 @@ type deployConfig struct {
 
 	// Envs passed via cmd to removed
 	EnvToRemove []string
-
-	Transport http.RoundTripper
 }
 
 // newDeployConfig creates a buildConfig populated from command flags and
diff --git a/cmd/info.go b/cmd/info.go
index eb8b3532..f8b02e7c 100644
--- a/cmd/info.go
+++ b/cmd/info.go
@@ -12,23 +12,9 @@ import (
 	"gopkg.in/yaml.v2"
 
 	fn "knative.dev/kn-plugin-func"
-	"knative.dev/kn-plugin-func/knative"
 )
 
-func newInfoClient(cfg infoConfig) (*fn.Client, error) {
-	describer := knative.NewDescriber(cfg.Namespace)
-
-	describer.Verbose = cfg.Verbose
-
-	return fn.New(
-		fn.WithDescriber(describer),
-		fn.WithVerbose(cfg.Verbose),
-	), nil
-}
-
-type infoClientFn func(infoConfig) (*fn.Client, error)
-
-func NewInfoCmd(clientFn infoClientFn) *cobra.Command {
+func NewInfoCmd(newClient ClientFactory) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "info <name>",
 		Short: "Show details of a function",
@@ -58,13 +44,13 @@ kn func info --output yaml --path myotherfunc
 	}
 
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
-		return runInfo(cmd, args, clientFn)
+		return runInfo(cmd, args, newClient)
 	}
 
 	return cmd
 }
 
-func runInfo(cmd *cobra.Command, args []string, clientFn infoClientFn) (err error) {
+func runInfo(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
 	config := newInfoConfig(args)
 
 	function, err := fn.NewFunction(config.Path)
@@ -78,10 +64,10 @@ func runInfo(cmd *cobra.Command, args []string, clientFn infoClientFn) (err erro
 	}
 
 	// Create a client
-	client, err := clientFn(config)
-	if err != nil {
-		return err
-	}
+	client := newClient(ClientOptions{
+		Namespace: config.Namespace,
+		Verbose:   config.Verbose,
+	})
 
 	// Get the description
 	d, err := client.Info(cmd.Context(), config.Name, config.Path)
diff --git a/cmd/invoke.go b/cmd/invoke.go
index 3823f4d3..0f4abbfc 100644
--- a/cmd/invoke.go
+++ b/cmd/invoke.go
@@ -11,27 +11,12 @@ import (
 	"github.com/google/uuid"
 	"github.com/ory/viper"
 	"github.com/spf13/cobra"
-	"knative.dev/kn-plugin-func/utils"
 
 	fn "knative.dev/kn-plugin-func"
-	fnhttp "knative.dev/kn-plugin-func/http"
-	knative "knative.dev/kn-plugin-func/knative"
+	"knative.dev/kn-plugin-func/utils"
 )
 
-type invokeClientFn func(invokeConfig) (*fn.Client, error)
-
-func newInvokeClient(cfg invokeConfig) (*fn.Client, error) {
-	describer := knative.NewDescriber(cfg.Namespace)
-	describer.Verbose = cfg.Verbose
-
-	return fn.New(
-		fn.WithDescriber(describer),
-		fn.WithTransport(fnhttp.NewRoundTripper()),
-		fn.WithVerbose(cfg.Verbose),
-	), nil
-}
-
-func NewInvokeCmd(clientFn invokeClientFn) *cobra.Command {
+func NewInvokeCmd(newClient ClientFactory) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "invoke",
 		Short: "Invoke a Function",
@@ -133,30 +118,30 @@ EXAMPLES
 
 	// Help Action
 	cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
-		runInvokeHelp(cmd, args, clientFn)
+		runInvokeHelp(cmd, args, newClient)
 	})
 
 	// Run Action
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
-		return runInvoke(cmd, args, clientFn)
+		return runInvoke(cmd, args, newClient)
 	}
 
 	return cmd
 }
 
 // Run
-func runInvoke(cmd *cobra.Command, args []string, clientFn invokeClientFn) (err error) {
+func runInvoke(cmd *cobra.Command, args []string, newClient ClientFactory) (err error) {
 	// Gather flag values for the invocation
-	cfg, err := newInvokeConfig(clientFn)
+	cfg, err := newInvokeConfig(newClient)
 	if err != nil {
 		return
 	}
 
 	// Client instance from env vars, flags, args and user prompts (if --confirm)
-	client, err := clientFn(cfg)
-	if err != nil {
-		return err
-	}
+	client := newClient(ClientOptions{
+		Namespace: cfg.Namespace,
+		Verbose:   cfg.Verbose,
+	})
 
 	// Message to send the running Function built from parameters gathered
 	// from the user (or defaults)
@@ -188,7 +173,7 @@ func runInvoke(cmd *cobra.Command, args []string, clientFn invokeClientFn) (err
 	return
 }
 
-func runInvokeHelp(cmd *cobra.Command, args []string, clientFn invokeClientFn) {
+func runInvokeHelp(cmd *cobra.Command, args []string, newClient ClientFactory) {
 	var (
 		body = cmd.Long + "\n\n" + cmd.UsageString()
 		t    = template.New("invoke")
@@ -221,7 +206,7 @@ type invokeConfig struct {
 	Verbose     bool
 }
 
-func newInvokeConfig(clientFn invokeClientFn) (cfg invokeConfig, err error) {
+func newInvokeConfig(newClient ClientFactory) (cfg invokeConfig, err error) {
 	cfg = invokeConfig{
 		Path:        viper.GetString("path"),
 		Target:      viper.GetString("target"),
@@ -250,10 +235,10 @@ func newInvokeConfig(clientFn invokeClientFn) (cfg invokeConfig, err error) {
 	}
 
 	// Client instance for use during prompting.
-	client, err := clientFn(cfg)
-	if err != nil {
-		return
-	}
+	client := newClient(ClientOptions{
+		Namespace: cfg.Namespace,
+		Verbose:   cfg.Verbose,
+	})
 
 	// If in interactive terminal mode, prompt to modify defaults.
 	if interactiveTerminal() {
diff --git a/cmd/list.go b/cmd/list.go
index a937fda3..a9bfc1b8 100644
--- a/cmd/list.go
+++ b/cmd/list.go
@@ -14,24 +14,9 @@ import (
 	"gopkg.in/yaml.v2"
 
 	fn "knative.dev/kn-plugin-func"
-	"knative.dev/kn-plugin-func/knative"
 )
 
-func newListClient(cfg listConfig) (*fn.Client, error) {
-	// TODO(lkingland): does an empty namespace mean all namespaces
-	// or the default namespace as defined in user's config?
-	lister := knative.NewLister(cfg.Namespace)
-	lister.Verbose = cfg.Verbose
-
-	return fn.New(
-		fn.WithLister(lister),
-		fn.WithVerbose(cfg.Verbose),
-	), nil
-}
-
-type listClientFn func(listConfig) (*fn.Client, error)
-
-func NewListCmd(clientFn listClientFn) *cobra.Command {
+func NewListCmd(newClient ClientFactory) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "list",
 		Short: "List functions",
@@ -62,23 +47,23 @@ kn func list --all-namespaces --output json
 	}
 
 	cmd.RunE = func(cmd *cobra.Command, args []string) error {
-		return runList(cmd, args, clientFn)
+		return runList(cmd, args, newClient)
 	}
 
 	return cmd
 }
 
-func runList(cmd *cobra.Command, _ []string, clientFn listClientFn) (err error) {
+func runList(cmd *cobra.Command, _ []string, newClient ClientFactory) (err error) {
 	config := newListConfig()
 
 	if err := config.Validate(); err != nil {
 		return err
 	}
 
-	client, err := clientFn(config)
-	if err != nil {
-		return err
-	}
+	client := newClient(ClientOptions{
+		Namespace: config.Namespace,
+		Verbose:   config.Verbose,
+	})
 
 	items, err := client.List(cmd.Context())
 	if err != nil {
diff --git a/cmd/root.go b/cmd/root.go
index fdbd86bd..a35a8408 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -14,6 +14,7 @@ import (
 	"knative.dev/client/pkg/util"
 
 	fn "knative.dev/kn-plugin-func"
+	fnhttp "knative.dev/kn-plugin-func/http"
 )
 
 var exampleTemplate = template.Must(template.New("example").Parse(`
@@ -29,10 +30,11 @@ curl $(kn service describe myfunc -o url)
 `))
 
 type RootCommandConfig struct {
-	Name    string // usually `func` or `kn func`
-	Date    string
-	Version string
-	Hash    string
+	Name      string // usually `func` or `kn func`
+	Date      string
+	Version   string
+	Hash      string
+	NewClient ClientFactory
 }
 
 // NewRootCmd creates the root of the command tree defines the command name, description, globally
@@ -85,15 +87,25 @@ Create, build and deploy Functions in serverless containers for multiple runtime
 
 	root.Version = version.String()
 
+	newClient := config.NewClient
+
+	if newClient == nil {
+		transport := fnhttp.NewRoundTripper()
+		root.PostRun = func(cmd *cobra.Command, args []string) {
+			transport.Close()
+		}
+		newClient = NewClientFactory(transport)
+	}
+
 	root.AddCommand(NewVersionCmd(version))
-	root.AddCommand(NewCreateCmd(newCreateClient))
+	root.AddCommand(NewCreateCmd(newClient))
 	root.AddCommand(NewConfigCmd())
-	root.AddCommand(NewBuildCmd(newBuildClient))
-	root.AddCommand(NewDeployCmd(newDeployClient))
-	root.AddCommand(NewDeleteCmd(newDeleteClient))
-	root.AddCommand(NewInfoCmd(newInfoClient))
-	root.AddCommand(NewListCmd(newListClient))
-	root.AddCommand(NewInvokeCmd(newInvokeClient))
+	root.AddCommand(NewBuildCmd(newClient))
+	root.AddCommand(NewDeployCmd(newClient))
+	root.AddCommand(NewDeleteCmd(newClient))
+	root.AddCommand(NewInfoCmd(newClient))
+	root.AddCommand(NewListCmd(newClient))
+	root.AddCommand(NewInvokeCmd(newClient))
 	root.AddCommand(NewRepositoryCmd(newRepositoryClient))
 	root.AddCommand(NewRunCmd(newRunClient))
 	root.AddCommand(NewCompletionCmd())


Test patch:
diff --git a/cmd/build_test.go b/cmd/build_test.go
index c7da5425..7bd37b92 100644
--- a/cmd/build_test.go
+++ b/cmd/build_test.go
@@ -1,13 +1,11 @@
 package cmd
 
 import (
-	"context"
 	"fmt"
 	"io/ioutil"
 	"os"
 	"testing"
 
-	"github.com/ory/viper"
 	fn "knative.dev/kn-plugin-func"
 	"knative.dev/kn-plugin-func/mock"
 )
@@ -42,8 +40,8 @@ created: 2021-01-01T00:00:00+00:00
 
 	// Create a command with a client constructor fn that instantiates a client
 	// with a the mocked builder.
-	cmd := NewBuildCmd(func(_ buildConfig) (*fn.Client, error) {
-		return fn.New(fn.WithBuilder(builder)), nil
+	cmd := NewBuildCmd(func(options ClientOptions) *fn.Client {
+		return fn.New(fn.WithBuilder(builder))
 	})
 
 	// Execute the command
@@ -72,6 +70,15 @@ created: 2009-11-10 23:00:00`,
 			shouldBuild: true,
 			shouldPush:  true,
 		},
+		{
+			name:     "do not push when --push=false",
+			pushFlag: false,
+			fileContents: `name: test-func
+runtime: go
+created: 2009-11-10 23:00:00`,
+			shouldBuild: true,
+			shouldPush:  false,
+		},
 		{
 			name:     "push flag with failing push",
 			pushFlag: true,
@@ -92,7 +99,7 @@ created: 2009-11-10 23:00:00`,
 				},
 			}
 			mockBuilder := mock.NewBuilder()
-			cmd := NewBuildCmd(func(bc buildConfig) (*fn.Client, error) {
+			cmd := NewBuildCmd(func(options ClientOptions) *fn.Client {
 				pusher := mockPusher
 				if tt.wantErr {
 					pusher = failPusher
@@ -100,7 +107,7 @@ created: 2009-11-10 23:00:00`,
 				return fn.New(
 					fn.WithBuilder(mockBuilder),
 					fn.WithPusher(pusher),
-				), nil
+				)
 			})
 
 			tempDir, err := os.MkdirTemp("", "func-tests")
@@ -122,9 +129,11 @@ created: 2009-11-10 23:00:00`,
 				t.Fatalf("file content was not written %v", err)
 			}
 
-			cmd.SetArgs([]string{"--path=" + tempDir})
-			viper.SetDefault("push", tt.pushFlag)
-			viper.SetDefault("registry", "docker.io/tigerteam")
+			cmd.SetArgs([]string{
+				"--path=" + tempDir,
+				fmt.Sprintf("--push=%t", tt.pushFlag),
+				"--registry=docker.io/tigerteam",
+			})
 
 			err = cmd.Execute()
 			if tt.wantErr != (err != nil) {
@@ -141,63 +150,3 @@ created: 2009-11-10 23:00:00`,
 		})
 	}
 }
-
-func TestBuild_newBuildClient(t *testing.T) {
-	tests := []struct {
-		name         string
-		cfg          buildConfig
-		fileContents string
-		succeed      bool
-	}{
-		{
-			name: "push flag set to false avoids pusher instanciation",
-			cfg: buildConfig{
-				Push: false,
-			},
-			fileContents: `name: test-func
-runtime: go
-image: registry.io/foo/bar:latest
-imageDigest: sha256:1111111111111111111111
-created: 2009-11-10 23:00:00`,
-			succeed: false,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-
-			tempDir, err := os.MkdirTemp("", "func-tests")
-			if err != nil {
-				t.Fatalf("temp dir couldn't be created %v", err)
-			}
-			t.Cleanup(func() {
-				os.RemoveAll(tempDir)
-			})
-
-			fullPath := tempDir + "/func.yaml"
-			tempFile, err := os.Create(fullPath)
-			if err != nil {
-				t.Fatalf("temp file couldn't be created %v", err)
-			}
-			_, err = tempFile.WriteString(tt.fileContents)
-			if err != nil {
-				t.Fatalf("file content was not written %v", err)
-			}
-
-			client, err := newBuildClient(tt.cfg)
-			if err != nil {
-				t.Error(err)
-			}
-
-			defer func() {
-				if r := recover(); r != nil && tt.succeed {
-					t.Errorf("expected function call to succeed %v, got actually %v", tt.succeed, r)
-				}
-			}()
-			err = client.Push(context.TODO(), tempDir)
-			if err != nil {
-				t.Error(err)
-			}
-		})
-	}
-}
diff --git a/cmd/create_test.go b/cmd/create_test.go
index 28ff9f97..31711fbf 100644
--- a/cmd/create_test.go
+++ b/cmd/create_test.go
@@ -18,7 +18,7 @@ func TestCreate_Execute(t *testing.T) {
 	defer fromTempDir(t)()
 
 	// command with a client factory which yields a fully default client.
-	cmd := NewCreateCmd(func(createConfig) *fn.Client { return fn.New() })
+	cmd := NewCreateCmd(func(ClientOptions) *fn.Client { return fn.New() })
 	cmd.SetArgs([]string{
 		fmt.Sprintf("--language=%s", "go"),
 	})
@@ -34,7 +34,7 @@ func TestCreate_NoRuntime(t *testing.T) {
 	defer fromTempDir(t)()
 
 	// command with a client factory which yields a fully default client.
-	cmd := NewCreateCmd(func(createConfig) *fn.Client { return fn.New() })
+	cmd := NewCreateCmd(func(ClientOptions) *fn.Client { return fn.New() })
 	cmd.SetArgs([]string{})
 
 	err := cmd.Execute()
@@ -50,7 +50,7 @@ func TestCreate_WithInvalidRuntime(t *testing.T) {
 	defer fromTempDir(t)()
 
 	// command with a client factory which yields a fully default client.
-	cmd := NewCreateCmd(func(createConfig) *fn.Client { return fn.New() })
+	cmd := NewCreateCmd(func(ClientOptions) *fn.Client { return fn.New() })
 	cmd.SetArgs([]string{
 		fmt.Sprintf("--language=%s", "test"),
 	})
@@ -68,7 +68,7 @@ func TestCreate_InvalidTemplate(t *testing.T) {
 	defer fromTempDir(t)()
 
 	// command with a client factory which yields a fully default client.
-	cmd := NewCreateCmd(func(createConfig) *fn.Client { return fn.New() })
+	cmd := NewCreateCmd(func(ClientOptions) *fn.Client { return fn.New() })
 	cmd.SetArgs([]string{
 		fmt.Sprintf("--language=%s", "go"),
 		fmt.Sprintf("--template=%s", "events"),
@@ -88,7 +88,7 @@ func TestCreate_ValidatesName(t *testing.T) {
 
 	// Create a new Create command with a fn.Client construtor
 	// which returns a default (noop) client suitable for tests.
-	cmd := NewCreateCmd(func(createConfig) *fn.Client { return fn.New() })
+	cmd := NewCreateCmd(func(ClientOptions) *fn.Client { return fn.New() })
 
 	// Execute the command with a function name containing invalid characters and
 	// confirm the expected error is returned
@@ -121,7 +121,7 @@ func TestCreate_RepositoriesPath(t *testing.T) {
 	// after flags, environment variables, etc. are calculated.  In this case it
 	// will validate the test condition:  that config reflects the value of
 	// XDG_CONFIG_HOME, and secondarily the path suffix `func/repositories`.
-	cmd := NewCreateCmd(func(cfg createConfig) *fn.Client {
+	cmd := NewCreateCmd(func(cfg ClientOptions) *fn.Client {
 		if cfg.Repositories != expected {
 			t.Fatalf("expected repositories default path to be '%v', got '%v'", expected, cfg.Repositories)
 		}
diff --git a/cmd/delete_test.go b/cmd/delete_test.go
index 407d6948..a21ed4c8 100644
--- a/cmd/delete_test.go
+++ b/cmd/delete_test.go
@@ -28,8 +28,8 @@ func TestDelete_ByName(t *testing.T) {
 
 	// Create a command with a client constructor fn that instantiates a client
 	// with a the mocked remover.
-	cmd := NewDeleteCmd(func(_ deleteConfig) (*fn.Client, error) {
-		return fn.New(fn.WithRemover(remover)), nil
+	cmd := NewDeleteCmd(func(ClientOptions) *fn.Client {
+		return fn.New(fn.WithRemover(remover))
 	})
 
 	// Execute the command
@@ -80,8 +80,8 @@ created: 2021-01-01T00:00:00+00:00
 
 	// Command with a Client constructor that returns  client with the
 	// mocked remover.
-	cmd := NewDeleteCmd(func(_ deleteConfig) (*fn.Client, error) {
-		return fn.New(fn.WithRemover(remover)), nil
+	cmd := NewDeleteCmd(func(ClientOptions) *fn.Client {
+		return fn.New(fn.WithRemover(remover))
 	})
 
 	// Execute the command simulating no arguments.
@@ -108,8 +108,8 @@ func TestDelete_NameAndPathExclusivity(t *testing.T) {
 	remover := mock.NewRemover()
 
 	// Command with a Client constructor using the mock remover.
-	cmd := NewDeleteCmd(func(_ deleteConfig) (*fn.Client, error) {
-		return fn.New(fn.WithRemover(remover)), nil
+	cmd := NewDeleteCmd(func(ClientOptions) *fn.Client {
+		return fn.New(fn.WithRemover(remover))
 	})
 
 	// Execute the command simulating the invalid argument combination of both