Skip to content

Commit 4cb046b

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 2c34ac2 commit 4cb046b

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"
@@ -290,57 +289,6 @@ Try this:
290289
For more options, run 'func deploy --help'`, fn.ErrClusterNotAccessible)
291290
}
292291

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

439-
// Validate cluster connection before building
440-
if err = validateClusterConnection(); err != nil {
441-
if errors.Is(err, fn.ErrInvalidKubeconfig) {
442-
return wrapInvalidKubeconfigError(err)
443-
}
444-
if errors.Is(err, fn.ErrClusterNotAccessible) {
445-
return wrapClusterNotAccessibleError(err)
446-
}
447-
return err
448-
}
449-
450387
changingNamespace := func(f fn.Function) bool {
451388
// We're changing namespace if:
452389
return f.Deploy.Namespace != "" && // it's already deployed
@@ -486,6 +423,12 @@ For more options, run 'func deploy --help'`, err)
486423
// Returned is the function with fields like Registry, f.Deploy.Image &
487424
// f.Deploy.Namespace populated.
488425
if url, f, err = client.RunPipeline(cmd.Context(), f); err != nil {
426+
if errors.Is(err, fn.ErrInvalidKubeconfig) {
427+
return wrapInvalidKubeconfigError(err)
428+
}
429+
if errors.Is(err, fn.ErrClusterNotAccessible) {
430+
return wrapClusterNotAccessibleError(err)
431+
}
489432
return
490433
}
491434
fmt.Fprintf(cmd.OutOrStdout(), "Function Deployed at %v\n", url)
@@ -537,6 +480,12 @@ For more options, run 'func deploy --help'`, err)
537480
}
538481
}
539482
if f, err = client.Deploy(cmd.Context(), f, fn.WithDeploySkipBuildCheck(cfg.Build == "false")); err != nil {
483+
if errors.Is(err, fn.ErrInvalidKubeconfig) {
484+
return wrapInvalidKubeconfigError(err)
485+
}
486+
if errors.Is(err, fn.ErrClusterNotAccessible) {
487+
return wrapClusterNotAccessibleError(err)
488+
}
540489
return
541490
}
542491
}

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)