mirror of https://github.com/knative/func.git
feat: consolidate formatters
- Replaces globally-scoped formatter function with methods - Defines enumerated Format types - Renames the 'output' flag 'format' due to confusion with command file descriptors - FunctionDescription now Function - Global verbose flag replaced with config struct based value throughout
This commit is contained in:
parent
89c09d9a78
commit
3fc39aa773
10
client.go
10
client.go
|
@ -94,10 +94,10 @@ type ProgressListener interface {
|
|||
// Describer of Functions' remote deployed aspect.
|
||||
type Describer interface {
|
||||
// Describe the running state of the service as reported by the underlyng platform.
|
||||
Describe(name string) (description FunctionDescription, err error)
|
||||
Describe(name string) (description Description, err error)
|
||||
}
|
||||
|
||||
type FunctionDescription struct {
|
||||
type Description struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Routes []string `json:"routes" yaml:"routes"`
|
||||
Subscriptions []Subscription `json:"subscriptions" yaml:"subscriptions"`
|
||||
|
@ -493,7 +493,7 @@ func (c *Client) List() ([]string, error) {
|
|||
|
||||
// Describe a Function. Name takes precidence. If no name is provided,
|
||||
// the Function defined at root is used.
|
||||
func (c *Client) Describe(name, root string) (fd FunctionDescription, err error) {
|
||||
func (c *Client) Describe(name, root string) (d Description, err error) {
|
||||
// If name is provided, it takes precidence.
|
||||
// Otherwise load the Function defined at root.
|
||||
if name != "" {
|
||||
|
@ -502,10 +502,10 @@ func (c *Client) Describe(name, root string) (fd FunctionDescription, err error)
|
|||
|
||||
f, err := NewFunction(root)
|
||||
if err != nil {
|
||||
return fd, err
|
||||
return d, err
|
||||
}
|
||||
if !f.Initialized() {
|
||||
return fd, fmt.Errorf("%v is not initialized", f.Name)
|
||||
return d, fmt.Errorf("%v is not initialized", f.Name)
|
||||
}
|
||||
return c.describer.Describe(f.Name)
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func runDelete(cmd *cobra.Command, args []string) (err error) {
|
|||
function := faas.Function{Root: config.Path, Name: config.Name}
|
||||
|
||||
client := faas.New(
|
||||
faas.WithVerbose(verbose),
|
||||
faas.WithVerbose(config.Verbose),
|
||||
faas.WithRemover(remover))
|
||||
|
||||
return client.Remove(function)
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/ory/viper"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -16,10 +18,10 @@ import (
|
|||
func init() {
|
||||
root.AddCommand(describeCmd)
|
||||
describeCmd.Flags().StringP("namespace", "n", "", "Override namespace in which to search for the Function. Default is to use currently active underlying platform setting - $FAAS_NAMESPACE")
|
||||
describeCmd.Flags().StringP("output", "o", "yaml", "optionally specify output format (yaml,xml,json). - $FAAS_OUTPUT")
|
||||
describeCmd.Flags().StringP("format", "f", "human", "optionally specify output format (human|plain|json|xml|yaml) $FAAS_FORMAT")
|
||||
describeCmd.Flags().StringP("path", "p", cwd(), "Path to the project which should be described - $FAAS_PATH")
|
||||
|
||||
err := describeCmd.RegisterFlagCompletionFunc("output", CompleteOutputFormatList)
|
||||
err := describeCmd.RegisterFlagCompletionFunc("format", CompleteOutputFormatList)
|
||||
if err != nil {
|
||||
fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err)
|
||||
}
|
||||
|
@ -28,10 +30,10 @@ func init() {
|
|||
var describeCmd = &cobra.Command{
|
||||
Use: "describe <name> [options]",
|
||||
Short: "Describe Function",
|
||||
Long: `Describes the Function by name, by explicit path, or by default the current directory.`,
|
||||
Long: `Describes the Function initialized in the current directory, or by passed name argument.`,
|
||||
SuggestFor: []string{"desc", "get"},
|
||||
ValidArgsFunction: CompleteFunctionList,
|
||||
PreRunE: bindEnv("namespace", "output", "path"),
|
||||
PreRunE: bindEnv("namespace", "format", "path"),
|
||||
RunE: runDescribe,
|
||||
}
|
||||
|
||||
|
@ -45,51 +47,25 @@ func runDescribe(cmd *cobra.Command, args []string) (err error) {
|
|||
describer.Verbose = config.Verbose
|
||||
|
||||
client := faas.New(
|
||||
faas.WithVerbose(verbose),
|
||||
faas.WithVerbose(config.Verbose),
|
||||
faas.WithDescriber(describer))
|
||||
|
||||
description, err := client.Describe(config.Name, config.Path)
|
||||
d, err := client.Describe(config.Name, config.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
formatted, err := formatDescription(description, config.Output)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(formatted)
|
||||
write(os.Stdout, description(d), config.Format)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Placeholder. Create a fit-for-purpose Description plaintext formatter.
|
||||
func fmtDescriptionPlain(i interface{}) ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%v", i)), nil
|
||||
}
|
||||
|
||||
// format the description as json|yaml|xml
|
||||
func formatDescription(desc faas.FunctionDescription, format string) (string, error) {
|
||||
formatters := map[string]func(interface{}) ([]byte, error){
|
||||
"plain": fmtDescriptionPlain,
|
||||
"json": json.Marshal,
|
||||
"yaml": yaml.Marshal,
|
||||
"xml": xml.Marshal,
|
||||
}
|
||||
formatFn, ok := formatters[format]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unknown format '%s'", format)
|
||||
}
|
||||
bytes, err := formatFn(desc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
// CLI Configuration (parameters)
|
||||
// ------------------------------
|
||||
|
||||
type describeConfig struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Output string
|
||||
Format string
|
||||
Path string
|
||||
Verbose bool
|
||||
}
|
||||
|
@ -102,8 +78,49 @@ func newDescribeConfig(args []string) describeConfig {
|
|||
return describeConfig{
|
||||
Name: deriveName(name, viper.GetString("path")),
|
||||
Namespace: viper.GetString("namespace"),
|
||||
Output: viper.GetString("output"),
|
||||
Format: viper.GetString("format"),
|
||||
Path: viper.GetString("path"),
|
||||
Verbose: viper.GetBool("verbose"),
|
||||
}
|
||||
}
|
||||
|
||||
// Output Formatting (serializers)
|
||||
// -------------------------------
|
||||
|
||||
type description faas.Description
|
||||
|
||||
func (d description) Human(w io.Writer) error {
|
||||
fmt.Fprintln(w, d.Name)
|
||||
fmt.Fprintln(w, "Routes:")
|
||||
for _, route := range d.Routes {
|
||||
fmt.Fprintf(w, " %v\n", route)
|
||||
}
|
||||
fmt.Fprintln(w, "Subscriptions (Source, Type, Broker):")
|
||||
for _, s := range d.Subscriptions {
|
||||
fmt.Fprintf(w, " %v %v %v\n", s.Source, s.Type, s.Broker)
|
||||
}
|
||||
return d.Plain(w)
|
||||
}
|
||||
|
||||
func (d description) Plain(w io.Writer) error {
|
||||
fmt.Fprintf(w, "NAME %v\n", d.Name)
|
||||
for _, route := range d.Routes {
|
||||
fmt.Fprintf(w, "ROUTE %v\n", route)
|
||||
}
|
||||
for _, s := range d.Subscriptions {
|
||||
fmt.Fprintf(w, "SUBSCRIPTION %v %v %v\n", s.Source, s.Type, s.Broker)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d description) JSON(w io.Writer) error {
|
||||
return json.NewEncoder(w).Encode(d)
|
||||
}
|
||||
|
||||
func (d description) XML(w io.Writer) error {
|
||||
return xml.NewEncoder(w).Encode(d)
|
||||
}
|
||||
|
||||
func (d description) YAML(w io.Writer) error {
|
||||
return yaml.NewEncoder(w).Encode(d)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Format string
|
||||
|
||||
const (
|
||||
Human Format = "human" // Headers, indentation, justification etc.
|
||||
Plain = "plain" // Suitable for cli automation via sed/awk etc.
|
||||
JSON = "json" // Technically a ⊆ yaml, but no one likes yaml.
|
||||
XML = "xml"
|
||||
YAML = "yaml"
|
||||
)
|
||||
|
||||
// formatter is any structure which has methods for serialization.
|
||||
type Formatter interface {
|
||||
Human(io.Writer) error
|
||||
Plain(io.Writer) error
|
||||
JSON(io.Writer) error
|
||||
XML(io.Writer) error
|
||||
YAML(io.Writer) error
|
||||
}
|
||||
|
||||
// write to the output the output of the formatter's appropriate serilization function.
|
||||
// the command to exit with value 2.
|
||||
func write(out io.Writer, s Formatter, formatName string) {
|
||||
var err error
|
||||
switch Format(formatName) {
|
||||
case Human:
|
||||
err = s.Human(out)
|
||||
case Plain:
|
||||
err = s.Plain(out)
|
||||
case JSON:
|
||||
err = s.JSON(out)
|
||||
case XML:
|
||||
err = s.XML(out)
|
||||
case YAML:
|
||||
err = s.YAML(out)
|
||||
default:
|
||||
err = fmt.Errorf("format not recognized: %v\n", formatName)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
89
cmd/list.go
89
cmd/list.go
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/ory/viper"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -16,9 +18,9 @@ import (
|
|||
func init() {
|
||||
root.AddCommand(listCmd)
|
||||
listCmd.Flags().StringP("namespace", "n", "", "Override namespace in which to search for Functions. Default is to use currently active underlying platform setting - $FAAS_NAMESPACE")
|
||||
listCmd.Flags().StringP("output", "o", "plain", "optionally specify output format (plain,json,yaml)")
|
||||
listCmd.Flags().StringP("format", "f", "human", "optionally specify output format (human|plain|json|xml|yaml) $FAAS_FORMAT")
|
||||
|
||||
err := listCmd.RegisterFlagCompletionFunc("output", CompleteOutputFormatList)
|
||||
err := listCmd.RegisterFlagCompletionFunc("format", CompleteOutputFormatList)
|
||||
if err != nil {
|
||||
fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err)
|
||||
}
|
||||
|
@ -29,7 +31,7 @@ var listCmd = &cobra.Command{
|
|||
Short: "Lists deployed Functions",
|
||||
Long: `Lists deployed Functions`,
|
||||
SuggestFor: []string{"ls", "lsit"},
|
||||
PreRunE: bindEnv("namespace", "output"),
|
||||
PreRunE: bindEnv("namespace", "format"),
|
||||
RunE: runList,
|
||||
}
|
||||
|
||||
|
@ -43,91 +45,62 @@ func runList(cmd *cobra.Command, args []string) (err error) {
|
|||
lister.Verbose = config.Verbose
|
||||
|
||||
client := faas.New(
|
||||
faas.WithVerbose(verbose),
|
||||
faas.WithVerbose(config.Verbose),
|
||||
faas.WithLister(lister))
|
||||
|
||||
names, err := client.List()
|
||||
nn, err := client.List()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
formatted, err := formatNames(names, config.Output)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(formatted)
|
||||
write(os.Stdout, names(nn), config.Format)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: placeholder. Create a fit-for-purpose Names plaintext formatter
|
||||
func fmtNamesPlain(i interface{}) ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%v", i)), nil
|
||||
}
|
||||
|
||||
func formatNames(names []string, format string) (string, error) {
|
||||
formatters := map[string]func(interface{}) ([]byte, error){
|
||||
"plain": fmtNamesPlain,
|
||||
"json": json.Marshal,
|
||||
"yaml": yaml.Marshal,
|
||||
"xml": xml.Marshal,
|
||||
}
|
||||
formatFn, ok := formatters[format]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Unknown format '%v'", format)
|
||||
}
|
||||
bytes, err := formatFn(names)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
// CLI Configuration (parameters)
|
||||
// ------------------------------
|
||||
|
||||
type listConfig struct {
|
||||
Namespace string
|
||||
Output string
|
||||
Format string
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func newListConfig() listConfig {
|
||||
return listConfig{
|
||||
Namespace: viper.GetString("namespace"),
|
||||
Output: viper.GetString("output"),
|
||||
Format: viper.GetString("format"),
|
||||
Verbose: viper.GetBool("verbose"),
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED BELOW (?):
|
||||
// TODO: regenerate completions, which may necessitate the below change:
|
||||
/*
|
||||
// Output Formatting (serializers)
|
||||
// -------------------------------
|
||||
|
||||
var validFormats []string
|
||||
type names []string
|
||||
|
||||
func completeFormats(cmd *cobra.Command, args []string, toComplete string) (formats []string, directive cobra.ShellCompDirective) {
|
||||
formats = validFormats
|
||||
directive = cobra.ShellCompDirectiveDefault
|
||||
return
|
||||
func (nn names) Human(w io.Writer) error {
|
||||
return nn.Plain(w)
|
||||
}
|
||||
|
||||
type fmtFn func(writer io.Writer, names []string) error
|
||||
|
||||
func fmtPlain(writer io.Writer, names []string) error {
|
||||
for _, name := range names {
|
||||
_, err := fmt.Fprintf(writer, "%s\n", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (nn names) Plain(w io.Writer) error {
|
||||
for _, name := range nn {
|
||||
fmt.Fprintln(w, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fmtJSON(writer io.Writer, names []string) error {
|
||||
encoder := json.NewEncoder(writer)
|
||||
return encoder.Encode(names)
|
||||
func (nn names) JSON(w io.Writer) error {
|
||||
return json.NewEncoder(w).Encode(nn)
|
||||
}
|
||||
|
||||
func fmtYAML(writer io.Writer, names []string) error {
|
||||
encoder := yaml.NewEncoder(writer)
|
||||
return encoder.Encode(names)
|
||||
func (nn names) XML(w io.Writer) error {
|
||||
return xml.NewEncoder(w).Encode(nn)
|
||||
}
|
||||
|
||||
func (nn names) YAML(w io.Writer) error {
|
||||
// the yaml.v2 package refuses to directly serialize a []string unless
|
||||
// exposed as a public struct member; so an inline anonymous is used.
|
||||
ff := struct{ Names []string }{nn}
|
||||
return yaml.NewEncoder(w).Encode(ff.Names)
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -11,10 +11,7 @@ import (
|
|||
"github.com/boson-project/faas"
|
||||
)
|
||||
|
||||
var (
|
||||
config = "~/.faas/config" // Location of the optional config file.
|
||||
verbose = false // Enable verbose logging (debug).
|
||||
)
|
||||
var config = "~/.faas/config" // Location of the optional system-wide config file.
|
||||
|
||||
// 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
|
||||
|
@ -39,6 +36,8 @@ 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).
|
||||
|
|
|
@ -30,7 +30,7 @@ func runUpdate(cmd *cobra.Command, args []string) (err error) {
|
|||
config := newUpdateConfig()
|
||||
|
||||
builder := buildpacks.NewBuilder()
|
||||
builder.Verbose = verbose
|
||||
builder.Verbose = config.Verbose
|
||||
|
||||
pusher := docker.NewPusher()
|
||||
pusher.Verbose = config.Verbose
|
||||
|
@ -39,10 +39,10 @@ func runUpdate(cmd *cobra.Command, args []string) (err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
updater.Verbose = verbose
|
||||
updater.Verbose = config.Verbose
|
||||
|
||||
client := faas.New(
|
||||
faas.WithVerbose(verbose),
|
||||
faas.WithVerbose(config.Verbose),
|
||||
faas.WithBuilder(builder),
|
||||
faas.WithPusher(pusher),
|
||||
faas.WithUpdater(updater))
|
||||
|
|
|
@ -59,7 +59,7 @@ func (v Version) String() string {
|
|||
// 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 verbose {
|
||||
if v.Verbose {
|
||||
return fmt.Sprintf("%s-%s-%s", v.Vers, v.Hash, v.Date)
|
||||
} else {
|
||||
return v.Vers
|
||||
|
@ -67,7 +67,7 @@ func (v Version) String() string {
|
|||
} else if v.Vers == "tip" {
|
||||
// Was built via make from an untagged commit
|
||||
v.Vers = "v0.0.0"
|
||||
if verbose {
|
||||
if v.Verbose {
|
||||
return fmt.Sprintf("%s-%s-%s", v.Vers, v.Hash, v.Date)
|
||||
} else {
|
||||
return v.Vers
|
||||
|
@ -76,7 +76,7 @@ func (v Version) String() string {
|
|||
// Was likely built from source
|
||||
v.Vers = "v0.0.0"
|
||||
v.Hash = "source"
|
||||
if verbose {
|
||||
if v.Verbose {
|
||||
return fmt.Sprintf("%s-%s", v.Vers, v.Hash)
|
||||
} else {
|
||||
return v.Vers
|
||||
|
|
|
@ -45,7 +45,7 @@ func NewDescriber(namespaceOverride string) (describer *Describer, err error) {
|
|||
// restricts to label-syntax, which is thus escaped. Therefore as a knative (kube) implementation
|
||||
// detal proper full names have to be escaped on the way in and unescaped on the way out. ex:
|
||||
// www.example-site.com -> www-example--site-com
|
||||
func (describer *Describer) Describe(name string) (description faas.FunctionDescription, err error) {
|
||||
func (describer *Describer) Describe(name string) (description faas.Description, err error) {
|
||||
|
||||
namespace := describer.namespace
|
||||
servingClient := describer.servingClient
|
||||
|
|
Loading…
Reference in New Issue