Repository: knative/client (go)

Issue:
Improve error reporting if kubeconfig is missing or cluster unreachable
on master right now I see this in a totally clean env:
```
$ kn service create --image asd asd
invalid configuration: no configuration has been provided
```

A config file should be optional.


Pull request description:
Improve reporting for missing kubeconfig and error connecting to the cluster
 Fixes #315

## Description
 - Improve error reporting if
    - kubeconfig file is missing or
    - there is no route to host
    - i/o timeout


/lint



Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 27191a42..46bee13d 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -18,6 +18,10 @@
 |===
 | | Description | PR
 
+| 🐛
+| Improve reporting for missing kubeconfig and error connecting to the cluster
+| https://github.com/knative/client/pull/725[#725]
+
 | 🎁
 | Add `kn source list`
 | https://github.com/knative/client/pull/666[#666]
diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go
index 99eab629..c9a0cd54 100644
--- a/pkg/errors/errors.go
+++ b/pkg/errors/errors.go
@@ -22,7 +22,18 @@ 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("no Knative %s API found on the backend, please verify the installation", name)
 	return NewKNError(msg)
 }
+
+func newNoRouteToHost(errString string) error {
+	parts := strings.SplitAfter(errString, "dial tcp")
+	if len(parts) == 2 {
+		return NewKNError(fmt.Sprintf("error connecting to the cluster, please verify connection at: %s", strings.Trim(parts[1], " ")))
+	}
+	return NewKNError(fmt.Sprintf("error connecting to the cluster: %s", errString))
+}
+
+func newNoKubeConfig(errString string) error {
+	return NewKNError("no kubeconfig has been provided, please use a valid configuration to connect to the cluster")
+}
diff --git a/pkg/errors/factory.go b/pkg/errors/factory.go
index 38e602db..d0b47385 100644
--- a/pkg/errors/factory.go
+++ b/pkg/errors/factory.go
@@ -27,29 +27,39 @@ func isCRDError(status api_errors.APIStatus) bool {
 			return true
 		}
 	}
-
 	return false
 }
 
+func isNoRouteToHostError(err error) bool {
+	return strings.Contains(err.Error(), "no route to host") || strings.Contains(err.Error(), "i/o timeout")
+}
+
+func isEmptyConfigError(err error) bool {
+	return strings.Contains(err.Error(), "no configuration has been provided")
+}
+
 //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 {
-	apiStatus, ok := err.(api_errors.APIStatus)
-	if !ok {
-		return err
-	}
-
-	if apiStatus.Status().Details == nil {
+	switch {
+	case isEmptyConfigError(err):
+		return newNoKubeConfig(err.Error())
+	case isNoRouteToHostError(err):
+		return newNoRouteToHost(err.Error())
+	default:
+		apiStatus, ok := err.(api_errors.APIStatus)
+		if !ok {
+			return err
+		}
+		if apiStatus.Status().Details == nil {
+			return err
+		}
+		var knerr *KNError
+		if isCRDError(apiStatus) {
+			knerr = newInvalidCRD(apiStatus.Status().Details.Group)
+			knerr.Status = apiStatus
+			return knerr
+		}
 		return err
 	}
-
-	var knerr *KNError
-
-	if isCRDError(apiStatus) {
-		knerr = newInvalidCRD(apiStatus.Status().Details.Group)
-		knerr.Status = apiStatus
-		return knerr
-	}
-
-	return err
 }
diff --git a/pkg/kn/commands/types.go b/pkg/kn/commands/types.go
index 5b30c534..b7b72352 100644
--- a/pkg/kn/commands/types.go
+++ b/pkg/kn/commands/types.go
@@ -31,6 +31,7 @@ import (
 	"knative.dev/client/pkg/util"
 
 	clientdynamic "knative.dev/client/pkg/dynamic"
+	knerrors "knative.dev/client/pkg/errors"
 	clienteventingv1alpha1 "knative.dev/client/pkg/eventing/v1alpha1"
 	clientservingv1 "knative.dev/client/pkg/serving/v1"
 )
@@ -145,13 +146,13 @@ func (params *KnParams) RestConfig() (*rest.Config, error) {
 	if params.ClientConfig == nil {
 		params.ClientConfig, err = params.GetClientConfig()
 		if err != nil {
-			return nil, err
+			return nil, knerrors.GetError(err)
 		}
 	}
 
 	config, err := params.ClientConfig.ClientConfig()
 	if err != nil {
-		return nil, err
+		return nil, knerrors.GetError(err)
 	}
 	if params.LogHTTP {
 		// TODO: When we update to the newer version of client-go, replace with


Test patch:
diff --git a/pkg/errors/errors_test.go b/pkg/errors/errors_test.go
index 016b5ef2..6e3df075 100644
--- a/pkg/errors/errors_test.go
+++ b/pkg/errors/errors_test.go
@@ -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, "no Knative serving API found on the backend, please verify the installation")
 
-	err = newInvalidCRD("serving")
-	assert.Error(t, err, "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")
 
 	err = newInvalidCRD("")
-	assert.Error(t, err, "no Knative  API found on the backend. Please verify the installation.")
+	assert.Error(t, err, "no Knative  API found on the backend, please verify the installation")
 
 }
