Repository: knative-sandbox/kn-plugin-func
Pull request #907 (2022-03-16 18:58:09)

doc: add documentation for using podman remotely

- 📖   This commit adds more documentation for using podman, specifically in this case, connecting to and using a remote podman service from a Mac computer.

/kind documentation


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/907#" 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":[]} -->
matejvasek: I find the name `registry` confusing there, it suggest relation to image registry in my mind.
Maybe rename it to `my-remote-machine`.
codecov[bot]: # [Codecov](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/907?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) Report
> Merging [#907](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/907?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) (359e18f) into [main](https://codecov.io/gh/knative-sandbox/kn-plugin-func/commit/c60079d7921f2e5dd28e071ff541b48b0ae9cd53?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) (c60079d) will **increase** coverage by `0.19%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##             main     #907      +/-   ##
==========================================
+ Coverage   42.68%   42.87%   +0.19%     
==========================================
  Files          55       55              
  Lines        5164     5185      +21     
==========================================
+ Hits         2204     2223      +19     
+ Misses       2653     2649       -4     
- Partials      307      313       +6     
```


| [Impacted Files](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/907?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox) | Coverage Δ | |
|---|---|---|
| [repository.go](https://codecov.io/gh/knative-sandbox/kn-plugin-func/pull/907/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=knative-sandbox#diff-cmVwb3NpdG9yeS5nbw==) | `70.32% <0.00%> (+2.62%)` | :arrow_up: |

------

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

lance: > I find the name `registry` confusing there, it suggest relation to image registry in my mind. Maybe rename it to `my-remote-machine`.

@matejvasek I renamed to `podman-remote` - wdyt?
matejvasek: I think that's fine.

Patch:
diff --git a/client.go b/client.go
index dde8e477..e4996762 100644
--- a/client.go
+++ b/client.go
@@ -246,7 +246,7 @@ func ConfigPath() (path string) {
 
 // RepositoriesPath accesses the currently effective repositories path,
 // which defaults to [ConfigPath]/repositories but can be set explicitly using
-// the WithRepositories option when creating the client..
+// the WithRepositoriesPath option when creating the client..
 // The path will be created if it does not already exist.
 func (c *Client) RepositoriesPath() (path string) {
 	path = c.repositories.Path()
@@ -256,7 +256,7 @@ func (c *Client) RepositoriesPath() (path string) {
 
 // RepositoriesPath is a convenience method for accessing the default path to
 // repositories that will be used by new instances of a Client unless options
-// such as WithRepositories are used to override.
+// such as WithRepositoriesPath are used to override.
 // The path will be created if it does not already exist.
 func RepositoriesPath() string {
 	return New().RepositoriesPath()
@@ -342,10 +342,10 @@ func WithDNSProvider(provider DNSProvider) Option {
 	}
 }
 
-// WithRepositories sets the location to use for extensible template
+// WithRepositoriesPath sets the location on disk to use for extensible template
 // repositories.  Extensible template repositories are additional templates
 // that exist on disk and are not built into the binary.
-func WithRepositories(path string) Option {
+func WithRepositoriesPath(path string) Option {
 	return func(c *Client) {
 		c.repositoriesPath = path
 	}
diff --git a/cmd/client.go b/cmd/client.go
index ed793faf..4427b62a 100644
--- a/cmd/client.go
+++ b/cmd/client.go
@@ -13,11 +13,11 @@ import (
 )
 
 type ClientOptions struct {
-	Namespace    string
-	Registry     string
-	Repository   string
-	Repositories string
-	Verbose      bool
+	Namespace        string
+	Registry         string
+	Repository       string
+	RepositoriesPath string
+	Verbose          bool
 }
 
 type ClientFactory func(opts ClientOptions) *fn.Client
@@ -99,8 +99,8 @@ func NewDefaultClientFactory() (newClient ClientFactory, cleanUp func() error) {
 			fn.WithPusher(pusher),
 		}
 
-		if clientOptions.Repositories != "" {
-			opts = append(opts, fn.WithRepositories(clientOptions.Repositories)) // path to repositories in disk
+		if clientOptions.RepositoriesPath != "" {
+			opts = append(opts, fn.WithRepositoriesPath(clientOptions.RepositoriesPath)) // path to repositories in disk
 		}
 
 		return fn.New(opts...)
diff --git a/cmd/create.go b/cmd/create.go
index 65c4872f..d56ffca0 100644
--- a/cmd/create.go
+++ b/cmd/create.go
@@ -27,9 +27,9 @@ type ErrInvalidTemplate error
 
 func createConfigToClientOptions(cfg createConfig) ClientOptions {
 	return ClientOptions{
-		Repositories: cfg.Repositories,
-		Repository:   cfg.Repository,
-		Verbose:      cfg.Verbose,
+		RepositoriesPath: cfg.RepositoriesPath,
+		Repository:       cfg.Repository,
+		Verbose:          cfg.Verbose,
 	}
 }
 
@@ -65,6 +65,7 @@ DESCRIPTION
 
 	To install more language runtimes and their templates see '{{.Name}} repository'.
 
+
 EXAMPLES
 	o Create a Node.js Function (the default language runtime) in the current
 	  directory (the default path) which handles http events (the default
@@ -186,12 +187,6 @@ type createConfig struct {
 	Verbose    bool   // Verbose output
 	Confirm    bool   // Confirm values via an interactive prompt
 
-	// Repositories is an optional path that, if it exists, will be used as a source
-	// for additional template repositories not included in the binary.  provided via
-	// env (FUNC_REPOSITORIES), the default location is $XDG_CONFIG_HOME/repositories
-	// ($HOME/.config/func/repositories)
-	Repositories string
-
 	// Template is the code written into the new Function project, including
 	// an implementation adhering to one of the supported function signatures.
 	// May also include additional configuration settings or examples.
@@ -203,6 +198,14 @@ type createConfig struct {
 
 	// Name of the Function
 	Name string
+
+	// RepositoriesPath is an optional configuration setting (not set via flags)
+	// which overrides the location on disk at which to search for installed
+	// repositories.
+	// Override using $FUNC_REPOSITORIES_PATH
+	// Default value is $XDG_CONFIG_HOME/func/repositories
+	// (~/.config/func/repositories)
+	RepositoriesPath string
 }
 
 // newCreateConfig returns a config populated from the current execution context
@@ -212,10 +215,10 @@ type createConfig struct {
 // current value of the config at time of prompting.
 func newCreateConfig(cmd *cobra.Command, args []string, newClient ClientFactory) (cfg createConfig, err error) {
 	var (
-		path         string
-		dirName      string
-		absolutePath string
-		repositories string
+		path             string
+		dirName          string
+		absolutePath     string
+		repositoriesPath string
 	)
 
 	if len(args) >= 1 {
@@ -231,22 +234,22 @@ func newCreateConfig(cmd *cobra.Command, args []string, newClient ClientFactory)
 	// Not exposed as a flag due to potential confusion with the more likely
 	// "repository override" flag, and due to its unlikliness of being needed, but
 	// it is still available as an environment variable.
-	repositories = os.Getenv("FUNC_REPOSITORIES")
-	if repositories == "" { // if no env var provided
-		repositories = fn.New().RepositoriesPath() // use ~/.config/func/repositories
+	repositoriesPath = os.Getenv("FUNC_REPOSITORIES_PATH")
+	if repositoriesPath == "" { // if no env var provided
+		repositoriesPath = fn.New().RepositoriesPath() // use ~/.config/func/repositories
 	}
 
 	// Config is the final default values based off the execution context.
 	// When prompting, these become the defaults presented.
 	cfg = createConfig{
-		Name:         dirName, // TODO: refactor to be git-like
-		Path:         absolutePath,
-		Repositories: repositories,
-		Repository:   viper.GetString("repository"),
-		Runtime:      viper.GetString("language"), // users refer to it is language
-		Template:     viper.GetString("template"),
-		Confirm:      viper.GetBool("confirm"),
-		Verbose:      viper.GetBool("verbose"),
+		Name:             dirName, // TODO: refactor to be git-like
+		Path:             absolutePath,
+		RepositoriesPath: repositoriesPath,
+		Repository:       viper.GetString("repository"),
+		Runtime:          viper.GetString("language"), // users refer to it is language
+		Template:         viper.GetString("template"),
+		Confirm:          viper.GetBool("confirm"),
+		Verbose:          viper.GetBool("verbose"),
 	}
 	// If not in confirm/prompting mode, this cfg structure is complete.
 	if !cfg.Confirm {
@@ -277,8 +280,6 @@ func newCreateConfig(cmd *cobra.Command, args []string, newClient ClientFactory)
 	fmt.Printf("Language:     %v\n", cfg.Runtime) // users refer to it as language
 	if cfg.Repository != "" {                     // if an override was provided
 		fmt.Printf("Repository:   %v\n", cfg.Repository) // show only the override
-	} else {
-		fmt.Printf("Repositories: %v\n", cfg.Repositories) // or path to installed
 	}
 	fmt.Printf("Template:     %v\n", cfg.Template)
 	return
diff --git a/cmd/repository.go b/cmd/repository.go
index bc6b6feb..d6f04f05 100644
--- a/cmd/repository.go
+++ b/cmd/repository.go
@@ -26,7 +26,7 @@ func newRepositoryClient(args []string) (repositoryConfig, RepositoryClient, err
 		return cfg, nil, err
 	}
 	client := repositoryClient{fn.New(
-		fn.WithRepositories(cfg.Repositories),
+		fn.WithRepositoriesPath(cfg.RepositoriesPath),
 		fn.WithVerbose(cfg.Verbose))}
 	return cfg, client, nil
 }
@@ -56,12 +56,6 @@ DESCRIPTION
 	flag.  Once added, a template from the repository can be used when creating
 	a new Function.
 
-	Alternative Repositories Location:
-	Repositories are stored on disk in ~/.config/func/repositories by default.
-	This location can be altered by either setting the FUNC_REPOSITORIES
-	environment variable, or by providing the --repositories (-r) flag to any
-	of the commands.  XDG_CONFIG_HOME is respected when determining the default.
-
 	Interactive Prompts:
 	To complete these commands interactively, pass the --confirm (-c) flag to
 	the 'repository' command, or any of the inidivual subcommands.
@@ -85,6 +79,12 @@ DESCRIPTION
 			--template hello-world \
 			--repository https://github.com/boson-project/templates
 
+	Alternative Repositories Location:
+	Repositories are stored on disk in ~/.config/func/repositories by default.
+	This location can be altered by setting the FUNC_REPOSITORIES_PATH
+	environment variable.
+
+
 COMMANDS
 
 	With no arguments, this help text is shown.  To manage repositories with
@@ -118,7 +118,7 @@ COMMANDS
 	  entirely.  When in confirm mode (--confirm) it will confirm before
 	  deletion, but in regular mode this is done immediately, so please use
 	  caution, especially when using an altered repositories location
-	  (FUNC_REPOSITORIES environment variable or --repositories).
+	  (via the FUNC_REPOSITORIES_PATH environment variable).
 	    $ {{.Name}} repository remove <name>
 
 EXAMPLES
@@ -156,11 +156,10 @@ EXAMPLES
 	  default
 `,
 		SuggestFor: []string{"repositories", "repos", "template", "templates", "pack", "packs"},
-		PreRunE:    bindEnv("repositories", "confirm"),
+		PreRunE:    bindEnv("confirm"),
 	}
 
 	cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all options interactively (Env: $FUNC_CONFIRM)")
