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
64 changes: 64 additions & 0 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,58 @@ EXAMPLES
return cmd
}

// wrapInvalidKubeconfigError returns a user-friendly error for invalid kubeconfig paths
func wrapInvalidKubeconfigError(err error) error {
kubeconfigPath := os.Getenv("KUBECONFIG")
if kubeconfigPath == "" {
kubeconfigPath = "~/.kube/config (default)"
}

return fmt.Errorf(`%w

The kubeconfig file at '%s' does not exist or is not accessible.

Try this:
export KUBECONFIG=~/.kube/config Use default kubeconfig
kubectl config view Verify current config
ls -la ~/.kube/config Check if config file exists

For more options, run 'func deploy --help'`, fn.ErrInvalidKubeconfig, kubeconfigPath)
}

// wrapClusterNotAccessibleError returns a user-friendly error for cluster connection failures
func wrapClusterNotAccessibleError(err error) error {
errMsg := err.Error()

// Case 1: Empty/no cluster configuration in kubeconfig
if strings.Contains(errMsg, "no configuration has been provided") ||
strings.Contains(errMsg, "invalid configuration") {
return fmt.Errorf(`%w

Cannot connect to Kubernetes cluster. No valid cluster configuration found.

Try this:
minikube start Start Minikube cluster
kind create cluster Start Kind cluster
kubectl cluster-info Verify cluster is running
kubectl config get-contexts List available contexts

For more options, run 'func deploy --help'`, fn.ErrClusterNotAccessible)
}

// Case 2: Cluster is down, network issues, auth errors, etc
return fmt.Errorf(`%w

Cannot connect to Kubernetes cluster.

Try this:
kubectl cluster-info Verify cluster is accessible
minikube status Check Minikube cluster status
kubectl get nodes Test cluster connection

For more options, run 'func deploy --help'`, fn.ErrClusterNotAccessible)
}

func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
var (
cfg deployConfig
Expand Down Expand Up @@ -354,6 +406,12 @@ For more options, run 'func deploy --help'`, err)
// Returned is the function with fields like Registry, f.Deploy.Image &
// f.Deploy.Namespace populated.
if url, f, err = client.RunPipeline(cmd.Context(), f); err != nil {
if errors.Is(err, fn.ErrInvalidKubeconfig) {
return wrapInvalidKubeconfigError(err)
}
if errors.Is(err, fn.ErrClusterNotAccessible) {
return wrapClusterNotAccessibleError(err)
}
return
}
fmt.Fprintf(cmd.OutOrStdout(), "Function Deployed at %v\n", url)
Expand Down Expand Up @@ -405,6 +463,12 @@ For more options, run 'func deploy --help'`, err)
}
}
if f, err = client.Deploy(cmd.Context(), f, fn.WithDeploySkipBuildCheck(cfg.Build == "false")); err != nil {
if errors.Is(err, fn.ErrInvalidKubeconfig) {
return wrapInvalidKubeconfigError(err)
}
if errors.Is(err, fn.ErrClusterNotAccessible) {
return wrapClusterNotAccessibleError(err)
}
return
}
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/functions/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ var (

// ErrConflictingImageAndRegistry is returned when both --image and --registry flags are explicitly provided
ErrConflictingImageAndRegistry = errors.New("both --image and --registry flags provided")

// ErrInvalidKubeconfig is returned when the kubeconfig file path is invalid or inaccessible
ErrInvalidKubeconfig = errors.New("invalid kubeconfig")

// ErrClusterNotAccessible is returned when cluster connection fails (network, auth, etc)
ErrClusterNotAccessible = errors.New("cluster not accessible")
)

// ErrNotInitialized indicates that a function is uninitialized
Expand Down
22 changes: 22 additions & 0 deletions pkg/knative/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package knative

import (
"fmt"
"os"
"time"

clienteventingv1 "knative.dev/client/pkg/eventing/v1"
clientservingv1 "knative.dev/client/pkg/serving/v1"
eventingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1"
servingv1 "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1"

fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/k8s"
)

