Skip to content

Commit 0c72cf2

Browse files
committed
refactor based on the review to move the implementation at the deployer instead of too much pre-validation at the cli
Signed-off-by: RayyanSeliya <[email protected]>
1 parent 09e7c98 commit 0c72cf2

File tree

3 files changed

+81
-66
lines changed

3 files changed

+81
-66
lines changed

cmd/deploy.go

Lines changed: 12 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ 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"
1817
"knative.dev/client/pkg/util"
1918

2019
"knative.dev/func/pkg/builders"
@@ -289,57 +288,6 @@ Try this:
289288
For more options, run 'func deploy --help'`, fn.ErrClusterNotAccessible)
290289
}
291290

292-
// validateClusterConnection checks if the Kubernetes cluster is accessible before starting build
293-
func validateClusterConnection() error {
294-
// Try to get cluster configuration
295-
restConfig, err := k8s.GetClientConfig().ClientConfig()
296-
if err != nil {
297-
kubeconfigPath := os.Getenv("KUBECONFIG")
298-
299-
// Check if this is an empty/missing config error
300-
if clientcmd.IsEmptyConfig(err) {
301-
// If KUBECONFIG is explicitly set, check if the file exists
302-
if kubeconfigPath != "" {
303-
if _, statErr := os.Stat(kubeconfigPath); os.IsNotExist(statErr) {
304-
// File doesn't exist - return invalid kubeconfig error for real usage
305-
// but skip for test paths (tests may have stale KUBECONFIG paths)
306-
if !strings.Contains(kubeconfigPath, "/testdata/") &&
307-
!strings.Contains(kubeconfigPath, "\\testdata\\") {
308-
return fmt.Errorf("%w: %v", fn.ErrInvalidKubeconfig, err)
309-
}
310-
// Test path - skip validation
311-
return nil
312-
}
313-
}
314-
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
315-
}
316-
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
317-
}
318-
319-
// Skip connectivity check for non-production clusters (example, test, localhost)
320-
host := restConfig.Host
321-
if strings.Contains(host, ".example.com") ||
322-
strings.Contains(host, "example.com:") ||
323-
strings.Contains(host, "localhost") ||
324-
strings.Contains(host, "127.0.0.1") {
325-
return nil
326-
}
327-
328-
// Create Kubernetes client to test connectivity
329-
client, err := k8s.NewKubernetesClientset()
330-
if err != nil {
331-
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
332-
}
333-
334-
// Verify cluster is actually reachable with an API call
335-
_, err = client.Discovery().ServerVersion()
336-
if err != nil {
337-
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
338-
}
339-
340-
return nil
341-
}
342-
343291
func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
344292
var (
345293
cfg deployConfig
@@ -419,17 +367,6 @@ For more options, run 'func deploy --help'`, err)
419367
}
420368
cmd.SetContext(cfg.WithValues(cmd.Context())) // Some optional settings are passed via context
421369

422-
// Validate cluster connection before building
423-
if err = validateClusterConnection(); err != nil {
424-
if errors.Is(err, fn.ErrInvalidKubeconfig) {
425-
return wrapInvalidKubeconfigError(err)
426-
}
427-
if errors.Is(err, fn.ErrClusterNotAccessible) {
428-
return wrapClusterNotAccessibleError(err)
429-
}
430-
return err
431-
}
432-
433370
changingNamespace := func(f fn.Function) bool {
434371
// We're changing namespace if:
435372
return f.Deploy.Namespace != "" && // it's already deployed
@@ -469,6 +406,12 @@ For more options, run 'func deploy --help'`, err)
469406
// Returned is the function with fields like Registry, f.Deploy.Image &
470407
// f.Deploy.Namespace populated.
471408
if url, f, err = client.RunPipeline(cmd.Context(), f); err != nil {
409+
if errors.Is(err, fn.ErrInvalidKubeconfig) {
410+
return wrapInvalidKubeconfigError(err)
411+
}
412+
if errors.Is(err, fn.ErrClusterNotAccessible) {
413+
return wrapClusterNotAccessibleError(err)
414+
}
472415
return
473416
}
474417
fmt.Fprintf(cmd.OutOrStdout(), "Function Deployed at %v\n", url)
@@ -520,6 +463,12 @@ For more options, run 'func deploy --help'`, err)
520463
}
521464
}
522465
if f, err = client.Deploy(cmd.Context(), f, fn.WithDeploySkipBuildCheck(cfg.Build == "false")); err != nil {
466+
if errors.Is(err, fn.ErrInvalidKubeconfig) {
467+
return wrapInvalidKubeconfigError(err)
468+
}
469+
if errors.Is(err, fn.ErrClusterNotAccessible) {
470+
return wrapClusterNotAccessibleError(err)
471+
}
523472
return
524473
}
525474
}