-	cmd.Flags().StringP("repositories", "r", fn.RepositoriesPath(), "Path to language pack repositories (Env: $FUNC_REPOSITORIES)")
 
 	cmd.SetHelpFunc(defaultTemplatedHelp)
 
@@ -178,13 +177,10 @@ EXAMPLES
 
 func NewRepositoryListCmd(clientFn repositoryClientFn) *cobra.Command {
 	cmd := &cobra.Command{
-		Short:   "List repositories",
-		Use:     "list",
-		PreRunE: bindEnv("repositories"),
+		Short: "List repositories",
+		Use:   "list",
 	}
 
-	cmd.Flags().StringP("repositories", "r", fn.RepositoriesPath(), "Path to language pack repositories (Env: $FUNC_REPOSITORIES)")
-
 	cmd.RunE = func(_ *cobra.Command, args []string) error {
 		return runRepositoryList(args, clientFn)
 	}
@@ -196,11 +192,10 @@ func NewRepositoryAddCmd(clientFn repositoryClientFn) *cobra.Command {
 		Short:      "Add a repository",
 		Use:        "add <name> <url>",
 		SuggestFor: []string{"ad", "install"},
-		PreRunE:    bindEnv("repositories", "confirm"),
+		PreRunE:    bindEnv("confirm"),
 	}
 
 	cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all options interactively (Env: $FUNC_CONFIRM)")
-	cmd.Flags().StringP("repositories", "r", fn.RepositoriesPath(), "Path to language pack repositories (Env: $FUNC_REPOSITORIES)")
 
 	cmd.RunE = func(_ *cobra.Command, args []string) error {
 		return runRepositoryAdd(args, clientFn)
@@ -212,11 +207,10 @@ func NewRepositoryRenameCmd(clientFn repositoryClientFn) *cobra.Command {
 	cmd := &cobra.Command{
 		Short:   "Rename a repository",
 		Use:     "rename <old> <new>",
-		PreRunE: bindEnv("repositories", "confirm"),
+		PreRunE: bindEnv("confirm"),
 	}
 
 	cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all options interactively (Env: $FUNC_CONFIRM)")
-	cmd.Flags().StringP("repositories", "r", fn.RepositoriesPath(), "Path to language pack repositories (Env: $FUNC_REPOSITORIES)")
 
 	cmd.RunE = func(_ *cobra.Command, args []string) error {
 		return runRepositoryRename(args, clientFn)
@@ -230,11 +224,10 @@ func NewRepositoryRemoveCmd(clientFn repositoryClientFn) *cobra.Command {
 		Use:        "remove <name>",
 		Aliases:    []string{"rm"},
 		SuggestFor: []string{"delete", "del"},
-		PreRunE:    bindEnv("repositories", "confirm"),
+		PreRunE:    bindEnv("confirm"),
 	}
 
 	cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all options interactively (Env: $FUNC_CONFIRM)")
-	cmd.Flags().StringP("repositories", "r", fn.RepositoriesPath(), "Path to language pack repositories (Env: $FUNC_REPOSITORIES)")
 
 	cmd.RunE = func(_ *cobra.Command, args []string) error {
 		return runRepositoryRemove(args, clientFn)
@@ -580,9 +573,9 @@ func installedRepositories(client RepositoryClient) ([]string, error) {
 
 // repositoryConfig used for instantiating a fn.Client
 type repositoryConfig struct {
-	Repositories string // Path to repos to be managed
-	Verbose      bool   // Enables verbose logging
-	Confirm      bool   // Enables interactive confirmation/prompting mode
+	RepositoriesPath string // Path to repos to be managed
+	Verbose          bool   // Enables verbose logging
+	Confirm          bool   // Enables interactive confirmation/prompting mode
 }
 
 // newRepositoryConfig creates a configuration suitable for use instantiating the
@@ -593,9 +586,15 @@ func newRepositoryConfig(args []string) (cfg repositoryConfig, err error) {
 	// first populated by static defaults, then environment variables,
 	// finally command flags.
 	cfg = repositoryConfig{
-		Repositories: viper.GetString("repositories"),
-		Verbose:      viper.GetBool("verbose"),
-		Confirm:      viper.GetBool("confirm"),
+		Verbose: viper.GetBool("verbose"),
+		Confirm: viper.GetBool("confirm"),
+	}
+
+	// Repositories Path
+	// Use env var to alter the default of ~/.config/func/repositories
+	cfg.RepositoriesPath = os.Getenv("FUNC_REPOSITORIES_PATH")
+	if cfg.RepositoriesPath == "" {
+		cfg.RepositoriesPath = fn.New().RepositoriesPath()
 	}
 
 	// If not in confirm (interactive prompting) mode,
@@ -612,7 +611,7 @@ func newRepositoryConfig(args []string) (cfg repositoryConfig, err error) {
 
 	// Noninteractive terminals in confirm/prompt mode simply echo
 	// effective values to stdout.
-	fmt.Fprintf(os.Stdout, "Repositories path: %v\n", cfg.Repositories)
+	fmt.Fprintf(os.Stdout, "Repositories path: %v\n", cfg.RepositoriesPath)
 	fmt.Fprintf(os.Stdout, "Verbose logging:   %v\n", cfg.Verbose)
 	return
 }
@@ -639,7 +638,7 @@ func (c repositoryConfig) prompt() (repositoryConfig, error) {
 			Name: "Repositories",
 			Prompt: &survey.Input{
 				Message: "Path to repositories:",
-				Default: c.Repositories,
+				Default: c.RepositoriesPath,
 			},
 			Validate: survey.Required,
 		}, {
diff --git a/docs/guides/commands.md b/docs/guides/commands.md
index ec6c8bc8..6e8b1057 100644
--- a/docs/guides/commands.md
+++ b/docs/guides/commands.md
@@ -235,15 +235,14 @@ $ func repository -c
 ```
 
 Manages template repositories installed on disk at either the default location
-(~/.config/func/repositories) or the location specified by the --repository
-flag.  Once added, a template from the repository can be used when creating
-a new Function.
+(~/.config/func/repositories) or the location specified by the
+FUNC_REPOSITORIES_PATH environment variable.  Once added, a template from the
+repository can be used when creating a new Function.
 
 _Alternative Repositories Location:_
 Repositories are stored on disk in ~/.config/func/repositories by default.
-This location can be altered by either setting the FUNC_REPOSITORIES
-environment variable, or by providing the --repositories (-r) flag to any
-of the commands.  XDG_CONFIG_HOME is respected when determining the default.
+This location can be altered by setting the FUNC_REPOSITORIES_PATH
+environment variable.  XDG_CONFIG_HOME is respected when determining the default.
 
 _Interactive Prompts:_
 To complete these commands interactively, pass the --confirm (-c) flag to
@@ -309,7 +308,7 @@ Remove a repository by name.  Removes the repository from local storage
 entirely.  When in confirm mode (--confirm) it will confirm before
 deletion, but in regular mode this is done immediately, so please use
 caution, especially when using an altered repositories location
-(FUNC_REPOSITORIES environment variable or --repositories).
+(FUNC_REPOSITORIES_PATH environment variable).
 ```console
 $ func repository remove <name>
 ```
diff --git a/docs/guides/podman.md b/docs/guides/podman.md
index d3e88475..ea534992 100644
--- a/docs/guides/podman.md
+++ b/docs/guides/podman.md
@@ -29,9 +29,60 @@ Under macOS you need to use `podman machine` as `podman` is native to Linux.
 ❯ podman machine init --cpus=2 --disk-size=30 --memory=8192
 ❯ podman machine start
 ❯ ssh-add -k $HOME/.ssh/podman-machine-default
-❯ export DOCKER_HOST=(podman system connection ls --format="{{.URI}}" | grep root)
+❯ export DOCKER_HOST=$(podman system connection ls --format="{{.URI}}" | grep root)
 ```
 
+### Remote connections
+
+You may also connect to a remote `podman` running on a remote Linux server. Follow the instructions above for running `podman` as a service on a Linux server. You will connect to this via SSH when `func` builds a project. In addition to having `podman` on the remote Linux server, you will also need to run the SSH daemon. Installing, configuring and running SSH is outside of the scope of this project. Please work with a system administrator to ensure that this prerequisite is met.
+
+To use `podman` remotely, create (or reuse) a default key on your client MacOS computer. If you do not already have a key created, you can create one with the following command.
+
+```
+ssh-keygen -t ed255519
+```
+
+This will create a public, private key pair. By default, if you followed the command above, the public key will be in `~/.ssh/id_ed255519.pub`. Add the contents of this file to the `authorized_keys` file on the remote Linux server. This should conclude the SSH configuration required.
+
+Once you have SSH configured, you need to configure `podman` on the local macOS computer. To add the remote connection to `podman` you will need to know the IP address or hostname of the Linux server that you are connecting to. You will also need to know the location of the unix socket used by `podman`. To find the location, you can issue the following command on the Linux server.
+
+```
+podman info | grep podman.sock
+```
+
+This should display something like `path: /run/user/1000/podman/podman.sock`. When you have this path, and the hostname or IP address of the Linux server, you can configure `podman` with the following command.
+
+```
+podman --remote system connection add podman-remote --identity ~/.ssh/id_ed255519 ssh://username@hostname/path/to/podman.socket
+```
+
+In the example above, a connection called `podman-remote` is added, which will connect using the SSH key created earlier to the host at `hostname` as the user `username`. Here is a real world example.
+
+```
+podman --remote system connection add podman-remote --identity ~/.ssh/id_podman_client ssh://lanceball@192.168.1.203/run/user/1000/podman/podman.sock
+```
+
+To make the newly configured remote the default for `podman` on your laptop, run the following command.
+
+```
+podman --remote system connection default podman-remote
+```
+
+To check if it was successfully added, run the following command. You should see the `podman-remote` connection added.
+
+```
+❯ podman --remote system connection list
+Name                         Identity                                  URI
+podman-remote*               /Users/lball/.ssh/id_podman_client        ssh://lanceball@192.168.1.203:22/run/user/1000/podman/podman.sock
+```
+
+Finally, to ensure that `func` knows the address of your newly configured registry, set and export the `DOCKER_HOST` environment variable with the following command.
+
+```
+export DOCKER_HOST=$(podman system connection ls --format="{{.URI}}" | head -1)
+```
+
+
 ## Known issues
 
 * In func version 0.19, some runtimes might be unable to build a function by using podman. You might see an error message similar to the following:


Test patch:
diff --git a/client_test.go b/client_test.go
index 010f42de..c97c305f 100644
--- a/client_test.go
+++ b/client_test.go
@@ -212,9 +212,9 @@ func TestClient_New_HiddenFilesIgnored(t *testing.T) {
 // using a custom path to template repositories on disk. The custom repositories
 // location is not defined herein but expected to be provided because, for
 // example, a CLI may want to use XDG_CONFIG_HOME.  Assuming a repository path
-// $FUNC_REPOSITORIES, a Go template named 'json' which is provided in the
+// $FUNC_REPOSITORIES_PATH, a Go template named 'json' which is provided in the
 // repository 'boson', would be expected to be in the location:
-// $FUNC_REPOSITORIES/boson/go/json
+// $FUNC_REPOSITORIES_PATH/boson/go/json
 // See the CLI for full details, but a standard default location is
 // $HOME/.config/func/repositories/boson/go/json
 func TestClient_New_RepositoriesExtensible(t *testing.T) {
@@ -222,7 +222,7 @@ func TestClient_New_RepositoriesExtensible(t *testing.T) {
 	defer Using(t, root)()
 
 	client := fn.New(
-		fn.WithRepositories("testdata/repositories"),
+		fn.WithRepositoriesPath("testdata/repositories"),
 		fn.WithRegistry(TestRegistry))
 
 	// Create a Function specifying a template which only exists in the extensible set
@@ -262,7 +262,7 @@ func TestClient_New_RuntimeNotFoundCustom(t *testing.T) {
 
 	// Create a new client with path to the extensible templates
 	client := fn.New(
-		fn.WithRepositories("testdata/repositories"),
+		fn.WithRepositoriesPath("testdata/repositories"),
 		fn.WithRegistry(TestRegistry))
 
 	// Create a Function specifying a runtime, 'python' that does not exist
@@ -300,7 +300,7 @@ func TestClient_New_TemplateNotFoundCustom(t *testing.T) {
 
 	// Create a new client with path to extensible templates
 	client := fn.New(
-		fn.WithRepositories("testdata/repositories"),
+		fn.WithRepositoriesPath("testdata/repositories"),
 		fn.WithRegistry(TestRegistry))
 
 	// An invalid template, but a valid custom provider
@@ -1005,7 +1005,7 @@ func TestClient_Runtimes(t *testing.T) {
 	// TODO: test when a specific repo override is indicated
 	// (remote repo which takes precidence over embedded and extended)
 
-	client := fn.New(fn.WithRepositories("testdata/repositories"))
+	client := fn.New(fn.WithRepositoriesPath("testdata/repositories"))
 
 	runtimes, err := client.Runtimes()
 	if err != nil {
diff --git a/cmd/create_test.go b/cmd/create_test.go
index 31711fbf..efd8b17b 100644
--- a/cmd/create_test.go
+++ b/cmd/create_test.go
@@ -122,8 +122,8 @@ func TestCreate_RepositoriesPath(t *testing.T) {
 	// 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 ClientOptions) *fn.Client {
-		if cfg.Repositories != expected {
-			t.Fatalf("expected repositories default path to be '%v', got '%v'", expected, cfg.Repositories)
+		if cfg.RepositoriesPath != expected {
+			t.Fatalf("expected repositories default path to be '%v', got '%v'", expected, cfg.RepositoriesPath)
 		}
 		return fn.New()
 	})
diff --git a/cmd/repository_test.go b/cmd/repository_test.go
index f15cfa4b..5c42a2a2 100644
--- a/cmd/repository_test.go
+++ b/cmd/repository_test.go
@@ -10,17 +10,16 @@ import (
 )
 
 // TestRepository_List ensures that the 'list' subcommand shows the client's
-// set of repositories by name, respects the repositories flag (provides it to
-// the client), and prints the list as expected.
+// set of repositories by name and prints the list as expected.
 func TestRepository_List(t *testing.T) {
 	var (
 		client = mock.NewClient()
 		list   = NewRepositoryListCmd(testRepositoryClientFn(client))
 	)
 
-	// Set the repositories flag, which will be passed to the client instance
+	// Set the repositories path, which will be passed to the client instance
 	// in the form of a config.
-	list.SetArgs([]string{"--repositories=testpath"})
+	os.Setenv("FUNC_REPOSITORIES_PATH", "testpath")
 
 	// Execute the command, capturing the output sent to stdout
 	stdout := piped(t)
@@ -30,7 +29,7 @@ func TestRepository_List(t *testing.T) {
 
 	// Assert the repository flag setting was preserved during execution
 	if client.RepositoriesPath != "testpath" {
-		t.Fatal("repositories flag not passed to client")
+		t.Fatal("repositories env not passed to client")
 	}
 
 	// Assert the output matches expectd (whitespace trimmed)
@@ -51,9 +50,10 @@ func TestRepository_Add(t *testing.T) {
 		list   = NewRepositoryListCmd(testRepositoryClientFn(client))
 		stdout = piped(t)
 	)
+	os.Setenv("FUNC_REPOSITORIES_PATH", "testpath")
+
 	// add [flags] <old> <new>
 	add.SetArgs([]string{
-		"--repositories=testpath",
 		"newrepo",
 		"https://git.example.com/user/repo",
 	})
@@ -65,7 +65,7 @@ func TestRepository_Add(t *testing.T) {
 
 	// Assert the repositories flag was parsed and provided to client
 	if client.RepositoriesPath != "testpath" {
-		t.Fatal("repositories flag not passed to client")
+		t.Fatal("repositories path not passed to client")
 	}
 
 	// List post-add, capturing output from stdout
@@ -92,6 +92,7 @@ func TestRepository_Rename(t *testing.T) {
 		list   = NewRepositoryListCmd(testRepositoryClientFn(client))
 		stdout = piped(t)
 	)
+	os.Setenv("FUNC_REPOSITORIES_PATH", "testpath")
 
 	// add a repo which will be renamed
 	add.SetArgs([]string{"newrepo", "https://git.example.com/user/repo"})
@@ -101,7 +102,6 @@ func TestRepository_Rename(t *testing.T) {
 
 	// rename [flags] <old> <new>
 	rename.SetArgs([]string{
-		"--repositories=testpath",
 		"newrepo",
 		"renamed",
 	})
@@ -113,7 +113,7 @@ func TestRepository_Rename(t *testing.T) {
 
 	// Assert the repositories flag was parsed and provided to client
 	if client.RepositoriesPath != "testpath" {
-		t.Fatal("repositories flag not passed to client")
+		t.Fatal("repositories path not passed to client")
 	}
 
 	// List post-rename, capturing output from stdout
@@ -140,6 +140,7 @@ func TestRepository_Remove(t *testing.T) {
 		list   = NewRepositoryListCmd(testRepositoryClientFn(client))
 		stdout = piped(t)
 	)
+	os.Setenv("FUNC_REPOSITORIES_PATH", "testpath")
 
 	// add a repo which will be removed
 	add.SetArgs([]string{"newrepo", "https://git.example.com/user/repo"})
@@ -149,7 +150,6 @@ func TestRepository_Remove(t *testing.T) {
 
 	// remove [flags] <name>
 	remove.SetArgs([]string{
-		"--repositories=testpath",
 		"newrepo",
 	})
 
@@ -189,7 +189,7 @@ func testRepositoryClientFn(client *mock.Client) repositoryClientFn {
 	return func(args []string) (repositoryConfig, RepositoryClient, error) {
 		cfg, err := newRepositoryConfig(args)
 		client.Confirm = cfg.Confirm
-		client.RepositoriesPath = cfg.Repositories
+		client.RepositoriesPath = cfg.RepositoriesPath
 		if err != nil {
 			return cfg, c, err
 		}
diff --git a/repositories_test.go b/repositories_test.go
index a752e4e1..f4d16532 100644
--- a/repositories_test.go
+++ b/repositories_test.go
@@ -27,7 +27,7 @@ func TestRepositories_List(t *testing.T) {
 	root, rm := Mktemp(t)
 	defer rm()
 
-	client := fn.New(fn.WithRepositories(root)) // Explicitly empty
+	client := fn.New(fn.WithRepositoriesPath(root)) // Explicitly empty
 
 	rr, err := client.Repositories().List()
 	if err != nil {
@@ -42,7 +42,7 @@ func TestRepositories_List(t *testing.T) {
 // TestRepositories_GetInvalid ensures that attempting to get an invalid repo
 // results in error.
 func TestRepositories_GetInvalid(t *testing.T) {
-	client := fn.New(fn.WithRepositories("testdata/repositories"))
+	client := fn.New(fn.WithRepositoriesPath("testdata/repositories"))
 
 	// invalid should error
 	_, err := client.Repositories().Get("invalid")
@@ -53,7 +53,7 @@ func TestRepositories_GetInvalid(t *testing.T) {
 
 // TestRepositories_Get ensures a repository can be accessed by name.
 func TestRepositories_Get(t *testing.T) {
-	client := fn.New(fn.WithRepositories("testdata/repositories"))
+	client := fn.New(fn.WithRepositoriesPath("testdata/repositories"))
 
 	// valid should not error
 	repo, err := client.Repositories().Get("customTemplateRepo")
@@ -78,7 +78,7 @@ func TestRepositories_All(t *testing.T) {
 	root, rm := Mktemp(t)
 	defer rm()
 
-	client := fn.New(fn.WithRepositories(root))
+	client := fn.New(fn.WithRepositoriesPath(root))
 
 	// Assert initially only the default is included
 	rr, err := client.Repositories().All()
@@ -121,7 +121,7 @@ func TestRepositories_Add(t *testing.T) {
 
 	// Instantiate the client using the current temp directory as the
 	// repositories' root location.
-	client := fn.New(fn.WithRepositories(root))
+	client := fn.New(fn.WithRepositoriesPath(root))
 
 	// Add the repository, explicitly specifying a name.  See other tests for
 	// defaulting from repository names and manifest-defined name.
@@ -161,7 +161,7 @@ func TestRepositories_AddDeafultName(t *testing.T) {
 	root, rm := Mktemp(t)
 	defer rm()
 
-	client := fn.New(fn.WithRepositories(root))
+	client := fn.New(fn.WithRepositoriesPath(root))
 
 	name, err := client.Repositories().Add("", uri)
 	if err != nil {
@@ -200,7 +200,7 @@ func TestRepositories_AddWithManifest(t *testing.T) {
 	root, rm := Mktemp(t)
 	defer rm()
 
-	client := fn.New(fn.WithRepositories(root))
+	client := fn.New(fn.WithRepositoriesPath(root))
 
 	name, err := client.Repositories().Add("", uri)
 	if err != nil {
@@ -237,7 +237,7 @@ func TestRepositories_AddExistingErrors(t *testing.T) {
 
 	// Instantiate the client using the current temp directory as the
 	// repositories' root location.
-	client := fn.New(fn.WithRepositories(root))
+	client := fn.New(fn.WithRepositoriesPath(root))
 
 	// Add twice.
 	name := "example"
@@ -275,7 +275,7 @@ func TestRepositories_Rename(t *testing.T) {
 
 	// Instantiate the client using the current temp directory as the
 	// repositories' root location.
-	client := fn.New(fn.WithRepositories(root))
+	client := fn.New(fn.WithRepositoriesPath(root))
 
 	// Add and Rename
 	if _, err := client.Repositories().Add("foo", uri); err != nil {
@@ -309,7 +309,7 @@ func TestRepositories_Remove(t *testing.T) {
 
 	// Instantiate the client using the current temp directory as the
 	// repositories' root location.
-	client := fn.New(fn.WithRepositories(root))
+	client := fn.New(fn.WithRepositoriesPath(root))
 
 	// Add and Remove
 	name := "example"
@@ -346,7 +346,7 @@ func TestRepositories_URL(t *testing.T) {
 	root, rm := Mktemp(t)
 	defer rm()
 
-	client := fn.New(fn.WithRepositories(root))
+	client := fn.New(fn.WithRepositoriesPath(root))
 
 	// Add the test repo
 	_, err := client.Repositories().Add("newrepo", uri)
diff --git a/repository_test.go b/repository_test.go
index a56cbbbc..5ab8e35f 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -14,7 +14,7 @@ import (
 // TestRepository_TemplatesPath ensures that repositories can specify
 // an alternate location for templates using a manifest.
 func TestRepository_TemplatesPath(t *testing.T) {
-	client := fn.New(fn.WithRepositories("testdata/repositories"))
+	client := fn.New(fn.WithRepositoriesPath("testdata/repositories"))
 
 	// The repo ./testdata/repositories/customLanguagePackRepo includes a
 	// manifest.yaml which defines templates as existing in the ./templates
@@ -39,7 +39,7 @@ func TestRepository_TemplatesPath(t *testing.T) {
 // and template level.  The tests check for both embedded structures:
 // HealthEndpoints BuildConfig.
 func TestRepository_Inheritance(t *testing.T) {
-	client := fn.New(fn.WithRepositories("testdata/repositories"))
+	client := fn.New(fn.WithRepositoriesPath("testdata/repositories"))
 
 	// The repo ./testdata/repositories/customLanguagePack includes a manifest
 	// which defines custom readiness and liveness endpoints.
diff --git a/templates_test.go b/templates_test.go
index 6a4ed29a..c3d0d781 100644
--- a/templates_test.go
+++ b/templates_test.go
@@ -22,7 +22,7 @@ import (
 func TestTemplates_List(t *testing.T) {
 	// A client which specifies a location of exensible repositoreis on disk
 	// will list all builtin plus exensible
-	client := fn.New(fn.WithRepositories("testdata/repositories"))
+	client := fn.New(fn.WithRepositoriesPath("testdata/repositories"))
 
 	// list templates for the "go" runtime
 	templates, err := client.Templates().List("go")
@@ -49,7 +49,7 @@ func TestTemplates_List(t *testing.T) {
 // when retrieving the list of templates for a runtime that does not exist
 // in an extended repository, but does in the default.
 func TestTemplates_List_ExtendedNotFound(t *testing.T) {
-	client := fn.New(fn.WithRepositories("testdata/repositories"))
+	client := fn.New(fn.WithRepositoriesPath("testdata/repositories"))
 
 	// list templates for the "python" runtime -
 	// not supplied by the extended repos
@@ -71,7 +71,7 @@ func TestTemplates_List_ExtendedNotFound(t *testing.T) {
 // TestTemplates_Get ensures that a template's metadata object can
 // be retrieved by full name (full name prefix optional for embedded).
 func TestTemplates_Get(t *testing.T) {
-	client := fn.New(fn.WithRepositories("testdata/repositories"))
+	client := fn.New(fn.WithRepositoriesPath("testdata/repositories"))
 
 	// Check embedded
 	embedded, err := client.Templates().Get("go", "http")
@@ -135,7 +135,7 @@ func TestTemplates_Custom(t *testing.T) {
 	// at: testdata/repositories/[provider]/[runtime]/[template]
 	client := fn.New(
 		fn.WithRegistry(TestRegistry),
-		fn.WithRepositories("testdata/repositories"))
+		fn.WithRepositoriesPath("testdata/repositories"))
 
 	// Create a function specifying a template from
 	// the custom provider's directory in the on-disk template repo.
@@ -306,7 +306,7 @@ func TestTemplates_ModeCustom(t *testing.T) {
 
 	client := fn.New(
 		fn.WithRegistry(TestRegistry),
-		fn.WithRepositories("testdata/repositories"))
+		fn.WithRepositoriesPath("testdata/repositories"))
 
 	// Write executable from custom repo
 	err := client.Create(fn.Function{
@@ -392,7 +392,7 @@ func TestTemplates_RuntimeManifestBuildEnvs(t *testing.T) {
 	// Client whose internal templates will be used.
 	client := fn.New(
 		fn.WithRegistry(TestRegistry),
-		fn.WithRepositories("testdata/repositories"))
+		fn.WithRepositoriesPath("testdata/repositories"))
 
 	// write out a template
 	err := client.Create(fn.Function{
@@ -439,7 +439,7 @@ func TestTemplates_ManifestBuildEnvs(t *testing.T) {
 	// Client whose internal templates will be used.
 	client := fn.New(
 		fn.WithRegistry(TestRegistry),
-		fn.WithRepositories("testdata/repositories"))
+		fn.WithRepositoriesPath("testdata/repositories"))
 
 	// write out a template
 	err := client.Create(fn.Function{
@@ -486,7 +486,7 @@ func TestTemplates_RepositoryManifestBuildEnvs(t *testing.T) {
 	// Client whose internal templates will be used.
 	client := fn.New(
 		fn.WithRegistry(TestRegistry),
-		fn.WithRepositories("testdata/repositories"))
+		fn.WithRepositoriesPath("testdata/repositories"))
 
 	// write out a template
 	err := client.Create(fn.Function{
@@ -531,7 +531,7 @@ func TestTemplates_ManifestInvocationHints(t *testing.T) {
 
 	client := fn.New(
 		fn.WithRegistry(TestRegistry),
-		fn.WithRepositories("testdata/repositories"))
+		fn.WithRepositoriesPath("testdata/repositories"))
 
 	err := client.Create(fn.Function{
 		Root:     root,
@@ -562,7 +562,7 @@ func TestTemplates_ManifestRemoved(t *testing.T) {
 	// Client whose internal templates will be used.
 	client := fn.New(
 		fn.WithRegistry(TestRegistry),
-		fn.WithRepositories("testdata/repositories"))
+		fn.WithRepositoriesPath("testdata/repositories"))
 
 	// write out a template
 	err := client.Create(fn.Function{
@@ -596,7 +596,7 @@ func TestTemplates_InvocationDefault(t *testing.T) {
 
 	client := fn.New(
 		fn.WithRegistry(TestRegistry),
-		fn.WithRepositories("testdata/repositories"))
+		fn.WithRepositoriesPath("testdata/repositories"))
 
 	// The customTemplateRepo explicitly does not
 	// include manifests as it exemplifies an entirely default template repo.