diff --git a/pkg/errors/factory_test.go b/pkg/errors/factory_test.go
index bce45f55..9c94b949 100644
--- a/pkg/errors/factory_test.go
+++ b/pkg/errors/factory_test.go
@@ -15,6 +15,7 @@
 package errors
 
 import (
+	"errors"
 	"testing"
 
 	"gotest.tools/assert"
@@ -23,7 +24,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 )
 
-func TestBuild(t *testing.T) {
+func TestKnErrorsStatusErrors(t *testing.T) {
 	cases := []struct {
 		Name        string
 		Schema      schema.GroupResource
@@ -47,7 +48,7 @@ func TestBuild(t *testing.T) {
 				}
 				return statusError
 			},
-			ExpectedMsg: "no Knative serving API found on the backend. Please verify the installation.",
+			ExpectedMsg: "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)
 			},
@@ -92,3 +93,40 @@ func TestBuild(t *testing.T) {
 		})
 	}
 }
+
+func TestKnErrors(t *testing.T) {
+	cases := []struct {
+		Name        string
+		Error       error
+		ExpectedMsg string
+	}{
+		{
+			Name:        "no kubeconfig provided",
+			Error:       errors.New("invalid configuration: no configuration has been provided"),
+			ExpectedMsg: "no kubeconfig has been provided, please use a valid configuration to connect to the cluster",
+		},
+		{
+			Name:        "i/o timeout",
+			Error:       errors.New("Get https://api.example.com:27435/apis/foo/bar: dial tcp 192.168.1.1:27435: i/o timeout"),
+			ExpectedMsg: "error connecting to the cluster, please verify connection at: 192.168.1.1:27435: i/o timeout",
+		},
+		{
+			Name:        "no route to host",
+			Error:       errors.New("Get https://192.168.39.141:8443/apis/foo/bar: dial tcp 192.168.39.141:8443: connect: no route to host"),
+			ExpectedMsg: "error connecting to the cluster, please verify connection at: 192.168.39.141:8443: connect: no route to host",
+		},
+		{
+			Name:        "no route to host without dial tcp string",
+			Error:       errors.New("no route to host 192.168.1.1"),
+			ExpectedMsg: "error connecting to the cluster: no route to host 192.168.1.1",
+		},
+	}
+	for _, tc := range cases {
+		tc := tc
+		t.Run(tc.Name, func(t *testing.T) {
+			t.Parallel()
+			err := GetError(tc.Error)
+			assert.Error(t, err, tc.ExpectedMsg)
+		})
+	}
+}
diff --git a/pkg/kn/commands/types_test.go b/pkg/kn/commands/types_test.go
index 58c14db2..e83a52f4 100644
--- a/pkg/kn/commands/types_test.go
+++ b/pkg/kn/commands/types_test.go
@@ -61,7 +61,7 @@ func TestPrepareConfig(t *testing.T) {
 	for i, tc := range []configTestCase{
 		{
 			clientcmd.NewDefaultClientConfig(clientcmdapi.Config{}, &clientcmd.ConfigOverrides{}),
-			"no configuration has been provided",
+			"no kubeconfig has been provided, please use a valid configuration to connect to the cluster",
 			false,
 		},
 		{
@@ -151,7 +151,7 @@ func TestNewSourcesClient(t *testing.T) {
 	for i, tc := range []configTestCase{
 		{
 			clientcmd.NewDefaultClientConfig(clientcmdapi.Config{}, &clientcmd.ConfigOverrides{}),
-			"no configuration has been provided",
+			"no kubeconfig has been provided, please use a valid configuration to connect to the cluster",
 			false,
 		},
 		{
@@ -202,7 +202,7 @@ func TestNewDynamicClient(t *testing.T) {
 	for i, tc := range []configTestCase{
 		{
 			clientcmd.NewDefaultClientConfig(clientcmdapi.Config{}, &clientcmd.ConfigOverrides{}),
-			"no configuration has been provided",
+			"no kubeconfig has been provided, please use a valid configuration to connect to the cluster",
 			false,
 		},
 		{