Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions internal/resource/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package resource

import (
"fmt"
"strings"

"k8s.io/apimachinery/pkg/api/errors"
)

// wrapKubernetesError takes an error returned from a Kubernetes API call and returns a more user-friendly error message if it recognizes the error type If err is nil, it returns nil. If the error is not recognized, it returns the original error.
func wrapKubernetesError(err error) error {
if err == nil {
return nil
}

if errors.IsUnauthorized(err) {
return fmt.Errorf("you are not authorized to access the cluster. Please check if you are logged in and your credentials are valid: %w", err)
}

if errors.IsForbidden(err) {
return fmt.Errorf("you do not have permission to perform this action in the cluster: %w", err)
}

errStr := strings.ToLower(err.Error())
if strings.Contains(errStr, "no configuration has been provided") ||
strings.Contains(errStr, "unable to load in-cluster configuration") ||
strings.Contains(errStr, "couldn't find kubeconfig file") {
return fmt.Errorf("no Kubernetes configuration found. Please ensure you have a valid kubeconfig file or that your KUBECONFIG environment variable is set: %w", err)
}

return err
}
Comment thread
matheusandre1 marked this conversation as resolved.
32 changes: 16 additions & 16 deletions internal/resource/resource_searcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ func NewResourceSearcher(resourceType string) (*Searcher, error) {
configOverrides := &clientcmd.ConfigOverrides{}
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides).ClientConfig()
if err != nil {
return nil, fmt.Errorf("error creating Kubernetes config: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating Kubernetes config: %w", err))
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating Kubernetes clientset: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating Kubernetes clientset: %w", err))
}

dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating dynamic client: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating dynamic client: %w", err))
}

kubeGet, err := gokubeget.NewKubeGet(config)
Expand All @@ -70,17 +70,17 @@ func NewGenericResourceSearcher(apiVersion, kind string) (*Searcher, error) {
configOverrides := &clientcmd.ConfigOverrides{}
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides).ClientConfig()
if err != nil {
return nil, fmt.Errorf("error creating Kubernetes config: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating Kubernetes config: %w", err))
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating Kubernetes clientset: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating Kubernetes clientset: %w", err))
}

dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating dynamic client: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating dynamic client: %w", err))
}

kubeGet, err := gokubeget.NewKubeGet(config)
Expand All @@ -105,17 +105,17 @@ func NewAutoDiscoveryResourceSearcher(kind string) (*Searcher, error) {
configOverrides := &clientcmd.ConfigOverrides{}
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides).ClientConfig()
if err != nil {
return nil, fmt.Errorf("error creating Kubernetes config: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating Kubernetes config: %w", err))
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating Kubernetes clientset: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating Kubernetes clientset: %w", err))
}

dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating dynamic client: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error creating dynamic client: %w", err))
}

kubeGet, err := gokubeget.NewKubeGet(config)
Expand All @@ -137,7 +137,7 @@ func NewAutoDiscoveryResourceSearcher(kind string) (*Searcher, error) {
func (s *Searcher) SearchWithoutNamespace(pattern string) ([]Occurrence, error) {
namespace, err := s.getDefaultNamespace()
if err != nil {
return nil, fmt.Errorf("error getting default namespace: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error getting default namespace: %w", err))
}
return s.Search(namespace, pattern)
}
Expand All @@ -150,7 +150,7 @@ func (s *Searcher) Search(namespace, pattern string) ([]Occurrence, error) {

resources, err := s.getGenericResourceNames(namespace)
if err != nil {
return nil, fmt.Errorf("error getting resources: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error getting resources: %w", err))
}

var occurrences []Occurrence
Expand All @@ -170,7 +170,7 @@ func (s *Searcher) SearchAllNamespaces(pattern string) ([]Occurrence, error) {

namespaces, err := s.getAllNamespaces()
if err != nil {
return nil, fmt.Errorf("error getting namespaces: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error getting namespaces: %w", err))
}

var allOccurrences []Occurrence
Expand Down Expand Up @@ -238,7 +238,7 @@ func (s *Searcher) getAllNamespaces() ([]string, error) {

namespaces, err := s.clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("error listing namespaces: %v", err)
return nil, wrapKubernetesError(fmt.Errorf("error listing namespaces: %w", err))
}

