diff --git a/client/client.go b/client/client.go index 21d6f0062c..c875f33e04 100644 --- a/client/client.go +++ b/client/client.go @@ -10,7 +10,7 @@ import ( "strings" "golang.org/x/net/publicsuffix" - "gopkg.in/yaml.v3" + "gopkg.in/yaml.v2" ) // Client for a given Service Function. @@ -28,6 +28,7 @@ type Client struct { updater Updater // Updates a deployed Service Function runner Runner // Runs the function locally remover Remover // Removes remote services + lister Lister // Lists remote services } // ConfigFileName is an optional file checked for in the function root. @@ -95,6 +96,12 @@ type Remover interface { Remove(name string) error } +// Lister of deployed services. +type Lister interface { + // List the service functions currently deployed. + List() ([]string, error) +} + // Option defines a function which when passed to the Client constructor optionally // mutates private members at time of instantiation. type Option func(*Client) @@ -182,6 +189,13 @@ func WithRemover(r Remover) Option { } } +// WithLister provides the concrete implementation of a lister. +func WithLister(l Lister) Option { + return func(c *Client) { + c.lister = l + } +} + // 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. @@ -341,6 +355,12 @@ func (c *Client) Run() error { return c.runner.Run(c.root) } +// List currently deployed service functions. +func (c *Client) List() ([]string, error) { + // delegate to concrete implementation of lister entirely. + return c.lister.List() +} + // 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 diff --git a/client/client_test.go b/client/client_test.go index 7f9511d6e9..595d8aca54 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -483,38 +483,6 @@ func TestRemove(t *testing.T) { } } -// TestRemoveExplicit ensures that a call to remove an explicit name, which -// may differ from the service function the client is associated wtith, is -// respected and passed along to the concrete remover implementation. -func TestRemoveExplicit(t *testing.T) { - var ( - root = "./testdata/example.com/admin" - name = "www.example.com" // Differs from that derived from root. - remover = mock.NewRemover() - ) - - // Create the test function root - os.MkdirAll(root, 0700) - defer os.RemoveAll(root) - - client, err := client.New(root, - client.WithRemover(remover)) - if err != nil { - t.Fatal(err) - } - remover.RemoveFn = func(name2 string) error { - if name2 != name { - t.Fatalf("remover expected name '%v' got '%v'", name, name2) - } - return nil - } - // Call remove with an explicit name which differs from that associated - // to the current client instance. - if err := client.Remove(name); err != nil { - t.Fatal(err) - } -} - // TestWithName ensures that an explicitly passed name is used in leau of the // path derived name when provide, and persists through instantiations. // This also ensures that an initialized service function's name persists if @@ -570,3 +538,24 @@ func TestWithName(t *testing.T) { } } + +// TestList ensures that the client invokes the configured lister. +func TestList(t *testing.T) { + var lister = mock.NewLister() + + client, err := client.New("", + client.WithLister(lister), // lists deployed service functions. + ) + if err != nil { + t.Fatal(err) + } + + _, err = client.List() + if err != nil { + t.Fatal(err) + } + + if !lister.ListInvoked { + t.Fatal("list did not invoke lister implementation") + } +} diff --git a/client/mock/lister.go b/client/mock/lister.go new file mode 100644 index 0000000000..929346995c --- /dev/null +++ b/client/mock/lister.go @@ -0,0 +1,17 @@ +package mock + +type Lister struct { + ListInvoked bool + ListFn func() ([]string, error) +} + +func NewLister() *Lister { + return &Lister{ + ListFn: func() ([]string, error) { return []string{}, nil }, + } +} + +func (l *Lister) List() ([]string, error) { + l.ListInvoked = true + return l.ListFn() +} diff --git a/cmd/list.go b/cmd/list.go index e40dfb5bfe..7d5ed46c50 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -2,11 +2,11 @@ package cmd import ( "fmt" + + "github.com/lkingland/faas/client" + "github.com/lkingland/faas/knative" "github.com/ory/viper" "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/clientcmd" - servingv1client "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1" ) const ( @@ -21,31 +21,38 @@ func init() { var listCmd = &cobra.Command{ Use: "list", - Short: "Lists deployed Service Function", - Long: `Lists deployed Service Function`, + Short: "Lists deployed Service Functions", + Long: `Lists deployed Service Functions`, SuggestFor: []string{"ls"}, RunE: list, } func list(cmd *cobra.Command, args []string) (err error) { - loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() - clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) - config, err := clientConfig.ClientConfig() + var ( + namespace = viper.GetString(nsFlag) + verbose = viper.GetBool("verbose") + ) + + lister, err := knative.NewLister(namespace) if err != nil { return } - client, err := servingv1client.NewForConfig(config) + lister.Verbose = verbose + + client, err := client.New(".", + client.WithVerbose(verbose), + client.WithLister(lister), + ) if err != nil { return } - opts := metav1.ListOptions{LabelSelector: "bosonFunction"} - ns := viper.GetString(nsFlag) - lst, err := client.Services(ns).List(opts) + + names, err := client.List() if err != nil { return } - for _, service := range lst.Items { - fmt.Printf("%s/%s", service.Namespace, service.Name) + for _, name := range names { + fmt.Printf("%s\n", name) } - return nil + return } diff --git a/cmd/version.go b/cmd/version.go index 6513dc2870..fc6e92bed0 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -8,7 +8,7 @@ import ( // Version // Printed on subcommand `version` or flag `--version` -const Version = "v0.0.14" +const Version = "v0.0.15" func init() { root.AddCommand(versionCmd) diff --git a/go.mod b/go.mod index 9ef8068cb0..33851f5179 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( 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.v3 v3.0.0-20200313102051-9f266ea9e77c + gopkg.in/yaml.v2 v2.2.8 k8s.io/apimachinery v0.17.4 k8s.io/client-go v0.17.4 knative.dev/pkg v0.0.0-20200414233146-0eed424fa4ee // indirect diff --git a/knative/lister.go b/knative/lister.go new file mode 100644 index 0000000000..e9bc782fa5 --- /dev/null +++ b/knative/lister.go @@ -0,0 +1,42 @@ +package knative + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" + servingv1client "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1" +) + +const labelSelector = "bosonFunction" + +type Lister struct { + Verbose bool + namespace string + client *servingv1client.ServingV1Client +} + +func NewLister(namespace string) (l *Lister, err error) { + l = &Lister{} + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) + config, err := clientConfig.ClientConfig() + if err != nil { + return + } + l.client, err = servingv1client.NewForConfig(config) + if err != nil { + return + } + return +} + +func (l *Lister) List() (names []string, err error) { + opts := metav1.ListOptions{LabelSelector: "bosonFunction"} + lst, err := l.client.Services(l.namespace).List(opts) + if err != nil { + return + } + for _, service := range lst.Items { + names = append(names, service.Name) + } + return +}