Add cronjob and apiserver source list commands (#559)

* Add kn source cronjob list command

* Add kn source apiserver list command
This commit is contained in:
Navid Shaikh 2019-12-17 21:59:04 +05:30 committed by Knative Prow Robot
parent 8b1434b789
commit 6380fc5eaf
20 changed files with 719 additions and 11 deletions

View File

@ -30,5 +30,6 @@ kn source apiserver [flags]
* [kn source apiserver create](kn_source_apiserver_create.md) - Create an ApiServer source.
* [kn source apiserver delete](kn_source_apiserver_delete.md) - Delete an ApiServer source.
* [kn source apiserver describe](kn_source_apiserver_describe.md) - Describe an ApiServer source.
* [kn source apiserver list](kn_source_apiserver_list.md) - List ApiServer sources.
* [kn source apiserver update](kn_source_apiserver_update.md) - Update an ApiServer source.

View File

@ -22,8 +22,8 @@ kn source apiserver create NAME --resource RESOURCE --service-account ACCOUNTNAM
```
-h, --help help for create
--mode string The mode the receive adapter controller runs under:,
"Ref" sends only the reference to the resource,
--mode string The mode the receive adapter controller runs under:,
"Ref" sends only the reference to the resource,
"Resource" send the full resource. (default "Ref")
-n, --namespace string Specify the namespace to operate in.
--resource strings Comma seperate Kind:APIVersion:isController list, e.g. Event:v1:true.

View File

@ -0,0 +1,44 @@
## kn source apiserver list
List ApiServer sources.
### Synopsis
List ApiServer sources.
```
kn source apiserver list [flags]
```
### Examples
```
# List all ApiServer sources in YAML format
kn source apiserver list -o yaml
```
### Options
```
-A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.
--allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true)
-h, --help help for list
-n, --namespace string Specify the namespace to operate in.
--no-headers When using the default output format, don't print headers (default: print headers).
-o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file.
--template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
```
### Options inherited from parent commands
```
--config string kn config file (default is $HOME/.kn/config.yaml)
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
--log-http log http traffic
```
### SEE ALSO
* [kn source apiserver](kn_source_apiserver.md) - Kubernetes API Server Event Source command group

View File

@ -22,8 +22,8 @@ kn source apiserver update NAME --resource RESOURCE --service-account ACCOUNTNAM
```
-h, --help help for update
--mode string The mode the receive adapter controller runs under:,
"Ref" sends only the reference to the resource,
--mode string The mode the receive adapter controller runs under:,
"Ref" sends only the reference to the resource,
"Resource" send the full resource. (default "Ref")
-n, --namespace string Specify the namespace to operate in.
--resource strings Comma seperate Kind:APIVersion:isController list, e.g. Event:v1:true.

View File

@ -30,5 +30,6 @@ kn source cronjob [flags]
* [kn source cronjob create](kn_source_cronjob_create.md) - Create a CronJob source.
* [kn source cronjob delete](kn_source_cronjob_delete.md) - Delete a CronJob source.
* [kn source cronjob describe](kn_source_cronjob_describe.md) - Describe a CronJob source.
* [kn source cronjob list](kn_source_cronjob_list.md) - List CronJob sources.
* [kn source cronjob update](kn_source_cronjob_update.md) - Update a CronJob source.

View File

@ -0,0 +1,44 @@
## kn source cronjob list
List CronJob sources.
### Synopsis
List CronJob sources.
```
kn source cronjob list [flags]
```
### Examples
```
# List all CronJob sources in YAML format
kn source cronjob list -o yaml
```
### Options
```
-A, --all-namespaces If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.
--allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true)
-h, --help help for list
-n, --namespace string Specify the namespace to operate in.
--no-headers When using the default output format, don't print headers (default: print headers).
-o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file.
--template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
```
### Options inherited from parent commands
```
--config string kn config file (default is $HOME/.kn/config.yaml)
--kubeconfig string kubectl config file (default is $HOME/.kube/config)
--log-http log http traffic
```
### SEE ALSO
* [kn source cronjob](kn_source_cronjob.md) - CronJob source command group

View File

