Cleanup commands (#807)

* src: refactor commands

Commands are constructed from root,
not by using init() blocks.

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* fixup the prefix issue

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* fixup style

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* fixup nolint:misspell

Signed-off-by: Matej Vasek <mvasek@redhat.com>
This commit is contained in:
Matej Vasek 2022-02-03 19:26:21 +01:00 committed by GitHub
parent e1095e0509
commit abd4eea0c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 340 additions and 365 deletions

View File

@ -19,12 +19,6 @@ import (
"knative.dev/kn-plugin-func/progress"
)
func init() {
// Add to the root a new "Build" command which obtains an appropriate
// instance of fn.Client from the given client creator function.
root.AddCommand(NewBuildCmd(newBuildClient))
}
func newBuildClient(cfg buildConfig) (*fn.Client, error) {
builder := buildpacks.NewBuilder()
listener := progress.New()

View File

@ -7,15 +7,11 @@ import (
"github.com/spf13/cobra"
)
func init() {
root.AddCommand(completionCmd)
}
// completionCmd represents the completion command
var completionCmd = &cobra.Command{
Use: "completion <bash|zsh|fish>",
Short: "Generate completion scripts for bash, fish and zsh",
Long: `To load completion run
func NewCompletionCmd() *cobra.Command {
return &cobra.Command{
Use: "completion <bash|zsh|fish>",
Short: "Generate completion scripts for bash, fish and zsh",
Long: `To load completion run
For zsh:
source <(func completion zsh)
@ -28,23 +24,25 @@ For bash:
source <(func completion bash)
`,
ValidArgs: []string{"bash", "zsh", "fish"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) < 1 {
return errors.New("missing argument")
}
switch args[0] {
case "bash":
err = root.GenBashCompletion(os.Stdout)
case "zsh":
err = root.GenZshCompletion(os.Stdout)
case "fish":
err = root.GenFishCompletion(os.Stdout, true)
default:
err = errors.New("unknown shell, only bash, zsh and fish are supported")
}
ValidArgs: []string{"bash", "zsh", "fish"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if len(args) < 1 {
return errors.New("missing argument")
}
switch args[0] {
case "bash":
err = cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
err = cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
err = cmd.Root().GenFishCompletion(os.Stdout, true)
default:
err = errors.New("unknown shell, only bash, zsh and fish are supported")
}
return err
},
}
return err
},
}

View File

@ -43,24 +43,28 @@ func (s standardLoaderSaver) Save(f fn.Function) error {
var defaultLoaderSaver standardLoaderSaver
func init() {
root.AddCommand(configCmd)
setPathFlag(configCmd)
configCmd.AddCommand(NewConfigLabelsCmd(defaultLoaderSaver))
}
var configCmd = &cobra.Command{
Use: "config",
Short: "Configure a function",
Long: `Configure a function
func NewConfigCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Configure a function",
Long: `Configure a function
Interactive propmt that allows configuration of Volume mounts, Environment
variables, and Labels for a function project present in the current directory
or from the directory specified with --path.
`,
SuggestFor: []string{"cfg", "cofnig"},
PreRunE: bindEnv("path"),
RunE: runConfigCmd,
SuggestFor: []string{"cfg", "cofnig"},
PreRunE: bindEnv("path"),
RunE: runConfigCmd,
}
setPathFlag(cmd)
cmd.AddCommand(NewConfigLabelsCmd(defaultLoaderSaver))
cmd.AddCommand(NewConfigEnvsCmd())
cmd.AddCommand(NewConfigVolumesCmd())
return cmd
}
func runConfigCmd(cmd *cobra.Command, args []string) (err error) {

View File

@ -14,41 +14,47 @@ import (
"knative.dev/kn-plugin-func/utils"
)
func init() {
setPathFlag(configEnvsCmd)
setPathFlag(configEnvsAddCmd)
setPathFlag(configEnvsRemoveCmd)
configCmd.AddCommand(configEnvsCmd)
configEnvsCmd.AddCommand(configEnvsAddCmd)
configEnvsCmd.AddCommand(configEnvsRemoveCmd)
}
var configEnvsCmd = &cobra.Command{
Use: "envs",
Short: "List and manage configured environment variable for a function",
Long: `List and manage configured environment variable for a function
func NewConfigEnvsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "envs",
Short: "List and manage configured environment variable for a function",
Long: `List and manage configured environment variable for a function
Prints configured Environment variable for a function project present in
the current directory or from the directory specified with --path.
`,
SuggestFor: []string{"ensv", "env"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
SuggestFor: []string{"ensv", "env"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
listEnvs(function)
return
}
},
}
listEnvs(function)
configEnvsAddCmd := NewConfigEnvsAddCmd()
configEnvsRemoveCmd := NewConfigEnvsRemoveCmd()
return
},
setPathFlag(cmd)
setPathFlag(configEnvsAddCmd)
setPathFlag(configEnvsRemoveCmd)
cmd.AddCommand(configEnvsAddCmd)
cmd.AddCommand(configEnvsRemoveCmd)
return cmd
}
var configEnvsAddCmd = &cobra.Command{
Use: "add",
Short: "Add environment variable to the function configuration",
Long: `Add environment variable to the function configuration
func NewConfigEnvsAddCmd() *cobra.Command {
return &cobra.Command{
Use: "add",
Short: "Add environment variable to the function configuration",
Long: `Add environment variable to the function configuration
Interactive prompt to add Environment variables to the function project
in the current directory or from the directory specified with --path.
@ -56,36 +62,41 @@ in the current directory or from the directory specified with --path.
The environment variable can be set directly from a value,
from an environment variable on the local machine or from Secrets and ConfigMaps.
`,
SuggestFor: []string{"ad", "create", "insert", "append"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
SuggestFor: []string{"ad", "create", "insert", "append"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
return runAddEnvsPrompt(cmd.Context(), function)
},
}
return runAddEnvsPrompt(cmd.Context(), function)
},
}
var configEnvsRemoveCmd = &cobra.Command{
Use: "remove",
Short: "Remove environment variable from the function configuration",
Long: `Remove environment variable from the function configuration
func NewConfigEnvsRemoveCmd() *cobra.Command {
return &cobra.Command{
Use: "remove",
Short: "Remove environment variable from the function configuration",
Long: `Remove environment variable from the function configuration
Interactive prompt to remove Environment variables from the function project
in the current directory or from the directory specified with --path.
`,
SuggestFor: []string{"rm", "del", "delete", "rmeove"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
SuggestFor: []string{"rm", "del", "delete", "rmeove"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
return runRemoveEnvsPrompt(function)
},
}
return runRemoveEnvsPrompt(function)
},
}
func listEnvs(f fn.Function) {

View File

@ -13,75 +13,86 @@ import (
"knative.dev/kn-plugin-func/k8s"
)
func init() {
setPathFlag(configVolumesCmd)
setPathFlag(configVolumesAddCmd)
setPathFlag(configVolumesRemoveCmd)
configCmd.AddCommand(configVolumesCmd)
configVolumesCmd.AddCommand(configVolumesAddCmd)
configVolumesCmd.AddCommand(configVolumesRemoveCmd)
}
var configVolumesCmd = &cobra.Command{
Use: "volumes",
Short: "List and manage configured volumes for a function",
Long: `List and manage configured volumes for a function
func NewConfigVolumesCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "volumes",
Short: "List and manage configured volumes for a function",
Long: `List and manage configured volumes for a function
Prints configured Volume mounts for a function project present in
the current directory or from the directory specified with --path.
`,
SuggestFor: []string{"volums", "volume", "vols"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
SuggestFor: []string{"volums", "volume", "vols"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
listVolumes(function)
return
}
},
}
listVolumes(function)
configVolumesAddCmd := NewConfigVolumesAddCmd()
configVolumesRemoveCmd := NewConfigVolumesRemoveCmd()
return
},
setPathFlag(cmd)
setPathFlag(configVolumesAddCmd)
setPathFlag(configVolumesRemoveCmd)
cmd.AddCommand(configVolumesAddCmd)
cmd.AddCommand(configVolumesRemoveCmd)
return cmd
}
var configVolumesAddCmd = &cobra.Command{
Use: "add",
Short: "Add volume to the function configuration",
Long: `Add volume to the function configuration
func NewConfigVolumesAddCmd() *cobra.Command {
return &cobra.Command{
Use: "add",
Short: "Add volume to the function configuration",
Long: `Add volume to the function configuration
Interactive prompt to add Secrets and ConfigMaps as Volume mounts to the function project
in the current directory or from the directory specified with --path.
`,
SuggestFor: []string{"ad", "create", "insert", "append"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
SuggestFor: []string{"ad", "create", "insert", "append"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
return runAddVolumesPrompt(cmd.Context(), function)
},
}
return runAddVolumesPrompt(cmd.Context(), function)
},
}
var configVolumesRemoveCmd = &cobra.Command{
Use: "remove",
Short: "Remove volume from the function configuration",
Long: `Remove volume from the function configuration
func NewConfigVolumesRemoveCmd() *cobra.Command {
return &cobra.Command{
Use: "remove",
Short: "Remove volume from the function configuration",
Long: `Remove volume from the function configuration
Interactive prompt to remove Volume mounts from the function project
in the current directory or from the directory specified with --path.
`,
SuggestFor: []string{"del", "delete", "rmeove"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
SuggestFor: []string{"del", "delete", "rmeove"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
function, err := initConfigCommand(args, defaultLoaderSaver)
if err != nil {
return
}
return runRemoveVolumesPrompt(function)
},
}
return runRemoveVolumesPrompt(function)
},
}
func listVolumes(f fn.Function) {

View File

@ -25,12 +25,6 @@ type ErrInvalidRuntime error
// ErrInvalidTemplate indicates that the passed template was invalid.
type ErrInvalidTemplate error
func init() {
// Add to the root a new "Create" command which obtains an appropriate
// instance of fn.Client from the given client creator function.
root.AddCommand(NewCreateCmd(newCreateClient))
}
// createClientFn is a factory function which returns a Client suitable for
// use with the Create command.
type createClientFn func(createConfig) *fn.Client
@ -53,16 +47,16 @@ func NewCreateCmd(clientFn createClientFn) *cobra.Command {
Short: "Create a Function Project",
Long: `
NAME
{{.Prefix}}func create - Create a Function project.
{{.Name}} create - Create a Function project.
SYNOPSIS
{{.Prefix}}func create [-l|--language] [-t|--template] [-r|--repository]
{{.Name}} create [-l|--language] [-t|--template] [-r|--repository]
[-c|--confirm] [-v|--verbose] [path]
DESCRIPTION
Creates a new Function project.
$ {{.Prefix}}func create -l node -t http
$ {{.Name}} create -l node -t http
Creates a Function in the current directory '.' which is written in the
language/runtime 'node' and handles HTTP events.
@ -71,24 +65,24 @@ DESCRIPTION
the path if necessary.
To complete this command interactivly, use --confirm (-c):
$ {{.Prefix}}func create -c
$ {{.Name}} create -c
Available Language Runtimes and Templates:
{{ .Options | indent 2 " " | indent 1 "\t" }}
To install more language runtimes and their templates see '{{.Prefix}}func repository'.
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
template).
$ {{.Prefix}}func create
$ {{.Name}} create
o Create a Node.js Function in the directory 'myfunc'.
$ {{.Prefix}}func create myfunc
$ {{.Name}} create myfunc
o Create a Go Function which handles CloudEvents in ./myfunc.
$ {{.Prefix}}func create -l go -t cloudevents myfunc
$ {{.Name}} create -l go -t cloudevents myfunc
`,
SuggestFor: []string{"vreate", "creaet", "craete", "new"},
PreRunE: bindEnv("language", "template", "repository", "confirm"),
@ -182,10 +176,10 @@ func runCreateHelp(cmd *cobra.Command, args []string, clientFn createClientFn) {
var data = struct {
Options string
Prefix string
Name string
}{
Options: options,
Prefix: pluginPrefix(),
Name: cmd.Root().Name(),
}
if err := tpl.Execute(cmd.OutOrStdout(), data); err != nil {
fmt.Fprintf(cmd.ErrOrStderr(), "unable to display help text: %v", err)

View File

@ -14,12 +14,6 @@ import (
"knative.dev/kn-plugin-func/progress"
)
func init() {
// Create a new delete command with a reference to
// a function which yields an appropriate concrete client instance.
root.AddCommand(NewDeleteCmd(newDeleteClient))
}
// newDeleteClient returns an instance of a Client using the
// final config state.
// Testing note: This method is swapped out during testing to allow

View File

@ -24,10 +24,6 @@ import (
"knative.dev/kn-plugin-func/progress"
)
func init() {
root.AddCommand(NewDeployCmd(newDeployClient))
}
func newDeployClient(cfg deployConfig) (*fn.Client, error) {
listener := progress.New()
builder := buildpacks.NewBuilder()

View File

@ -2,6 +2,7 @@ package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
@ -28,6 +29,25 @@ func main() {
os.Exit(137)
}()
cmd.SetMeta(date, vers, hash)
cmd.Execute(ctx)
root, err := cmd.NewRootCmd(cmd.RootCommandConfig{
Name: "func",
Date: date,
Version: vers,
Hash: hash,
})
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if err := root.ExecuteContext(ctx); err != nil {
if ctx.Err() != nil {
os.Exit(130)
return
}
// Errors are printed to STDERR output and the process exits with code of 1.
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

View File

@ -15,10 +15,6 @@ import (
"knative.dev/kn-plugin-func/knative"
)
func init() {
root.AddCommand(NewInfoCmd(newInfoClient))
}
func newInfoClient(cfg infoConfig) (*fn.Client, error) {
describer, err := knative.NewDescriber(cfg.Namespace)
if err != nil {

View File

@ -18,10 +18,6 @@ import (
knative "knative.dev/kn-plugin-func/knative"
)
func init() {
root.AddCommand(NewInvokeCmd(newInvokeClient))
}
type invokeClientFn func(invokeConfig) (*fn.Client, error)
func newInvokeClient(cfg invokeConfig) (*fn.Client, error) {
@ -44,10 +40,10 @@ func NewInvokeCmd(clientFn invokeClientFn) *cobra.Command {
Short: "Invoke a Function",
Long: `
NAME
{{.Prefix}}func invoke - Invoke a Function.
{{.Name}} invoke - Invoke a Function.
SYNOPSIS
{{.Prefix}}func invoke [-t|--target] [-f|--format]
{{.Name}} invoke [-t|--target] [-f|--format]
[--id] [--source] [--type] [--data] [--file] [--content-type]
[-s|--save] [-p|--path] [-c|--confirm] [-v|--verbose]
@ -71,54 +67,54 @@ DESCRIPTION
Invocation Target
The Function instance to invoke can be specified using the --target flag
which accepts the values "local", "remote", or <URL>. By default the
local Function instance is chosen if running (see {{.Prefix}}func run).
local Function instance is chosen if running (see {{.Name}} run).
To explicitly target the remote (deployed) Function:
{{.Prefix}}func invoke --target=remote
{{.Name}} invoke --target=remote
To target an arbitrary endpoint, provide a URL:
{{.Prefix}}func invoke --target=https://myfunction.example.com
{{.Name}} invoke --target=https://myfunction.example.com
Invocation Data
Providing a filename in the --file flag will base64 encode its contents
as the "data" parameter sent to the Function. The value of --content-type
should be set to the type from the source file. For example, the following
would send a JPEG base64 encoded in the "data" POST parameter:
{{.Prefix}}func invoke --file=example.jpeg --content-type=image/jpeg
{{.Name}} invoke --file=example.jpeg --content-type=image/jpeg
Message Format
By default Functions are sent messages which match the invocation format
of the template they were created using; for example "http" or "cloudevent".
To override this behavior, use the --format (-f) flag.
{{.Prefix}}func invoke -f=cloudevent -t=http://my-sink.my-cluster
{{.Name}} invoke -f=cloudevent -t=http://my-sink.my-cluster
EXAMPLES
o Invoke the default (local or remote) running Function with default values
$ {{.Prefix}}func invoke
$ {{.Name}} invoke
o Run the Function locally and then invoke it with a test request:
(run in two terminals or by running the first in the background)
$ {{.Prefix}}func run
$ {{.Prefix}}func invoke
$ {{.Name}} run
$ {{.Name}} invoke
o Deploy and then invoke the remote Function:
$ {{.Prefix}}func deploy
$ {{.Prefix}}func invoke
$ {{.Name}} deploy
$ {{.Name}} invoke
o Invoke a remote (deployed) Function when it is already running locally:
(overrides the default behavior of preferring locally running instances)
$ {{.Prefix}}func invoke --target=remote
$ {{.Name}} invoke --target=remote
o Specify the data to send to the Function as a flag
$ {{.Prefix}}func invoke --data="Hello World!"
$ {{.Name}} invoke --data="Hello World!"
o Send a JPEG to the Function
$ {{.Prefix}}func invoke --file=example.jpeg --content-type=image/jpeg
$ {{.Name}} invoke --file=example.jpeg --content-type=image/jpeg
o Invoke an arbitrary endpoint (HTTP POST)
$ {{.Prefix}}func invoke --target="https://my-http-handler.example.com"
$ {{.Name}} invoke --target="https://my-http-handler.example.com"
o Invoke an arbitrary endpoint (CloudEvent)
$ {{.Prefix}}func invoke -f=cloudevent -t="https://my-event-broker.example.com"
$ {{.Name}} invoke -f=cloudevent -t="https://my-event-broker.example.com"
`,
SuggestFor: []string{"emit", "emti", "send", "emit", "exec", "nivoke", "onvoke", "unvoke", "knvoke", "imvoke", "ihvoke", "ibvoke"},
@ -203,9 +199,9 @@ func runInvokeHelp(cmd *cobra.Command, args []string, clientFn invokeClientFn) {
)
var data = struct {
Prefix string
Name string
}{
Prefix: pluginPrefix(),
Name: cmd.Root().Name(),
}
if err := tpl.Execute(cmd.OutOrStdout(), data); err != nil {

View File

@ -17,10 +17,6 @@ import (
"knative.dev/kn-plugin-func/knative"
)
func init() {
root.AddCommand(NewListCmd(newListClient))
}
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?

View File

@ -12,15 +12,6 @@ import (
fn "knative.dev/kn-plugin-func"
)
func init() {
repositoryCmd := NewRepositoryCmd(newRepositoryClient)
repositoryCmd.AddCommand(NewRepositoryListCmd(newRepositoryClient))
repositoryCmd.AddCommand(NewRepositoryAddCmd(newRepositoryClient))
repositoryCmd.AddCommand(NewRepositoryRenameCmd(newRepositoryClient))
repositoryCmd.AddCommand(NewRepositoryRemoveCmd(newRepositoryClient))
root.AddCommand(repositoryCmd)
}
// repositoryClientFn is a function which yields both a client and the final
// config used to instantiate.
type repositoryClientFn func([]string) (repositoryConfig, RepositoryClient, error)
@ -174,6 +165,12 @@ EXAMPLES
cmd.RunE = func(cmd *cobra.Command, args []string) error {
return runRepository(cmd, args, clientFn)
}
cmd.AddCommand(NewRepositoryListCmd(newRepositoryClient))
cmd.AddCommand(NewRepositoryAddCmd(newRepositoryClient))
cmd.AddCommand(NewRepositoryRenameCmd(newRepositoryClient))
cmd.AddCommand(NewRepositoryRemoveCmd(newRepositoryClient))
return cmd
}

View File

@ -2,7 +2,6 @@ package cmd
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
@ -29,69 +28,34 @@ var exampleTemplate = template.Must(template.New("example").Parse(`
curl $(kn service describe myfunc -o url)
`))
// The root of the command tree defines the command name, description, globally
type RootCommandConfig struct {
Name string // usually `func` or `kn func`
Date string
Version string
Hash string
}
// NewRootCmd creates the root of the command tree defines the command name, description, globally
// available flags, etc. It has no action of its own, such that running the
// resultant binary with no arguments prints the help/usage text.
var root = &cobra.Command{
Use: "func",
Short: "Serverless Functions",
SilenceErrors: true, // we explicitly handle errors in Execute()
SilenceUsage: true, // no usage dump on error
Long: `Serverless Functions
func NewRootCmd(config RootCommandConfig) (*cobra.Command, error) {
var err error
root := &cobra.Command{
Use: config.Name,
Short: "Serverless Functions",
SilenceErrors: true, // we explicitly handle errors in Execute()
SilenceUsage: true, // no usage dump on error
Long: `Serverless Functions
Create, build and deploy Functions in serverless containers for multiple runtimes on Knative`,
}
}
func init() {
var err error
root.Example, err = replaceNameInTemplate("func", "example")
root.Example, err = replaceNameInTemplate(config.Name, "example")
if err != nil {
root.Example = "Usage could not be loaded"
}
}
func replaceNameInTemplate(name, template string) (string, error) {
var buffer bytes.Buffer
err := exampleTemplate.ExecuteTemplate(&buffer, template, name)
if err != nil {
return "", err
}
return buffer.String(), nil
}
// pluginPrefix returns an optional prefix for help commands based on the
// value of the FUNC_PARENT_COMMAND environment variable.
func pluginPrefix() string {
parent := os.Getenv("FUNC_PARENT_COMMAND")
if parent != "" {
return parent + " "
}
return ""
}
// NewRootCmd can be used to embed the func commands as a library, such as
// a plugin in 'kn'.
func NewRootCmd() (*cobra.Command, error) {
// TODO: have 'kn' provide this environment variable, such that this works
// generically, and works whether it is compiled in as a library or used via
// the `kn-func` binary naming convention.
os.Setenv("FUNC_PARENT_COMMAND", "kn")
// TODO: update the below to use the environment variable, and the general
// structure seen in the create command's help text.
root.Use = "kn func"
var err error
root.Example, err = replaceNameInTemplate("kn func", "example")
if err != nil {
root.Example = "Usage could not be loaded"
}
return root, err
}
// When the code is loaded into memory upon invocation, the cobra/viper packages
// are invoked to gather system context. This includes reading the configuration
// file, environment variables, and parsing the command flags.
func init() {
// read in environment variables that match
viper.AutomaticEnv()
@ -101,9 +65,9 @@ func init() {
// which thus overrides both the default and the value read in from the
// config file (i.e. flags always take highest precidence).
root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", verbose, "print verbose logs")
err := viper.BindPFlag("verbose", root.PersistentFlags().Lookup("verbose"))
err = viper.BindPFlag("verbose", root.PersistentFlags().Lookup("verbose"))
if err != nil {
panic(err)
return nil, err
}
// Override the --version template to match the output format from the
@ -112,24 +76,38 @@ func init() {
// Prefix all environment variables with "FUNC_" to avoid collisions with other apps.
viper.SetEnvPrefix("func")
version := Version{
Date: config.Date,
Vers: config.Version,
Hash: config.Hash,
}
root.Version = version.String()
root.AddCommand(NewVersionCmd(version))
root.AddCommand(NewCreateCmd(newCreateClient))
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(NewRepositoryCmd(newRepositoryClient))
root.AddCommand(NewRunCmd(newRunClient))
root.AddCommand(NewCompletionCmd())
return root, nil
}
// Execute the command tree by executing the root command, which runs
// according to the context defined by: the optional config file,
// Environment Variables, command arguments and flags.
func Execute(ctx context.Context) {
// Sets version to a string partially populated by compile-time flags.
root.Version = version.String()
// Execute the root of the command tree.
if err := root.ExecuteContext(ctx); err != nil {
if ctx.Err() != nil {
os.Exit(130)
return
}
// Errors are printed to STDERR output and the process exits with code of 1.
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
func replaceNameInTemplate(name, template string) (string, error) {
var buffer bytes.Buffer
err := exampleTemplate.ExecuteTemplate(&buffer, template, name)
if err != nil {
return "", err
}
return buffer.String(), nil
}
// Helpers
@ -338,3 +316,45 @@ func setPathFlag(cmd *cobra.Command) {
func setNamespaceFlag(cmd *cobra.Command) {
cmd.Flags().StringP("namespace", "n", "", "The namespace on the cluster. By default, the namespace in func.yaml is used or the currently active namespace if not set in the configuration. (Env: $FUNC_NAMESPACE)")
}
type Version struct {
// Date of compilation
Date string
// Version tag of the git commit, or 'tip' if no tag.
Vers string
// Hash of the currently active git commit on build.
Hash string
// Verbose printing enabled for the string representation.
Verbose bool
}
func (v Version) String() string {
// If 'vers' is not a semver already, then the binary was built either
// from an untagged git commit (set semver to v0.0.0), or was built
// directly from source (set semver to v0.0.0-source).
if strings.HasPrefix(v.Vers, "v") {
// Was built via make with a tagged commit
if v.Verbose {
return fmt.Sprintf("%s-%s-%s", v.Vers, v.Hash, v.Date)
} else {
return v.Vers
}
} else if v.Vers == "tip" {
// Was built via make from an untagged commit
v.Vers = "v0.0.0"
if v.Verbose {
return fmt.Sprintf("%s-%s-%s", v.Vers, v.Hash, v.Date)
} else {
return v.Vers
}
} else {
// Was likely built from source
v.Vers = "v0.0.0"
v.Hash = "source"
if v.Verbose {
return fmt.Sprintf("%s-%s", v.Vers, v.Hash)
} else {
return v.Vers
}
}
}

View File

@ -120,6 +120,11 @@ func TestRoot_mergeEnvMaps(t *testing.T) {
func TestRoot_CMDParameterized(t *testing.T) {
rootConfig := RootCommandConfig{
Name: "func",
}
root, _ := NewRootCmd(rootConfig)
if root.Use != "func" {
t.Fatalf("default command use should be \"func\".")
}
@ -129,13 +134,17 @@ func TestRoot_CMDParameterized(t *testing.T) {
t.Fatalf("default command example should assume \"func\" as executable name. error: %v", err)
}
cmd, err := NewRootCmd()
rootConfig = RootCommandConfig{
Name: "kn func",
}
cmd, err := NewRootCmd(rootConfig)
if cmd.Use != "kn func" && err != nil {
t.Fatalf("plugin command use should be \"kn func\".")
}
usageExample, _ = replaceNameInTemplate("kn func", "example")
cmd, err = NewRootCmd()
cmd, err = NewRootCmd(rootConfig)
if cmd.Example != usageExample || err != nil {
t.Fatalf("plugin command example should assume \"kn func\" as executable name. error: %v", err)
}

View File

@ -15,10 +15,6 @@ import (
"knative.dev/kn-plugin-func/progress"
)
func init() {
root.AddCommand(NewRunCmd(newRunClient))
}
func newRunClient(cfg runConfig) *fn.Client {
bc := newBuildConfig()
runner := docker.NewRunner()

View File

@ -2,88 +2,25 @@ package cmd
import (
"fmt"
"strings"
"github.com/ory/viper"
"github.com/spf13/cobra"
)
// metadata about the build process/binary etc.
// Not populated if building from source with go build.
// Set by the `make` targets.
var version = Version{}
func NewVersionCmd(version Version) *cobra.Command {
runVersion := func(cmd *cobra.Command, args []string) {
fmt.Println(version)
}
// SetMeta is called by `main` with any provided build metadata
func SetMeta(date, vers, hash string) {
version.Date = date // build timestamp
version.Vers = vers // version tag
version.Hash = hash // git commit hash
}
func init() {
root.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Show the version",
Long: `Show the version
cmd := &cobra.Command{
Use: "version",
Short: "Show the version",
Long: `Show the version
Use the --verbose option to include the build date stamp and commit hash"
`,
SuggestFor: []string{"vers", "verison"},
Run: runVersion,
}
func runVersion(cmd *cobra.Command, args []string) {
// update version with the value of the (global) flag 'verbose'
version.Verbose = viper.GetBool("verbose")
// version is the metadata, serialized.
fmt.Println(version)
}
// versionMetadata is set by the main package.
// When compiled from source, they remain the zero value.
// When compiled via `make`, they are initialized to the noted values.
type Version struct {
// Date of compilation
Date string
// Version tag of the git commit, or 'tip' if no tag.
Vers string
// Hash of the currently active git commit on build.
Hash string
// Verbose printing enabled for the string representation.
Verbose bool
}
func (v Version) String() string {
// If 'vers' is not a semver already, then the binary was built either
// from an untagged git commit (set semver to v0.0.0), or was built
// directly from source (set semver to v0.0.0-source).
if strings.HasPrefix(v.Vers, "v") {
// Was built via make with a tagged commit
if v.Verbose {
return fmt.Sprintf("%s-%s-%s", v.Vers, v.Hash, v.Date)
} else {
return v.Vers
}
} else if v.Vers == "tip" {
// Was built via make from an untagged commit
v.Vers = "v0.0.0"
if v.Verbose {
return fmt.Sprintf("%s-%s-%s", v.Vers, v.Hash, v.Date)
} else {
return v.Vers
}
} else {
// Was likely built from source
v.Vers = "v0.0.0"
v.Hash = "source"
if v.Verbose {
return fmt.Sprintf("%s-%s", v.Vers, v.Hash)
} else {
return v.Vers
}
SuggestFor: []string{"vers", "verison"}, //nolint:misspell
Run: runVersion,
}
return cmd
}

View File

@ -35,13 +35,19 @@ func (f *funcPlugin) Execute(args []string) error {
cancel()
}()
rootCmd, _ := cmd.NewRootCmd()
rootConfig := cmd.RootCommandConfig{
Name: "kn func",
}
info, _ := debug.ReadBuildInfo()
for _, dep := range info.Deps {
if strings.Contains(dep.Path, "knative.dev/kn-plugin-func") {
cmd.SetMeta("", dep.Version, dep.Sum)
rootConfig.Version, rootConfig.Hash = dep.Version, dep.Sum
}
}
rootCmd, _ := cmd.NewRootCmd(rootConfig)
oldArgs := os.Args
defer (func() {
os.Args = oldArgs