Skip to content

Commit 64728ec

Browse files
committed
Add early cluster validation to prevent wasted build time in func deploy
Signed-off-by: RayyanSeliya <[email protected]>
1 parent 5093bfb commit 64728ec

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

cmd/deploy.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/ory/viper"
1515
"github.com/spf13/cobra"
1616
"k8s.io/apimachinery/pkg/api/resource"
17+
"k8s.io/client-go/tools/clientcmd"
1718
"knative.dev/client/pkg/util"
1819

1920
"knative.dev/func/pkg/builders"
@@ -236,6 +237,46 @@ EXAMPLES
236237
return cmd
237238
}
238239

240+
// validateClusterConnection checks if the Kubernetes cluster is accessible before starting build
241+
func validateClusterConnection() error {
242+
// Skip for test environments (check if using test kubeconfig)
243+
kubeconfigPath := os.Getenv("KUBECONFIG")
244+
if kubeconfigPath != "" && (strings.Contains(kubeconfigPath, "/testdata/") || strings.HasSuffix(kubeconfigPath, "default_kubeconfig")) {
245+
return nil
246+
}
247+
248+
restConfig, err := k8s.GetClientConfig().ClientConfig()
249+
if err != nil {
250+
if clientcmd.IsEmptyConfig(err) {
251+
if kubeconfigPath != "" {
252+
if _, statErr := os.Stat(kubeconfigPath); os.IsNotExist(statErr) {
253+
return fmt.Errorf("%w: %v", fn.ErrInvalidKubeconfig, err)
254+
}
255+
}
256+
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
257+
}
258+
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
259+
}
260+
261+
// Skip connectivity check for example/test clusters
262+
if strings.Contains(restConfig.Host, ".example.com") {
263+
return nil
264+
}
265+
266+
client, err := k8s.NewKubernetesClientset()
267+
if err != nil {
268+
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
269+
}
270+
271+
// Test actual cluster connectivity
272+
_, err = client.Discovery().ServerVersion()
273+
if err != nil {
274+
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
275+
}
276+
277+
return nil
278+
}
279+
239280
func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
240281
var (
241282
cfg deployConfig
@@ -315,6 +356,61 @@ For more options, run 'func deploy --help'`, err)
315356
}
316357
cmd.SetContext(cfg.WithValues(cmd.Context())) // Some optional settings are passed via context
317358

359+
// Validate cluster connection before building
360+
if err = validateClusterConnection(); err != nil {
361+
if errors.Is(err, fn.ErrInvalidKubeconfig) {
362+
kubeconfigPath := os.Getenv("KUBECONFIG")
363+
if kubeconfigPath == "" {
364+
kubeconfigPath = "~/.kube/config (default)"
365+
}
366+
367+
return fmt.Errorf(`%w
368+
369+
The kubeconfig file at '%s' does not exist or is not accessible.
370+
371+
Try this:
372+
export KUBECONFIG=~/.kube/config Use default kubeconfig
373+
kubectl config view Verify current config
374+
ls -la ~/.kube/config Check if config file exists
375+
376+
For more options, run 'func deploy --help'`, fn.ErrInvalidKubeconfig, kubeconfigPath)
377+
}
378+
379+
if errors.Is(err, fn.ErrClusterNotAccessible) {
380+
errMsg := err.Error()
381+
382+
// Case 1: Empty/no cluster configuration in kubeconfig
383+
if strings.Contains(errMsg, "no configuration has been provided") ||
384+
strings.Contains(errMsg, "invalid configuration") {
385+
return fmt.Errorf(`%w
386+
387+
Cannot connect to Kubernetes cluster. No valid cluster configuration found.
388+
389+
Try this:
390+
minikube start Start Minikube cluster
391+
kind create cluster Start Kind cluster
392+
kubectl cluster-info Verify cluster is running
393+
kubectl config get-contexts List available contexts
394+
395+
For more options, run 'func deploy --help'`, fn.ErrClusterNotAccessible)
396+
}
397+
398+
// Case 2: Cluster is down, network issues, auth errors, etc
399+
return fmt.Errorf(`%w
400+
401+
Cannot connect to Kubernetes cluster.
402+
403+
Try this:
404+
kubectl cluster-info Verify cluster is accessible
405+
minikube status Check Minikube cluster status
406+
kubectl get nodes Test cluster connection
407+
408+
For more options, run 'func deploy --help'`, fn.ErrClusterNotAccessible)
409+
}
410+
411+
return err
412+
}
413+
318414
changingNamespace := func(f fn.Function) bool {
319415
// We're changing namespace if:
320416
return f.Deploy.Namespace != "" && // it's already deployed

pkg/functions/errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ var (
3434

3535
// ErrConflictingImageAndRegistry is returned when both --image and --registry flags are explicitly provided
3636
ErrConflictingImageAndRegistry = errors.New("both --image and --registry flags provided")
37+
38+
// ErrInvalidKubeconfig is returned when the kubeconfig file path is invalid or inaccessible
39+
ErrInvalidKubeconfig = errors.New("invalid kubeconfig")
40+
41+
// ErrClusterNotAccessible is returned when cluster connection fails (network, auth, etc)
42+
ErrClusterNotAccessible = errors.New("cluster not accessible")
3743
)
3844

3945
// ErrNotInitialized indicates that a function is uninitialized

0 commit comments

Comments
 (0)