@ -37,6 +37,10 @@ type KnAPIServerSourcesClient interface {
// Delete an ApiServerSource by name
DeleteAPIServerSource(name string) error
// List ApiServerSource
// TODO: Support list configs like in service list
ListAPIServerSource() (*v1alpha1.ApiServerSourceList, error)
// Get namespace for this client
Namespace() string
}
@ -98,6 +102,35 @@ func (c *apiServerSourcesClient) Namespace() string {
return c.namespace
}
// ListAPIServerSource returns the available ApiServer type sources
func (c *apiServerSourcesClient) ListAPIServerSource() (*v1alpha1.ApiServerSourceList, error) {
sourceList, err := c.client.List(metav1.ListOptions{})
if err != nil {
return nil, err
}
return updateAPIServerSourceListGVK(sourceList)
}
func updateAPIServerSourceListGVK(sourceList *v1alpha1.ApiServerSourceList) (*v1alpha1.ApiServerSourceList, error) {
sourceListNew := sourceList.DeepCopy()
err := updateSourceGVK(sourceListNew)
if err != nil {
return nil, err
}
sourceListNew.Items = make([]v1alpha1.ApiServerSource, len(sourceList.Items))
for idx, source := range sourceList.Items {
sourceClone := source.DeepCopy()
err := updateSourceGVK(sourceClone)
if err != nil {
return nil, err
}
sourceListNew.Items[idx] = *sourceClone
}
return sourceListNew, nil
}
// APIServerSourceBuilder is for building the source
type APIServerSourceBuilder struct {
apiServerSource *v1alpha1.ApiServerSource

View File

@ -103,6 +103,17 @@ func (c *MockKnAPIServerSourceClient) DeleteAPIServerSource(name string) error {
return mock.ErrorOrNil(call.Result[0])
}
// ListAPIServerSource records a call for ListAPIServerSource with the expected error (nil if none)
func (sr *APIServerSourcesRecorder) ListAPIServerSource(apiJobSourceList *v1alpha1.ApiServerSourceList, err error) {
sr.r.Add("ListAPIServerSource", []interface{}{}, []interface{}{apiJobSourceList, err})
}
// ListAPIServerSource performs a previously recorded action, failing if non has been registered
func (c *MockKnAPIServerSourceClient) ListAPIServerSource() (*v1alpha1.ApiServerSourceList, error) {
call := c.recorder.r.VerifyCall("ListAPIServerSource")
return call.Result[0].(*v1alpha1.ApiServerSourceList), mock.ErrorOrNil(call.Result[1])
}
// Validate validates whether every recorded action has been called
func (sr *APIServerSourcesRecorder) Validate() {
sr.r.CheckThatAllRecordedMethodsHaveBeenCalled()

View File

@ -116,6 +116,20 @@ func TestUpdateApiServerSource(t *testing.T) {
assert.ErrorContains(t, err, "errorSource")
}
func TestListAPIServerSource(t *testing.T) {
sourcesServer, client := setupAPIServerSourcesClient(t)
sourcesServer.AddReactor("list", "apiserversources",
func(a client_testing.Action) (bool, runtime.Object, error) {
cJSource := newAPIServerSource("testsource", "Event")
return true, &v1alpha1.ApiServerSourceList{Items: []v1alpha1.ApiServerSource{*cJSource}}, nil
})
sourceList, err := client.ListAPIServerSource()
assert.NilError(t, err)
assert.Equal(t, len(sourceList.Items), 1)
}
func newAPIServerSource(name, resource string) *v1alpha1.ApiServerSource {
b := NewAPIServerSourceBuilder(name).ServiceAccount("testsa").Mode("Ref")
b.Sink(&v1beta1.Destination{

View File

@ -17,8 +17,12 @@ package v1alpha1
import (
"fmt"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
"knative.dev/client/pkg/util"
"knative.dev/eventing/pkg/client/clientset/versioned/scheme"
client_v1alpha1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha1"
duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
)
@ -38,6 +42,10 @@ type KnCronJobSourcesClient interface {
// Delete a cronjob source by name
DeleteCronJobSource(name string) error
// List CronJob sources
// TODO: Support list configs like in service list
ListCronJobSource() (*v1alpha1.CronJobSourceList, error)
// Get namespace for this source
Namespace() string
}
@ -77,11 +85,44 @@ func (c *cronJobSourcesClient) UpdateCronJobSource(cronjobSource *v1alpha1.CronJ
}
func (c *cronJobSourcesClient) DeleteCronJobSource(name string) error {
return c.client.Delete(name, &meta_v1.DeleteOptions{})
return c.client.Delete(name, &metav1.DeleteOptions{})
}
func (c *cronJobSourcesClient) GetCronJobSource(name string) (*v1alpha1.CronJobSource, error) {
return c.client.Get(name, meta_v1.GetOptions{})
return c.client.Get(name, metav1.GetOptions{})
}
// ListCronJobSource returns the available CronJob type sources
func (c *cronJobSourcesClient) ListCronJobSource() (*v1alpha1.CronJobSourceList, error) {
sourceList, err := c.client.List(metav1.ListOptions{})
if err != nil {
return nil, err
}
return updateCronJobSourceListGVK(sourceList)
}
func updateCronJobSourceListGVK(sourceList *v1alpha1.CronJobSourceList) (*v1alpha1.CronJobSourceList, error) {
sourceListNew := sourceList.DeepCopy()
err := updateSourceGVK(sourceListNew)
if err != nil {
return nil, err
}
sourceListNew.Items = make([]v1alpha1.CronJobSource, len(sourceList.Items))
for idx, source := range sourceList.Items {
sourceClone := source.DeepCopy()
err := updateSourceGVK(sourceClone)
if err != nil {
return nil, err
}
sourceListNew.Items[idx] = *sourceClone
}
return sourceListNew, nil
}
func updateSourceGVK(obj runtime.Object) error {
return util.UpdateGroupVersionKindWithScheme(obj, v1alpha1.SchemeGroupVersion, scheme.Scheme)
}
// Builder for building up cronjob sources
@ -92,7 +133,7 @@ type CronJobSourceBuilder struct {
func NewCronJobSourceBuilder(name string) *CronJobSourceBuilder {
return &CronJobSourceBuilder{cronjobSource: &v1alpha1.CronJobSource{
ObjectMeta: meta_v1.ObjectMeta{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}}

View File

@ -102,6 +102,17 @@ func (c *MockKnCronJobSourceClient) DeleteCronJobSource(name string) error {
return mock.ErrorOrNil(call.Result[0])
}
// ListCronJobSource records a call for ListCronJobSource with the expected error (nil if none)
func (sr *CronJobSourcesRecorder) ListCronJobSource(cronJobSourceList *v1alpha1.CronJobSourceList, err error) {
sr.r.Add("ListCronJobSource", []interface{}{}, []interface{}{cronJobSourceList, err})
}
// ListCronJobSource performs a previously recorded action, failing if non has been registered
func (c *MockKnCronJobSourceClient) ListCronJobSource() (*v1alpha1.CronJobSourceList, error) {
call := c.recorder.r.VerifyCall("ListCronJobSource")
return call.Result[0].(*v1alpha1.CronJobSourceList), mock.ErrorOrNil(call.Result[1])
}
// Validates validates whether every recorded action has been called
func (sr *CronJobSourcesRecorder) Validate() {
sr.r.CheckThatAllRecordedMethodsHaveBeenCalled()

View File

@ -119,6 +119,20 @@ func TestGetCronJobSource(t *testing.T) {
assert.ErrorContains(t, err, "errorSource")
}
func TestListCronJobSource(t *testing.T) {
sourcesServer, client := setupCronJobSourcesClient(t)
sourcesServer.AddReactor("list", "cronjobsources",
func(a client_testing.Action) (bool, runtime.Object, error) {
cJSource := newCronJobSource("testsource", "mysvc")
return true, &v1alpha1.CronJobSourceList{Items: []v1alpha1.CronJobSource{*cJSource}}, nil
})
sourceList, err := client.ListCronJobSource()
assert.NilError(t, err)
assert.Equal(t, len(sourceList.Items), 1)
}
func newCronJobSource(name string, sink string) *v1alpha1.CronJobSource {
b := NewCronJobSourceBuilder(name).
Schedule("* * * * *").

View File

@ -34,6 +34,7 @@ func NewAPIServerCommand(p *commands.KnParams) *cobra.Command {
apiServerSourceCmd.AddCommand(NewAPIServerUpdateCommand(p))
apiServerSourceCmd.AddCommand(NewAPIServerDescribeCommand(p))
apiServerSourceCmd.AddCommand(NewAPIServerDeleteCommand(p))
apiServerSourceCmd.AddCommand(NewAPIServerListCommand(p))
return apiServerSourceCmd
}

View File

@ -15,11 +15,18 @@
package apiserver
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
"knative.dev/client/pkg/kn/commands"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
hprinters "knative.dev/client/pkg/printers"
)
const (
@ -77,8 +84,8 @@ func (f *APIServerSourceUpdateFlags) Add(cmd *cobra.Command) {
cmd.Flags().StringVar(&f.Mode,
"mode",
"Ref",
`The mode the receive adapter controller runs under:,
"Ref" sends only the reference to the resource,
`The mode the receive adapter controller runs under:,
"Ref" sends only the reference to the resource,
"Resource" send the full resource.`)
cmd.Flags().StringSliceVar(&f.Resources,
"resource",
@ -87,3 +94,108 @@ func (f *APIServerSourceUpdateFlags) Add(cmd *cobra.Command) {
"APIVersion" and "isControler" can be omitted.
"APIVersion" is "v1" by default, "isController" is "false" by default.`)
}
// APIServerSourceListHandlers handles printing human readable table for `kn source apiserver list` command's output
func APIServerSourceListHandlers(h hprinters.PrintHandler) {
sourceColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Namespace", Type: "string", Description: "Namespace of the ApiServer source", Priority: 0},
{Name: "Name", Type: "string", Description: "Name of the ApiServer source", Priority: 1},
{Name: "Resources", Type: "string", Description: "Event resources configured for the ApiServer source", Priority: 1},
{Name: "Sink", Type: "string", Description: "Sink of the ApiServer source", Priority: 1},
{Name: "Conditions", Type: "string", Description: "Ready state conditions", Priority: 1},
{Name: "Ready", Type: "string", Description: "Ready state of the ApiServer source", Priority: 1},
{Name: "Reason", Type: "string", Description: "Reason if state is not Ready", Priority: 1},
}
h.TableHandler(sourceColumnDefinitions, printSource)
h.TableHandler(sourceColumnDefinitions, printSourceList)
}
// printSource populates a single row of source apiserver list table
func printSource(source *v1alpha1.ApiServerSource, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: source},
}
name := source.Name
conditions := commands.ConditionsValue(source.Status.Conditions)
ready := commands.ReadyCondition(source.Status.Conditions)
reason := strings.TrimSpace(commands.NonReadyConditionReason(source.Status.Conditions))
var resources []string
for _, resource := range source.Spec.Resources {
resources = append(resources, fmt.Sprintf("%s:%s:%s", resource.Kind, resource.APIVersion, strconv.FormatBool(resource.Controller)))
}
var sink string
if source.Spec.Sink != nil {
if source.Spec.Sink.Ref != nil {
if source.Spec.Sink.Ref.Kind == "Service" {
sink = fmt.Sprintf("svc:%s", source.Spec.Sink.Ref.Name)
} else {
sink = fmt.Sprintf("%s:%s", source.Spec.Sink.Ref.Kind, source.Spec.Sink.Ref.Name)
}
}
}
if options.AllNamespaces {
row.Cells = append(row.Cells, source.Namespace)
}
row.Cells = append(row.Cells, name, strings.Join(resources[:], ","), sink, conditions, ready, reason)
return []metav1beta1.TableRow{row}, nil
}
// printSourceList populates the source apiserver list table rows
func printSourceList(sourceList *v1alpha1.ApiServerSourceList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
if options.AllNamespaces {
return printSourceListWithNamespace(sourceList, options)
}
rows := make([]metav1beta1.TableRow, 0, len(sourceList.Items))
sort.SliceStable(sourceList.Items, func(i, j int) bool {
return sourceList.Items[i].GetName() < sourceList.Items[j].GetName()
})
for _, item := range sourceList.Items {
row, err := printSource(&item, options)
if err != nil {
return nil, err
}
rows = append(rows, row...)
}
return rows, nil
}
// printSourceListWithNamespace populates the knative service table rows with namespace column
func printSourceListWithNamespace(sourceList *v1alpha1.ApiServerSourceList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(sourceList.Items))
// temporary slice for sorting services in non-default namespace
others := []metav1beta1.TableRow{}
for _, source := range sourceList.Items {
// Fill in with services in `default` namespace at first
if source.Namespace == "default" {
r, err := printSource(&source, options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
continue
}
// put other services in temporary slice
r, err := printSource(&source, options)
if err != nil {
return nil, err
}
others = append(others, r...)
}
// sort other services list alphabetically by namespace
sort.SliceStable(others, func(i, j int) bool {
return others[i].Cells[0].(string) < others[j].Cells[0].(string)
})
return append(rows, others...), nil
}

View File

@ -0,0 +1,74 @@
// Copyright © 2019 The Knative Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiserver
import (
"fmt"
"github.com/spf13/cobra"
"knative.dev/client/pkg/kn/commands"
"knative.dev/client/pkg/kn/commands/flags"
)
// NewAPIServerListCommand is for listing ApiServer source COs
func NewAPIServerListCommand(p *commands.KnParams) *cobra.Command {
listFlags := flags.NewListFlags(APIServerSourceListHandlers)
listCommand := &cobra.Command{
Use: "list",
Short: "List ApiServer sources.",
Example: `
# List all ApiServer sources in YAML format
kn source apiserver list -o yaml`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
// TODO: filter list by given source name
apiSourceClient, err := newAPIServerSourceClient(p, cmd)
if err != nil {
return err
}
sourceList, err := apiSourceClient.ListAPIServerSource()
if err != nil {
return err
}
if len(sourceList.Items) == 0 {
fmt.Fprintf(cmd.OutOrStdout(), "No ApiServer source found.\n")
return nil
}
if apiSourceClient.Namespace() == "" {
listFlags.EnsureWithNamespace()
}
printer, err := listFlags.ToPrinter()
if err != nil {
return nil
}
err = printer.PrintObj(sourceList, cmd.OutOrStdout())
if err != nil {
return err
}
return nil
},
}
commands.AddNamespaceFlags(listCommand.Flags(), true)
listFlags.AddFlags(listCommand)
return listCommand
}

View File

@ -0,0 +1,59 @@
// Copyright © 2019 The Knative Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiserver
import (
"testing"
"gotest.tools/assert"
knsource_v1alpha1 "knative.dev/client/pkg/eventing/sources/v1alpha1"
"knative.dev/client/pkg/util"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
)
func TestListAPIServerSource(t *testing.T) {
apiServerClient := knsource_v1alpha1.NewMockKnAPIServerSourceClient(t)
apiServerRecorder := apiServerClient.Recorder()
sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Ref", "testsvc", false)
sampleSourceList := v1alpha1.ApiServerSourceList{}
sampleSourceList.Items = []v1alpha1.ApiServerSource{*sampleSource}
apiServerRecorder.ListAPIServerSource(&sampleSourceList, nil)
out, err := executeAPIServerSourceCommand(apiServerClient, nil, "list")
assert.NilError(t, err, "sources should be listed")
util.ContainsAll(out, "NAME", "RESOURCES", "SINK", "CONDITIONS", "READY", "REASON")
util.ContainsAll(out, "testsource", "Eventing:v1:false", "mysvc")
apiServerRecorder.Validate()
}
func TestListAPIServerSourceEmpty(t *testing.T) {
apiServerClient := knsource_v1alpha1.NewMockKnAPIServerSourceClient(t)
apiServerRecorder := apiServerClient.Recorder()
sampleSourceList := v1alpha1.ApiServerSourceList{}
apiServerRecorder.ListAPIServerSource(&sampleSourceList, nil)
out, err := executeAPIServerSourceCommand(apiServerClient, nil, "list")
assert.NilError(t, err, "Sources should be listed")
util.ContainsNone(out, "NAME", "RESOURCES", "SINK", "CONDITIONS", "READY", "REASON")
util.ContainsAll(out, "No", "ApiServer", "source", "found")
apiServerRecorder.Validate()
}

View File

@ -33,6 +33,7 @@ func NewCronJobCommand(p *commands.KnParams) *cobra.Command {
cronImporterCmd.AddCommand(NewCronJobDeleteCommand(p))
cronImporterCmd.AddCommand(NewCronJobDescribeCommand(p))
cronImporterCmd.AddCommand(NewCronJobUpdateCommand(p))
cronImporterCmd.AddCommand(NewCronJobListCommand(p))
return cronImporterCmd
}

View File

@ -14,7 +14,19 @@
package cronjob
import "github.com/spf13/cobra"
import (
"fmt"
"sort"
"github.com/spf13/cobra"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"knative.dev/client/pkg/kn/commands"
hprinters "knative.dev/client/pkg/printers"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
)
type cronJobUpdateFlags struct {
schedule string
@ -25,3 +37,105 @@ func (c *cronJobUpdateFlags) addCronJobFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&c.schedule, "schedule", "", "Schedule specification in crontab format (e.g. '* * * * */2' for every two minutes")
cmd.Flags().StringVarP(&c.data, "data", "d", "", "String data to send")
}
// CronJobListHandlers handles printing human readable table for `kn source cronjob list` command's output
func CronJobSourceListHandlers(h hprinters.PrintHandler) {
sourceColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Namespace", Type: "string", Description: "Namespace of the CronJob source", Priority: 0},
{Name: "Name", Type: "string", Description: "Name of the CronJob source", Priority: 1},
{Name: "Schedule", Type: "string", Description: "Schedule of the CronJob source", Priority: 1},
{Name: "Sink", Type: "string", Description: "Sink of the CronJob source", Priority: 1},
{Name: "Conditions", Type: "string", Description: "Ready state conditions", Priority: 1},
{Name: "Ready", Type: "string", Description: "Ready state of the CronJob source", Priority: 1},
{Name: "Reason", Type: "string", Description: "Reason if state is not Ready", Priority: 1},
}
h.TableHandler(sourceColumnDefinitions, printSource)
h.TableHandler(sourceColumnDefinitions, printSourceList)
}
// printSource populates a single row of source cronjob list table
func printSource(source *v1alpha1.CronJobSource, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: source},
}
name := source.Name
schedule := source.Spec.Schedule
conditions := commands.ConditionsValue(source.Status.Conditions)
ready := commands.ReadyCondition(source.Status.Conditions)
reason := commands.NonReadyConditionReason(source.Status.Conditions)
var sink string
if source.Spec.Sink != nil {
if source.Spec.Sink.Ref != nil {
if source.Spec.Sink.Ref.Kind == "Service" {
sink = fmt.Sprintf("svc:%s", source.Spec.Sink.Ref.Name)
} else {
sink = fmt.Sprintf("%s:%s", source.Spec.Sink.Ref.Kind, source.Spec.Sink.Ref.Name)
}
}
}
if options.AllNamespaces {
row.Cells = append(row.Cells, source.Namespace)
}
row.Cells = append(row.Cells, name, schedule, sink, conditions, ready, reason)
return []metav1beta1.TableRow{row}, nil
}
// printSourceList populates the source cronjob list table rows
func printSourceList(sourceList *v1alpha1.CronJobSourceList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
if options.AllNamespaces {
return printSourceListWithNamespace(sourceList, options)
}
rows := make([]metav1beta1.TableRow, 0, len(sourceList.Items))
sort.SliceStable(sourceList.Items, func(i, j int) bool {
return sourceList.Items[i].GetName() < sourceList.Items[j].GetName()
})
for _, item := range sourceList.Items {
row, err := printSource(&item, options)
if err != nil {
return nil, err
}
rows = append(rows, row...)
}
return rows, nil
}
// printSourceListWithNamespace populates the knative service table rows with namespace column
func printSourceListWithNamespace(sourceList *v1alpha1.CronJobSourceList, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
rows := make([]metav1beta1.TableRow, 0, len(sourceList.Items))
// temporary slice for sorting services in non-default namespace
others := []metav1beta1.TableRow{}
for _, source := range sourceList.Items {
// Fill in with services in `default` namespace at first
if source.Namespace == "default" {
r, err := printSource(&source, options)
if err != nil {
return nil, err
}
rows = append(rows, r...)
continue
}
// put other services in temporary slice
r, err := printSource(&source, options)
if err != nil {
return nil, err
}
others = append(others, r...)
}
// sort other services list alphabetically by namespace
sort.SliceStable(others, func(i, j int) bool {
return others[i].Cells[0].(string) < others[j].Cells[0].(string)
})
return append(rows, others...), nil
}

View File

@ -0,0 +1,74 @@
// Copyright © 2019 The Knative Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cronjob
import (
"fmt"
"github.com/spf13/cobra"
"knative.dev/client/pkg/kn/commands"
"knative.dev/client/pkg/kn/commands/flags"
)
// NewCronJobListCommand is for listing CronJob source COs
func NewCronJobListCommand(p *commands.KnParams) *cobra.Command {
listFlags := flags.NewListFlags(CronJobSourceListHandlers)
listCommand := &cobra.Command{
Use: "list",
Short: "List CronJob sources.",
Example: `
# List all CronJob sources in YAML format
kn source cronjob list -o yaml`,
RunE: func(cmd *cobra.Command, args []string) (err error) {
// TODO: filter list by given source name
cronSourceClient, err := newCronJobSourceClient(p, cmd)
if err != nil {
return err
}
sourceList, err := cronSourceClient.ListCronJobSource()
if err != nil {
return err
}
if len(sourceList.Items) == 0 {
fmt.Fprintf(cmd.OutOrStdout(), "No CronJob source found.\n")
return nil
}
if cronSourceClient.Namespace() == "" {
listFlags.EnsureWithNamespace()
}
printer, err := listFlags.ToPrinter()
if err != nil {
return nil
}
err = printer.PrintObj(sourceList, cmd.OutOrStdout())
if err != nil {
return err
}
return nil
},
}
commands.AddNamespaceFlags(listCommand.Flags(), true)
listFlags.AddFlags(listCommand)
return listCommand
}

View File

@ -0,0 +1,59 @@
// Copyright © 2019 The Knative Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cronjob
import (
"testing"
"gotest.tools/assert"
knsource_v1alpha1 "knative.dev/client/pkg/eventing/sources/v1alpha1"
"knative.dev/client/pkg/util"
"knative.dev/eventing/pkg/apis/sources/v1alpha1"
)
func TestListCronJobSource(t *testing.T) {
cronjobClient := knsource_v1alpha1.NewMockKnCronJobSourceClient(t)
cronJobRecorder := cronjobClient.Recorder()
cJSource := createCronJobSource("testsource", "* * * * */2", "maxwell", "mysvc")
cJSourceList := v1alpha1.CronJobSourceList{}
cJSourceList.Items = []v1alpha1.CronJobSource{*cJSource}
cronJobRecorder.ListCronJobSource(&cJSourceList, nil)
out, err := executeCronJobSourceCommand(cronjobClient, nil, "list")
assert.NilError(t, err, "Sources should be listed")
util.ContainsAll(out, "NAME", "SCHEDULE", "SINK", "CONDITIONS", "READY", "REASON")
util.ContainsAll(out, "testsource", "* * * * */2", "mysvc")
cronJobRecorder.Validate()
}
func TestListCronJobSourceEmpty(t *testing.T) {
cronjobClient := knsource_v1alpha1.NewMockKnCronJobSourceClient(t)
cronJobRecorder := cronjobClient.Recorder()
cJSourceList := v1alpha1.CronJobSourceList{}
cronJobRecorder.ListCronJobSource(&cJSourceList, nil)
out, err := executeCronJobSourceCommand(cronjobClient, nil, "list")
assert.NilError(t, err, "Sources should be listed")
util.ContainsNone(out, "NAME", "SCHEDULE", "SINK", "CONDITIONS", "READY", "REASON")
util.ContainsAll(out, "No", "CronJob", "source", "found")
cronJobRecorder.Validate()
}