Skip to content

Commit

Permalink
List inbuilt sources if CRD access is restricted
Browse files Browse the repository at this point in the history
 Fixes knative#947
 - Identify restricted access error
 - If server returns restricted access error, fallback to listing
   only eventing inbuilt sources using their GVKs.
 - List any inbuilt source (ApiServerSource) object and read the error
   to know if eventing is installed for `kn source list-types`.
  • Loading branch information
navidshaikh committed Jul 23, 2020
1 parent 3129669 commit a0e527b
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 14 deletions.
53 changes: 50 additions & 3 deletions pkg/dynamic/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package dynamic

import (
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
Expand Down Expand Up @@ -48,6 +50,9 @@ type KnDynamicClient interface {
// ListSources returns list of available source objects
ListSources(types ...WithType) (*unstructured.UnstructuredList, error)

// ListSources returns list of available source objects using given list of GVKs
ListSourcesUsingGVKs(*[]schema.GroupVersionKind, ...WithType) (*unstructured.UnstructuredList, error)

// RawClient returns the raw dynamic client interface
RawClient() dynamic.Interface
}
Expand Down Expand Up @@ -107,7 +112,7 @@ func (c *knDynamicClient) ListSources(types ...WithType) (*unstructured.Unstruct
var (
sourceList unstructured.UnstructuredList
options metav1.ListOptions
numberOfsourceTypesFound int
numberOfSourceTypesFound int
)
sourceTypes, err := c.ListSourcesTypes()
if err != nil {
Expand Down Expand Up @@ -141,14 +146,56 @@ func (c *knDynamicClient) ListSources(types ...WithType) (*unstructured.Unstruct

if len(sList.Items) > 0 {
// keep a track if we found source objects of different types
numberOfsourceTypesFound++
numberOfSourceTypesFound++
sourceList.Items = append(sourceList.Items, sList.Items...)
sourceList.SetGroupVersionKind(sList.GetObjectKind().GroupVersionKind())
}
}
// Clear the Group and Version for list if there are multiple types of source objects found
// Keep the source's GVK if there is only one type of source objects found or requested via --type filter
if numberOfSourceTypesFound > 1 {
sourceList.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "", Kind: "List"})
}
return &sourceList, nil
}

// ListSources returns list of available source objects using given list of GVKs
func (c *knDynamicClient) ListSourcesUsingGVKs(gvks *[]schema.GroupVersionKind, types ...WithType) (*unstructured.UnstructuredList, error) {
if gvks == nil {
return nil, nil
}

var (
sourceList unstructured.UnstructuredList
options metav1.ListOptions
numberOfSourceTypesFound int
)
namespace := c.Namespace()
filters := WithTypes(types).List()

for _, gvk := range *gvks {
if len(filters) > 0 && !util.SliceContainsIgnoreCase(filters, gvk.Kind) {
continue
}

gvr := gvk.GroupVersion().WithResource(strings.ToLower(gvk.Kind) + "s")

// list objects of source type with this GVR
sList, err := c.client.Resource(gvr).Namespace(namespace).List(options)
if err != nil {
return nil, err
}

if len(sList.Items) > 0 {
// keep a track if we found source objects of different types
numberOfSourceTypesFound++
sourceList.Items = append(sourceList.Items, sList.Items...)
sourceList.SetGroupVersionKind(sList.GetObjectKind().GroupVersionKind())
}
}
// Clear the Group and Version for list if there are multiple types of source objects found
// Keep the source's GVK if there is only one type of source objects found or requested via --type filter
if numberOfsourceTypesFound > 1 {
if numberOfSourceTypesFound > 1 {
sourceList.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "", Kind: "List"})
}
return &sourceList, nil
Expand Down
22 changes: 22 additions & 0 deletions pkg/dynamic/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package dynamic

import (
"fmt"
"strings"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand Down Expand Up @@ -123,3 +124,24 @@ func (types WithTypes) List() []string {
}
return stypes
}

