Skip to content

Commit

Permalink
Try to reload config when disconnected from the cluster (#6130)
Browse files Browse the repository at this point in the history
* Try to reload config when disconnected from the cluster

* Fix integration test

* Message also when isForbidden
  • Loading branch information
feloy committed Sep 20, 2022
1 parent 5d4f1b2 commit 1d78b85
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 8 deletions.
1 change: 1 addition & 0 deletions pkg/kclient/interface.go
Expand Up @@ -76,6 +76,7 @@ type ClientInterface interface {
SetDiscoveryInterface(client discovery.DiscoveryInterface)
IsResourceSupported(apiGroup, apiVersion, resourceName string) (bool, error)
IsSSASupported() bool
Refresh() (newConfig bool, err error)

// namespace.go
GetCurrentNamespace() string
Expand Down
15 changes: 15 additions & 0 deletions pkg/kclient/mock_Client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions pkg/kclient/refresh.go
@@ -0,0 +1,67 @@
package kclient

import (
"fmt"
"reflect"

"k8s.io/client-go/tools/clientcmd"
)

// Refresh re-creates a new Kubernetes client and checks if the Config changes
// If config changed, updates the Kubernetes client with the new configuration and returns true
// If the namespace or cluster of the current context has changed since the last time
// the config has been loaded, the function will not update the configuration
func (c *Client) Refresh() (bool, error) {
newClient, err := New()
if err != nil {
return false, err
}

oldCluster, oldNs, err := getContext(c)
if err != nil {
return false, err
}
newCluster, newNs, err := getContext(newClient)
if err != nil {
return false, err
}

if oldCluster != newCluster {
return false, fmt.Errorf("cluster changed (%q -> %q), won't refresh the configuration", oldCluster, newCluster)
}
if oldNs != newNs {
return false, fmt.Errorf("namespace changed (%q -> %q), won't refresh the configuration", oldNs, newNs)
}

updated, err := isConfigUpdated(c.GetConfig(), newClient.GetConfig())
if err != nil {
return false, err
}

if updated {
*c = *newClient
}
return updated, nil
}

func getContext(c *Client) (cluster string, namespace string, err error) {
raw, err := c.GetConfig().RawConfig()
if err != nil {
return "", "", err
}

currentCtx := raw.Contexts[raw.CurrentContext]
return currentCtx.Cluster, currentCtx.Namespace, nil
}

func isConfigUpdated(oldC, newC clientcmd.ClientConfig) (bool, error) {
oldRaw, err := oldC.RawConfig()
if err != nil {
return false, err
}
newRaw, err := newC.RawConfig()
if err != nil {
return false, err
}
return !reflect.DeepEqual(oldRaw, newRaw), nil
}
32 changes: 24 additions & 8 deletions pkg/watch/watch.go
Expand Up @@ -27,6 +27,7 @@ import (

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/klog"
Expand Down Expand Up @@ -175,7 +176,7 @@ func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ct
}

o.keyWatcher = getKeyWatcher(ctx, out)
return o.eventWatcher(ctx, parameters, out, evaluateFileChanges, processEvents, componentStatus)
return o.eventWatcher(ctx, parameters, out, evaluateFileChanges, o.processEvents, componentStatus)
}

// eventWatcher loops till the context's Done channel indicates it to stop looping, at which point it performs cleanup.
Expand Down Expand Up @@ -424,7 +425,7 @@ func evaluateFileChanges(events []fsnotify.Event, path string, fileIgnores []str
return changedFiles, deletedPaths
}

func processEvents(
func (o *WatchClient) processEvents(
changedFiles, deletedPaths []string,
parameters WatchParameters,
out io.Writer,
Expand Down Expand Up @@ -461,12 +462,23 @@ func processEvents(
return nil, err
}
klog.V(4).Infof("Error from Push: %v", err)
if parameters.WatchFiles {
// Log and output, but intentionally not exiting on error here.
// We don't want to break watch when push failed, it might be fixed with the next change.
fmt.Fprintf(out, "%s - %s\n\n", PushErrorString, err.Error())
// Log and output, but intentionally not exiting on error here.
// We don't want to break watch when push failed, it might be fixed with the next push.
if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) {
fmt.Fprintf(out, "Error connecting to the cluster. Please log in again\n\n")
var refreshed bool
refreshed, err = o.kubeClient.Refresh()
if err != nil {
fmt.Fprintf(out, "Error updating Kubernetes config: %s\n", err)
} else if refreshed {
fmt.Fprintf(out, "Updated Kubernetes config\n")
}
} else {
return nil, err
if parameters.WatchFiles {
fmt.Fprintf(out, "%s - %s\n\n", PushErrorString, err.Error())
} else {
return nil, err
}
}
wait := backoff.Delay()
return &wait, nil
Expand All @@ -484,7 +496,11 @@ func (o *WatchClient) CleanupDevResources(devfileObj parser.DevfileObj, componen
fmt.Fprintln(out, "Cleaning resources, please wait")
isInnerLoopDeployed, resources, err := o.deleteClient.ListResourcesToDeleteFromDevfile(devfileObj, "app", componentName, labels.ComponentDevMode)
if err != nil {
fmt.Fprintf(out, "failed to delete inner loop resources: %v", err)
if kerrors.IsUnauthorized(err) || kerrors.IsForbidden(err) {
fmt.Fprintf(out, "Error connecting to the cluster, the resources were not cleaned up.\nPlease log in again and cleanup the resource with `odo delete component`\n\n")
} else {
fmt.Fprintf(out, "Failed to delete inner loop resources: %v\n", err)
}
return err
}
// if innerloop deployment resource is present, then execute preStop events
Expand Down

0 comments on commit 1d78b85

Please sign in to comment.