mirror of https://github.com/knative/func.git
209 lines
7.1 KiB
Go
209 lines
7.1 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/mitchellh/go-homedir"
|
|
"github.com/ory/viper"
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/boson-project/faas"
|
|
)
|
|
|
|
var (
|
|
config = "~/.faas/config" // Location of the optional config file.
|
|
verbose = false // Enable verbose logging (debug).
|
|
)
|
|
|
|
// The root of the command tree defines the command name, descriotion, 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: "faas",
|
|
Short: "Function as a Service",
|
|
SilenceErrors: true, // we explicitly handle errors in Execute()
|
|
SilenceUsage: true, // no usage dump on error
|
|
Long: `Function as a Service
|
|
|
|
Create and run Functions as a Service.`,
|
|
}
|
|
|
|
// 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() {
|
|
// Populate `config` var with the value of --config flag, if provided.
|
|
root.PersistentFlags().StringVar(&config, "config", config, "config file path")
|
|
|
|
// read in environment variables that match
|
|
viper.AutomaticEnv()
|
|
|
|
// Populate the `verbose` flag with the value of --verbose, if provided,
|
|
// 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"))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Override the --version template to match the output format from the
|
|
// version subcommand: nothing but the version.
|
|
root.SetVersionTemplate(`{{printf "%s\n" .Version}}`)
|
|
|
|
// Prefix all environment variables with "FAAS_" to avoid collisions with other apps.
|
|
viper.SetEnvPrefix("faas")
|
|
}
|
|
|
|
// 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() {
|
|
// 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.Execute(); err != nil {
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// Helper functions used by multiple commands
|
|
// ------------------------------------------
|
|
|
|
// interactiveTerminal returns whether or not the currently attached process
|
|
// terminal is interactive. Used for determining whether or not to
|
|
// interactively prompt the user to confirm default choices, etc.
|
|
func interactiveTerminal() bool {
|
|
fi, err := os.Stdin.Stat()
|
|
return err == nil && ((fi.Mode() & os.ModeCharDevice) != 0)
|
|
}
|
|
|
|
// cwd returns the current working directory or exits 1 printing the error.
|
|
func cwd() (cwd string) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Unable to determine current working directory: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
return cwd
|
|
}
|
|
|
|
// configPath is the effective path to the optional config directory used for
|
|
// function defaults and extensible templates.
|
|
func configPath() (path string) {
|
|
if path = os.Getenv("XDG_CONFIG_HOME"); path != "" {
|
|
return
|
|
}
|
|
path, err := homedir.Expand("~/.config")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "could not derive home directory for use as default templates path: %v", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// bindFunc which conforms to the cobra PreRunE method signature
|
|
type bindFunc func(*cobra.Command, []string) error
|
|
|
|
// bindEnv returns a bindFunc that binds env vars to the namd flags.
|
|
func bindEnv(flags ...string) bindFunc {
|
|
return func(cmd *cobra.Command, args []string) (err error) {
|
|
for _, flag := range flags {
|
|
if err = viper.BindPFlag(flag, cmd.Flags().Lookup(flag)); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
// overrideImage overwrites (or sets) the value of the Function's .Image
|
|
// property, which preempts the default funcitonality of deriving the value as:
|
|
// Deafult: [config.Repository]/[config.Name]:latest
|
|
func overrideImage(root, override string) (err error) {
|
|
if override == "" {
|
|
return
|
|
}
|
|
f, err := faas.NewFunction(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.Image = override
|
|
return f.WriteConfig()
|
|
}
|
|
|
|
// overrideNamespace overwrites (or sets) the value of the Function's .Namespace
|
|
// property, which preempts the default functionality of using the underlying
|
|
// platform configuration (if supported). In the case of Kubernetes, this
|
|
// overrides the configured namespace (usually) set in ~/.kube.config.
|
|
func overrideNamespace(root, override string) (err error) {
|
|
if override == "" {
|
|
return
|
|
}
|
|
f, err := faas.NewFunction(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.Namespace = override
|
|
return f.WriteConfig()
|
|
}
|
|
|
|
// deriveName returns the explicit value (if provided) or attempts to derive
|
|
// from the given path. Path is defaulted to current working directory, where
|
|
// a function configuration, if it exists and contains a name, is used. Lastly
|
|
// derivation using the path us used.
|
|
func deriveName(explicitName string, path string) string {
|
|
// If the name was explicitly provided, use it.
|
|
if explicitName != "" {
|
|
return explicitName
|
|
}
|
|
// If the directory at path contains an initialized funciton, use the name therein
|
|
f, err := faas.NewFunction(path)
|
|
if err == nil && f.Name != "" {
|
|
return f.Name
|
|
}
|
|
maxRecursion := faas.DefaultMaxRecursion
|
|
derivedName, _ := faas.DerivedName(path, maxRecursion)
|
|
return derivedName
|
|
}
|
|
|
|
// deriveImage returns the same image name which will be used if no explicit
|
|
// image is provided. I.e. derived from the configured repository (registry
|
|
// plus username) and the Function's name.
|
|
//
|
|
// This is calculated preemptively here in the CLI (prior to invoking the
|
|
// client), only in order to provide information to the user via the prompt.
|
|
// The client will calculate this same value if the image override is not
|
|
// provided.
|
|
//
|
|
// Derivation logic:
|
|
// deriveImage attempts to arrive at a final, full image name:
|
|
// format: [registry]/[username]/[FunctionName]:[tag]
|
|
// example: quay.io/myname/my.function.name:tag.
|
|
//
|
|
// Registry can optionally be omitted, in which case DefaultRegistry
|
|
// will be prepended.
|
|
//
|
|
// If the image flag is provided, this value is used directly (the user supplied
|
|
// --image or $FAAS_IMAGE). Otherwise, the Function at 'path' is loaded, and
|
|
// the Image name therein is used (i.e. it was previously calculated).
|
|
// Finally, the default repository is used, which is prepended to the Function
|
|
// name, and appended with ':latest':
|
|
func deriveImage(explicitImage, defaultRepo, path string) string {
|
|
if explicitImage != "" {
|
|
return explicitImage // use the explicit value provided.
|
|
}
|
|
f, err := faas.NewFunction(path)
|
|
if err != nil {
|
|
return "" // unable to derive due to load error (uninitialized?)
|
|
}
|
|
if f.Image != "" {
|
|
return f.Image // use value previously provided or derived.
|
|
}
|
|
derivedValue, _ := faas.DerivedImage(path, defaultRepo)
|
|
return derivedValue // Use the faas system's derivation logic.
|
|
}
|