func UnstructuredCRDFromGVK(gvk schema.GroupVersionKind) *unstructured.Unstructured {
name := fmt.Sprintf("%ss.%s", strings.ToLower(gvk.Kind), gvk.Group)
plural := fmt.Sprintf("%ss", strings.ToLower(gvk.Kind))
u := &unstructured.Unstructured{}
u.SetUnstructuredContent(map[string]interface{}{
"metadata": map[string]interface{}{
"name": name,
},
"spec": map[string]interface{}{
"group": gvk.Group,
"version": gvk.Version,
"names": map[string]interface{}{
"kind": gvk.Kind,
"plural": plural,
},
},
})

return u
}
6 changes: 5 additions & 1 deletion pkg/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
func newInvalidCRD(apiGroup string) *KNError {
parts := strings.Split(apiGroup, ".")
name := parts[0]
msg := fmt.Sprintf("no Knative %s API found on the backend, please verify the installation", name)
msg := fmt.Sprintf("404: no Knative %s API found on the backend, please verify the installation", name)
return NewKNError(msg)
}

Expand All @@ -37,3 +37,7 @@ func newNoRouteToHost(errString string) error {
func newNoKubeConfig(errString string) error {
return NewKNError("no kubeconfig has been provided, please use a valid configuration to connect to the cluster")
}

func newForbidden(code int32, msg string) *KNError {
return NewKNError(fmt.Sprintf("%d: %s", code, msg))
}
6 changes: 3 additions & 3 deletions pkg/errors/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import (

func TestNewInvalidCRD(t *testing.T) {
err := newInvalidCRD("serving.knative.dev")
assert.Error(t, err, "no Knative serving API found on the backend, please verify the installation")
assert.Error(t, err, "404: no Knative serving API found on the backend, please verify the installation")

err = newInvalidCRD("eventing")
assert.Error(t, err, "no Knative eventing API found on the backend, please verify the installation")
assert.Error(t, err, "404: no Knative eventing API found on the backend, please verify the installation")

err = newInvalidCRD("")
assert.Error(t, err, "no Knative API found on the backend, please verify the installation")
assert.Error(t, err, "404: no Knative API found on the backend, please verify the installation")

}
15 changes: 13 additions & 2 deletions pkg/errors/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package errors

import (
"net/http"
"strings"

api_errors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -38,6 +39,10 @@ func isEmptyConfigError(err error) bool {
return strings.Contains(err.Error(), "no configuration has been provided")
}

func IsForbiddenError(status api_errors.APIStatus) bool {
return status.Status().Code == http.StatusForbidden
}

//Retrieves a custom error struct based on the original error APIStatus struct
//Returns the original error struct in case it can't identify the kind of APIStatus error
func GetError(err error) error {
Expand All @@ -55,11 +60,17 @@ func GetError(err error) error {
return err
}
var knerr *KNError
if isCRDError(apiStatus) {
switch {
case isCRDError(apiStatus):
knerr = newInvalidCRD(apiStatus.Status().Details.Group)
knerr.Status = apiStatus
return knerr
case IsForbiddenError(apiStatus):
knerr = newForbidden(apiStatus.Status().Code, apiStatus.Status().Message)
knerr.Status = apiStatus
return knerr
default:
return err
}
return err
}
}
2 changes: 1 addition & 1 deletion pkg/errors/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestKnErrorsStatusErrors(t *testing.T) {
}
return statusError
},
ExpectedMsg: "no Knative serving API found on the backend, please verify the installation",
ExpectedMsg: "404: no Knative serving API found on the backend, please verify the installation",
Validate: func(t *testing.T, err error, msg string) {
assert.Error(t, err, msg)
},
Expand Down
12 changes: 11 additions & 1 deletion pkg/kn/commands/source/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ package source

import (
"fmt"
"strings"

"github.com/spf13/cobra"

"knative.dev/client/pkg/dynamic"
knerrors "knative.dev/client/pkg/errors"
"knative.dev/client/pkg/kn/commands"
"knative.dev/client/pkg/kn/commands/flags"
"knative.dev/client/pkg/kn/commands/source/duck"
sourcesv1alpha2 "knative.dev/client/pkg/sources/v1alpha2"
)

var listExample = `
Expand Down Expand Up @@ -58,8 +61,15 @@ func NewListCommand(p *commands.KnParams) *cobra.Command {
}
sourceList, err := dynamicClient.ListSources(filters...)
if err != nil {
return err
if strings.HasPrefix(knerrors.GetError(err).Error(), "403") {
gvks := sourcesv1alpha2.BuiltInSourcesGVKs()
sourceList, err = dynamicClient.ListSourcesUsingGVKs(&gvks, filters...)
if err != nil {
return knerrors.GetError(err)
}
}
}

if len(sourceList.Items) == 0 {
fmt.Fprintf(cmd.OutOrStdout(), "No sources found in %s namespace.\n", namespace)
return nil
Expand Down
36 changes: 34 additions & 2 deletions pkg/kn/commands/source/list_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ package source

import (
"fmt"
"strings"

"github.com/spf13/cobra"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"knative.dev/client/pkg/dynamic"
knerrors "knative.dev/client/pkg/errors"
"knative.dev/client/pkg/kn/commands"
"knative.dev/client/pkg/kn/commands/flags"
sourcesv1alpha2 "knative.dev/client/pkg/sources/v1alpha2"
)

// NewListTypesCommand defines and processes `kn source list-types`
Expand Down Expand Up @@ -48,11 +53,21 @@ func NewListTypesCommand(p *commands.KnParams) *cobra.Command {

sourceListTypes, err := dynamicClient.ListSourcesTypes()
if err != nil {
return err
if strings.HasPrefix(knerrors.GetError(err).Error(), "403") {
sourcesClient, err := p.NewSourcesClient(namespace)
if err != nil {
return err
}

sourceListTypes, err = listBuiltInSourceTypes(sourcesClient)
if err != nil {
return knerrors.GetError(err)
}
}
}

if len(sourceListTypes.Items) == 0 {
fmt.Fprintf(cmd.OutOrStdout(), "No sources found.\n")
fmt.Fprintf(cmd.OutOrStdout(), "404: no sources found on the backend, please verify the installation\n")
return nil
}

Expand All @@ -73,3 +88,20 @@ func NewListTypesCommand(p *commands.KnParams) *cobra.Command {
listTypesFlags.AddFlags(listTypesCommand)
return listTypesCommand
}

func listBuiltInSourceTypes(c sourcesv1alpha2.KnSourcesClient) (*unstructured.UnstructuredList, error) {
_, err := c.APIServerSourcesClient().ListAPIServerSource()
if err != nil {
if strings.HasPrefix(err.Error(), "404") {
return nil, err
}
}

uList := unstructured.UnstructuredList{}
gvks := sourcesv1alpha2.BuiltInSourcesGVKs()
for _, gvk := range gvks {
u := dynamic.UnstructuredCRDFromGVK(gvk)
uList.Items = append(uList.Items, *u)
}
return &uList, nil
}
2 changes: 1 addition & 1 deletion pkg/sources/v1alpha2/apiserver_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func (c *apiServerSourcesClient) Namespace() string {
func (c *apiServerSourcesClient) ListAPIServerSource() (*v1alpha2.ApiServerSourceList, error) {
sourceList, err := c.client.List(metav1.ListOptions{})
if err != nil {
return nil, err
return nil, knerrors.GetError(err)
}

return updateAPIServerSourceListGVK(sourceList)
Expand Down
14 changes: 14 additions & 0 deletions pkg/sources/v1alpha2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
package v1alpha2

import (
"strings"

"k8s.io/apimachinery/pkg/runtime/schema"
sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
clientv1alpha2 "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1alpha2"
)

Expand Down Expand Up @@ -61,3 +65,13 @@ func (c *sourcesClient) SinkBindingClient() KnSinkBindingClient {
func (c *sourcesClient) APIServerSourcesClient() KnAPIServerSourcesClient {
return newKnAPIServerSourcesClient(c.client.ApiServerSources(c.namespace), c.namespace)
}

// BuiltInSourcesGVKs returns the GVKs for built in sources
func BuiltInSourcesGVKs() []schema.GroupVersionKind {
return []schema.GroupVersionKind{
sourcesv1alpha2.SchemeGroupVersion.WithKind("ApiServerSource"),
sourcesv1alpha2.SchemeGroupVersion.WithKind("ContainerSource"),
sourcesv1alpha2.SchemeGroupVersion.WithKind("PingSource"),
sourcesv1alpha2.SchemeGroupVersion.WithKind("SinkBinding"),
}
}

0 comments on commit a0e527b

Please sign in to comment.