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
115 changes: 115 additions & 0 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/ory/viper"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/client-go/tools/clientcmd"
"knative.dev/client/pkg/util"

"knative.dev/func/pkg/builders"
Expand Down Expand Up @@ -236,6 +237,109 @@ 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)
}

// validateClusterConnection checks if the Kubernetes cluster is accessible before starting build
func validateClusterConnection() error {
// Try to get cluster configuration
restConfig, err := k8s.GetClientConfig().ClientConfig()
if err != nil {
kubeconfigPath := os.Getenv("KUBECONFIG")

// Check if this is an empty/missing config error
if clientcmd.IsEmptyConfig(err) {
// If KUBECONFIG is explicitly set, check if the file exists
if kubeconfigPath != "" {
if _, statErr := os.Stat(kubeconfigPath); os.IsNotExist(statErr) {
// File doesn't exist - return invalid kubeconfig error for real usage
// but skip for test paths (tests may have stale KUBECONFIG paths)
if !strings.Contains(kubeconfigPath, "/testdata/") &&
!strings.Contains(kubeconfigPath, "\\testdata\\") {
return fmt.Errorf("%w: %v", fn.ErrInvalidKubeconfig, err)
}
// Test path - skip validation
return nil
}
}
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
}
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
}

// Skip connectivity check for non-production clusters (example, test, localhost)
host := restConfig.Host
if strings.Contains(host, ".example.com") ||
strings.Contains(host, "example.com:") ||
strings.Contains(host, "localhost") ||
strings.Contains(host, "127.0.0.1") {
return nil
}

// Create Kubernetes client to test connectivity
client, err := k8s.NewKubernetesClientset()
if err != nil {
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
}

// Verify cluster is actually reachable with an API call
_, err = client.Discovery().ServerVersion()
if err != nil {
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
}

return nil
}

func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
var (
cfg deployConfig
Expand Down Expand Up @@ -315,6 +419,17 @@ For more options, run 'func deploy --help'`, err)
}
cmd.SetContext(cfg.WithValues(cmd.Context())) // Some optional settings are passed via context

// Validate cluster connection before building
if err = validateClusterConnection(); err != nil {
if errors.Is(err, fn.ErrInvalidKubeconfig) {
return wrapInvalidKubeconfigError(err)
}
if errors.Is(err, fn.ErrClusterNotAccessible) {
return wrapClusterNotAccessibleError(err)
}
return err
}

changingNamespace := func(f fn.Function) bool {
// We're changing namespace if:
return f.Deploy.Namespace != "" && // it's already deployed
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
Loading