Expand All @@ -18,6 +20,9 @@ const (
)

func NewServingClient(namespace string) (clientservingv1.KnServingClient, error) {
if err := validateKubeconfigFile(); err != nil {
return nil, err
}

restConfig, err := k8s.GetClientConfig().ClientConfig()
if err != nil {
Expand All @@ -35,6 +40,9 @@ func NewServingClient(namespace string) (clientservingv1.KnServingClient, error)
}

func NewEventingClient(namespace string) (clienteventingv1.KnEventingClient, error) {
if err := validateKubeconfigFile(); err != nil {
return nil, err
}

restConfig, err := k8s.GetClientConfig().ClientConfig()
if err != nil {
Expand All @@ -50,3 +58,17 @@ func NewEventingClient(namespace string) (clienteventingv1.KnEventingClient, err

return client, nil
}

// validateKubeconfigFile checks if explicitly set KUBECONFIG path exists
func validateKubeconfigFile() error {
kubeconfigPath := os.Getenv("KUBECONFIG")
if kubeconfigPath == "" {
return nil
}

if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
return fmt.Errorf("%w: kubeconfig file does not exist at path: %s", fn.ErrInvalidKubeconfig, kubeconfigPath)
}

return nil
}
50 changes: 47 additions & 3 deletions pkg/knative/deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,17 +157,17 @@ func (d *Deployer) Deploy(ctx context.Context, f fn.Function) (fn.DeploymentResu
// Clients
client, err := NewServingClient(namespace)
if err != nil {
return fn.DeploymentResult{}, err
return fn.DeploymentResult{}, wrapDeployerClientError(err)
}
eventingClient, err := NewEventingClient(namespace)
if err != nil {
return fn.DeploymentResult{}, err
return fn.DeploymentResult{}, wrapDeployerClientError(err)
}
// check if 'dapr-system' namespace exists
daprInstalled := false
k8sClient, err := k8s.NewKubernetesClientset()
if err != nil {
return fn.DeploymentResult{}, err
return fn.DeploymentResult{}, wrapDeployerClientError(err)
}
_, err = k8sClient.CoreV1().Namespaces().Get(ctx, "dapr-system", metav1.GetOptions{})
if err == nil {
Expand All @@ -187,6 +187,9 @@ func (d *Deployer) Deploy(ctx context.Context, f fn.Function) (fn.DeploymentResu

previousService, err := client.GetService(ctx, f.Name)
if err != nil {
if wrappedErr := wrapK8sConnectionError(err); wrappedErr != nil {
return fn.DeploymentResult{}, wrappedErr
}
if errors.IsNotFound(err) {

referencedSecrets := sets.New[string]()
Expand Down Expand Up @@ -1118,3 +1121,44 @@ func setServiceOptions(template *v1.RevisionTemplateSpec, options fn.Options) er

return servingclientlib.UpdateRevisionTemplateAnnotations(template, toUpdate, toRemove)
}

// wrapDeployerClientError wraps Kubernetes client creation errors with typed errors
func wrapDeployerClientError(err error) error {
if err == nil {
return nil
}

errMsg := err.Error()

// Missing kubeconfig file
if strings.Contains(errMsg, "kubeconfig file does not exist at path") {
return fmt.Errorf("%w: %v", fn.ErrInvalidKubeconfig, err)
}

// Empty config or cluster not accessible
if strings.Contains(errMsg, "no configuration has been provided") ||
strings.Contains(errMsg, "invalid configuration") {
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
}

return err
}

// wrapK8sConnectionError wraps connection errors during API calls
func wrapK8sConnectionError(err error) error {
if err == nil {
return nil
}

errMsg := err.Error()

// Connection errors (refused, timeout, certificate issues)
if strings.Contains(errMsg, "connection refused") ||
strings.Contains(errMsg, "dial tcp") ||
strings.Contains(errMsg, "i/o timeout") ||
strings.Contains(errMsg, "x509:") {
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
}

return nil
}
Loading