pkg/knative/client.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package knative
22

33
import (
44
"fmt"
5+
"os"
56
"time"
67

78
clienteventingv1 "knative.dev/client/pkg/eventing/v1"
89
clientservingv1 "knative.dev/client/pkg/serving/v1"
910
eventingv1 "knative.dev/eventing/pkg/client/clientset/versioned/typed/eventing/v1"
1011
servingv1 "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1"
1112

13+
fn "knative.dev/func/pkg/functions"
1214
"knative.dev/func/pkg/k8s"
1315
)
1416

@@ -18,6 +20,9 @@ const (
1820
)
1921

2022
func NewServingClient(namespace string) (clientservingv1.KnServingClient, error) {
23+
if err := validateKubeconfigFile(); err != nil {
24+
return nil, err
25+
}
2126

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

3742
func NewEventingClient(namespace string) (clienteventingv1.KnEventingClient, error) {
43+
if err := validateKubeconfigFile(); err != nil {
44+
return nil, err
45+
}
3846

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

5159
return client, nil
5260
}
61+
62+
// validateKubeconfigFile checks if explicitly set KUBECONFIG path exists
63+
func validateKubeconfigFile() error {
64+
kubeconfigPath := os.Getenv("KUBECONFIG")
65+
if kubeconfigPath == "" {
66+
return nil
67+
}
68+
69+
if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
70+
return fmt.Errorf("%w: kubeconfig file does not exist at path: %s", fn.ErrInvalidKubeconfig, kubeconfigPath)
71+
}
72+
73+
return nil
74+
}

pkg/knative/deployer.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,17 @@ func (d *Deployer) Deploy(ctx context.Context, f fn.Function) (fn.DeploymentResu
157157
// Clients
158158
client, err := NewServingClient(namespace)
159159
if err != nil {
160-
return fn.DeploymentResult{}, err
160+
return fn.DeploymentResult{}, wrapDeployerClientError(err)
161161
}
162162
eventingClient, err := NewEventingClient(namespace)
163163
if err != nil {
164-
return fn.DeploymentResult{}, err
164+
return fn.DeploymentResult{}, wrapDeployerClientError(err)
165165
}
166166
// check if 'dapr-system' namespace exists
167167
daprInstalled := false
168168
k8sClient, err := k8s.NewKubernetesClientset()
169169
if err != nil {
170-
return fn.DeploymentResult{}, err
170+
return fn.DeploymentResult{}, wrapDeployerClientError(err)
171171
}
172172
_, err = k8sClient.CoreV1().Namespaces().Get(ctx, "dapr-system", metav1.GetOptions{})
173173
if err == nil {
@@ -187,6 +187,9 @@ func (d *Deployer) Deploy(ctx context.Context, f fn.Function) (fn.DeploymentResu
187187

188188
previousService, err := client.GetService(ctx, f.Name)
189189
if err != nil {
190+
if wrappedErr := wrapK8sConnectionError(err); wrappedErr != nil {
191+
return fn.DeploymentResult{}, wrappedErr
192+
}
190193
if errors.IsNotFound(err) {
191194

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

11191122
return servingclientlib.UpdateRevisionTemplateAnnotations(template, toUpdate, toRemove)
11201123
}
1124+
1125+
// wrapDeployerClientError wraps Kubernetes client creation errors with typed errors
1126+
func wrapDeployerClientError(err error) error {
1127+
if err == nil {
1128+
return nil
1129+
}
1130+
1131+
errMsg := err.Error()
1132+
1133+
// Missing kubeconfig file
1134+
if strings.Contains(errMsg, "kubeconfig file does not exist at path") {
1135+
return fmt.Errorf("%w: %v", fn.ErrInvalidKubeconfig, err)
1136+
}
1137+
1138+
// Empty config or cluster not accessible
1139+
if strings.Contains(errMsg, "no configuration has been provided") ||
1140+
strings.Contains(errMsg, "invalid configuration") {
1141+
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
1142+
}
1143+
1144+
return err
1145+
}
1146+
1147+
// wrapK8sConnectionError wraps connection errors during API calls
1148+
func wrapK8sConnectionError(err error) error {
1149+
if err == nil {
1150+
return nil
1151+
}
1152+
1153+
errMsg := err.Error()
1154+
1155+
// Connection errors (refused, timeout, certificate issues)
1156+
if strings.Contains(errMsg, "connection refused") ||
1157+
strings.Contains(errMsg, "dial tcp") ||
1158+
strings.Contains(errMsg, "i/o timeout") ||
1159+
strings.Contains(errMsg, "x509:") {
1160+
return fmt.Errorf("%w: %v", fn.ErrClusterNotAccessible, err)
1161+
}
1162+
1163+
return nil
1164+
}

0 commit comments

Comments
 (0)