feat: 'describe' sub-command for faas cli

This commit is contained in:
Matej Vasek 2020-04-27 16:39:07 +02:00
parent 130751b01e
commit 380cc2f3ef
6 changed files with 210 additions and 10 deletions

View File

@ -32,6 +32,7 @@ type Client struct {
runner Runner // Runs the function locally
remover Remover // Removes remote services
lister Lister // Lists remote services
describer Describer
}
// ConfigFileName is an optional file checked for in the function root.
@ -105,6 +106,22 @@ type Lister interface {
List() ([]string, error)
}
type Subscription struct {
Source string `json:"source" yaml:"source"`
Type string `json:"type" yaml:"type"`
Broker string `json:"broker" yaml:"broker"`
}
type FunctionDescription struct {
Name string `json:"name" yaml:"name"`
Routes []string `json:"routes" yaml:"routes"`
Subscriptions []Subscription `json:"subscriptions" yaml:"subscriptions"`
}
type Describer interface {
Describe(name string) (description FunctionDescription, err error)
}
// Option defines a function which when passed to the Client constructor optionally
// mutates private members at time of instantiation.
type Option func(*Client)
@ -199,6 +216,12 @@ func WithLister(l Lister) Option {
}
}
func WithDescriber(describer Describer) Option {
return func(c *Client) {
c.describer = describer
}
}
// New client for a function service rooted at the given directory (default .) or
// that explicitly set via the option. Will fail if the directory already contains
// config files or other non-hidden files.
@ -370,6 +393,10 @@ func (c *Client) List() ([]string, error) {
return c.lister.List()
}
func (c *Client) Describe(name string) (FunctionDescription, error) {
return c.describer.Describe(name)
}
// Remove a function from remote, bringing the service funciton
// to the same state as if it had been created --local only.
// Name is the presently configured client's name, which was

76
cmd/describe.go Normal file
View File

@ -0,0 +1,76 @@
package cmd
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"gopkg.in/yaml.v2"
"github.com/boson-project/faas/client"
"github.com/boson-project/faas/knative"
"github.com/ory/viper"
"github.com/spf13/cobra"
)
func init() {
root.AddCommand(describeCmd)
describeCmd.Flags().StringP("output", "o", "yaml", "optionally specify output format (yaml,xml,json).")
viper.BindPFlag("output", describeCmd.Flags().Lookup("output"))
}
var describeCmd = &cobra.Command{
Use: "describe",
Short: "Describe Service Function",
Long: `Describe Service Function`,
SuggestFor: []string{"desc"},
Args: cobra.ExactArgs(1),
RunE: describe,
}
func describe(cmd *cobra.Command, args []string) (err error) {
var (
verbose = viper.GetBool("verbose")
format = viper.GetString("output")
)
name := args[0]
describer, err := knative.NewDescriber(client.FaasNamespace)
if err != nil {
return
}
describer.Verbose = verbose
client, err := client.New(".",
client.WithVerbose(verbose),
client.WithDescriber(describer),
)
if err != nil {
return
}
description, err := client.Describe(name)
if err != nil {
return
}
formatFunctions := map[string] func(interface{})([]byte, error) {
"json" : json.Marshal,
"yaml" : yaml.Marshal,
"xml" : xml.Marshal,
}
formatFun, found := formatFunctions[format]
if !found {
return errors.New("unknown output format")
}
data, err := formatFun(description)
if err != nil {
return
}
fmt.Println(string(data))
return
}

View File

