/
root.go
220 lines (197 loc) · 7.37 KB
/
root.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package cmd
import (
"fmt"
"os"
"path/filepath"
"github.com/mitchellh/go-homedir"
"github.com/ory/viper"
"github.com/spf13/cobra"
"github.com/boson-project/faas"
)
// 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() {
// read in environment variables that match
viper.AutomaticEnv()
verbose := viper.GetBool("verbose")
// 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 != "" {
path = filepath.Join(path, "faas")
return
}
home, err := homedir.Expand("~")
if err != nil {
fmt.Fprintf(os.Stderr, "could not derive home directory for use as default templates path: %v", err)
path = filepath.Join(".config", "faas")
} else {
path = filepath.Join(home, ".config", "faas")
}
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 functionality 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()
}
// functionWithOverrides sets the namespace and image strings for the
// Function project at root, if provided, and returns the Function
// configuration values
func functionWithOverrides(root, namespace, image string) (f faas.Function, err error) {
if err = overrideNamespace(root, namespace); err != nil {
return
}
if err = overrideImage(root, image); err != nil {
return
}
return faas.NewFunction(root)
}
// 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 Function, 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.
}