var namespaceNames []string
Expand Down Expand Up @@ -302,7 +302,7 @@ func (s *Searcher) getGenericResourceNames(namespace string) ([]string, error) {
}
}

return nil, fmt.Errorf("error getting %s resources: %v", s.kind, err)
return nil, wrapKubernetesError(fmt.Errorf("error getting %s resources: %w", s.kind, err))
}

// getGenericResourceYAML gets YAML for generic resources.
Expand Down Expand Up @@ -362,7 +362,7 @@ func (s *Searcher) getGenericResourceYAML(namespace, name string) (string, error
}
}

return "", fmt.Errorf("error getting %s resources: %v", s.kind, err)
return "", wrapKubernetesError(fmt.Errorf("error getting %s resources: %w", s.kind, err))
}

// objectToYAML converts a runtime.Object to YAML string.
Expand All @@ -383,7 +383,7 @@ func (s *Searcher) discoverAPIVersionAndKind() (string, string, string, error) {

apiGroups, err := s.clientset.Discovery().ServerGroups()
if err != nil {
return "", "", "", fmt.Errorf("error getting API groups: %v", err)
return "", "", "", wrapKubernetesError(fmt.Errorf("error getting API groups: %w", err))
}

for _, group := range apiGroups.Groups {
Expand Down
63 changes: 63 additions & 0 deletions internal/resource/resource_searcher_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package resource

import (
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/fake"
)

Expand Down Expand Up @@ -365,3 +368,63 @@ func TestSearcher_APIVersionAndKindPrecedence(t *testing.T) {
assert.Error(t, err)
assert.Contains(t, err.Error(), "kubeGet client not available")
}

func TestWrapKubernetesError(t *testing.T) {
assert.NoError(t, wrapKubernetesError(nil))

t.Run("unauthorized error", func(t *testing.T) {
origErr := errors.NewUnauthorized("unauthorized")
wrapped := wrapKubernetesError(origErr)
assert.Error(t, wrapped)
assert.Contains(t, wrapped.Error(), "you are not authorized to access the cluster")
assert.ErrorIs(t, wrapped, origErr)
})

t.Run("forbidden error", func(t *testing.T) {
origErr := errors.NewForbidden(schema.GroupResource{}, "name", fmt.Errorf("forbidden"))
wrapped := wrapKubernetesError(origErr)
assert.Error(t, wrapped)
assert.Contains(t, wrapped.Error(), "you do not have permission to perform this action")
assert.ErrorIs(t, wrapped, origErr)
})

t.Run("configuration errors", func(t *testing.T) {
configErrs := []string{
"no configuration has been provided",
"unable to load in-cluster configuration",
"Couldn't find kubeconfig file",
}

for _, msg := range configErrs {
t.Run(msg, func(t *testing.T) {
origErr := fmt.Errorf("%s", msg)
wrapped := wrapKubernetesError(origErr)
assert.Error(t, wrapped)
assert.Contains(t, wrapped.Error(), "no Kubernetes configuration found")
assert.ErrorIs(t, wrapped, origErr)
})
}
})

t.Run("invalid configuration substring is not rewritten", func(t *testing.T) {
origErr := fmt.Errorf("parser failed: invalid configuration in application payload")
wrapped := wrapKubernetesError(origErr)
assert.Equal(t, origErr, wrapped)
})

t.Run("context preservation", func(t *testing.T) {
origErr := errors.NewUnauthorized("unauthorized")
contextErr := fmt.Errorf("outer context: %w", origErr)
wrapped := wrapKubernetesError(contextErr)
assert.Error(t, wrapped)
assert.Contains(t, wrapped.Error(), "outer context")
assert.Contains(t, wrapped.Error(), "you are not authorized to access the cluster")
assert.ErrorIs(t, wrapped, origErr)
})

t.Run("unknown error", func(t *testing.T) {
origErr := fmt.Errorf("some random error")
wrapped := wrapKubernetesError(origErr)
assert.Equal(t, origErr, wrapped)
})
}