@ -9,14 +9,8 @@ import (
"github.com/spf13/cobra"
)
const (
nsFlag = "namespace"
)
func init() {
root.AddCommand(listCmd)
listCmd.Flags().StringP(nsFlag, "n", "", "optionally specify a namespace")
viper.BindPFlag(nsFlag, listCmd.Flags().Lookup(nsFlag))
}
var listCmd = &cobra.Command{
@ -29,11 +23,10 @@ var listCmd = &cobra.Command{
func list(cmd *cobra.Command, args []string) (err error) {
var (
namespace = viper.GetString(nsFlag)
verbose = viper.GetBool("verbose")
)
lister, err := knative.NewLister(namespace)
lister, err := knative.NewLister(client.FaasNamespace)
if err != nil {
return
}

2
go.mod
View File

@ -9,12 +9,14 @@ require (
github.com/google/go-containerregistry v0.0.0-20200423114255-8f808463544c // indirect
github.com/openzipkin/zipkin-go v0.2.2 // indirect
github.com/ory/viper v1.7.4
github.com/robfig/cron v1.2.0 // indirect
github.com/spf13/cobra v1.0.0
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd
gomodules.xyz/jsonpatch/v2 v2.1.0 // indirect
gopkg.in/yaml.v2 v2.2.8
k8s.io/apimachinery v0.17.4
k8s.io/client-go v0.17.4
knative.dev/eventing v0.14.1
knative.dev/pkg v0.0.0-20200414233146-0eed424fa4ee // indirect
knative.dev/serving v0.14.0
)

6
go.sum
View File

@ -320,6 +320,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
@ -581,8 +583,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -612,6 +612,8 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf
k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
knative.dev/eventing v0.14.1 h1:YmnEl3IBVRkBcVYWPMWZegRGifeI7ibcA9xuhHWvAaw=
knative.dev/eventing v0.14.1/go.mod h1:UxweNv8yXhsdHJitcb9R6rmfNaUD2DFi9GWwNRyIs58=
knative.dev/pkg v0.0.0-20200414233146-0eed424fa4ee h1:G1QedLB/RxF4QTyL1Pq9M1QK1uj8khQgTypofUXrG20=
knative.dev/pkg v0.0.0-20200414233146-0eed424fa4ee/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
knative.dev/serving v0.14.0 h1:9iDyOqTciNuAh2D5KJP0soOq23FDR4HQHdIQNBQ/rAE=

100
knative/describer.go Normal file
View File

@ -0,0 +1,100 @@
package knative
import (
"fmt"
"github.com/boson-project/faas/client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"knative.dev/eventing/pkg/apis/eventing/v1alpha1"
eventingv1client "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1alpha1"
servingv1client "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1"
)
type Describer struct {
Verbose bool
namespace string
servingClient *servingv1client.ServingV1alpha1Client
eventingClient *eventingv1client.EventingV1alpha1Client
config *rest.Config
}
func NewDescriber(namespace string) (describer *Describer, err error) {
describer = &Describer{namespace: namespace}
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
if describer.namespace == "" {
namespace, _, err := clientConfig.Namespace()
if err == nil {
describer.namespace = namespace
}
}
config, err := clientConfig.ClientConfig()
if err != nil {
return
}
describer.servingClient, err = servingv1client.NewForConfig(config)
if err != nil {
return
}
describer.eventingClient, err = eventingv1client.NewForConfig(config)
if err != nil {
return
}
describer.config = config
return
}
func (describer *Describer) Describe(name string) (description client.FunctionDescription, err error) {
namespace := describer.namespace
servingClient := describer.servingClient
eventingClient := describer.eventingClient
service, err := servingClient.Services(namespace).Get(name, metav1.GetOptions{})
if err != nil {
return
}
serviceLabel := fmt.Sprintf("serving.knative.dev/service=%s", name)
routes, err := servingClient.Routes(namespace).List(metav1.ListOptions{LabelSelector: serviceLabel})
if err != nil {
return
}
routeURLs := make([]string, 0, len(routes.Items))
for _, route := range routes.Items {
routeURLs = append(routeURLs, route.Status.URL.String())
}
triggers, err := eventingClient.Triggers(namespace).List(metav1.ListOptions{})
if err != nil {
return
}
triggerMatches := func(t *v1alpha1.Trigger) bool {
return (t.Spec.Subscriber.Ref != nil && t.Spec.Subscriber.Ref.Name == service.Name) ||
(t.Spec.Subscriber.URI != nil && service.Status.Address != nil && service.Status.Address.URL != nil &&
t.Spec.Subscriber.URI.Path == service.Status.Address.URL.Path)
}
subscriptions := make([]client.Subscription, 0, len(triggers.Items))
for _, trigger := range triggers.Items {
if triggerMatches(&trigger) {
filterAttrs := *trigger.Spec.Filter.Attributes
subscription := client.Subscription{
Source: filterAttrs["source"],
Type: filterAttrs["type"],
Broker: trigger.Spec.Broker,
}
subscriptions = append(subscriptions, subscription)
}
}
description.Name = service.Name
description.Routes = routeURLs
description.Subscriptions = subscriptions
return
}