Skip to content

Commit

Permalink
Fix fallback when no Kubernetes versions are available
Browse files Browse the repository at this point in the history
If Rancher Desktop was previously started with a version, but the user
wipes the `k3s-versions.json` cache and we fail to download a new copy,
handle the error and disable Kubernetes instead of throwing an error.  To
make sure the user is aware of the issue, also pop a diagnostic to describe
the situation.

Signed-off-by: Mark Yen <[email protected]>
  • Loading branch information
mook-as committed Sep 19, 2024
1 parent b5a5385 commit 3766dc2
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 23 deletions.
9 changes: 8 additions & 1 deletion pkg/rancher-desktop/backend/k3sHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,12 @@ export default class K3sHelper extends events.EventEmitter {

return;
}
await this.updateCache();
try {
await this.updateCache();
} catch (ex) {
console.log(`Ignoring failure to get initial versions list: ${ ex }`);
// At this point this.versions is still empty.
}
})();
}
this.versionFromChannel = {};
Expand Down Expand Up @@ -561,6 +566,8 @@ export default class K3sHelper extends events.EventEmitter {

/**
* The versions that are available to install.
* @note The list will be empty if the machine is offline and we have no
* cached versions.
*/
get availableVersions(): Promise<SemanticVersionEntry[]> {
return (async() => {
Expand Down
18 changes: 11 additions & 7 deletions pkg/rancher-desktop/backend/kube/lima.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default class LimaKubernetesBackend extends events.EventEmitter implement
const result = await showMessageBox(options, true);

if (result.response !== 0) {
return [undefined, false];
return [undefined, true];
}
}
console.log(`Going with alternative version ${ newVersion.raw }`);
Expand Down Expand Up @@ -324,20 +324,24 @@ export default class LimaKubernetesBackend extends events.EventEmitter implement
protected get desiredVersion(): Promise<semver.SemVer | undefined> {
return (async() => {
let availableVersions: SemanticVersionEntry[];
let available = true;

try {
availableVersions = await this.k3sHelper.availableVersions;

return await BackendHelper.getDesiredVersion(
this.cfg as BackendSettings,
availableVersions,
this.vm.noModalDialogs,
this.vm.writeSetting.bind(this.vm));
} catch (ex) {
console.error(`Could not get desired version: ${ ex }`);
available = false;

return undefined;
} finally {
mainEvents.emit('diagnostics-event', { id: 'kube-versions-available', available });
}

return await BackendHelper.getDesiredVersion(
this.cfg as BackendSettings,
availableVersions,
this.vm.noModalDialogs,
this.vm.writeSetting.bind(this.vm));
})();
}

Expand Down
16 changes: 10 additions & 6 deletions pkg/rancher-desktop/backend/kube/wsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,24 @@ export default class WSLKubernetesBackend extends events.EventEmitter implements
protected get desiredVersion(): Promise<semver.SemVer | undefined> {
return (async() => {
let availableVersions: SemanticVersionEntry[];
let available = true;

try {
availableVersions = await this.k3sHelper.availableVersions;

return await BackendHelper.getDesiredVersion(
this.cfg as BackendSettings,
availableVersions,
this.vm.noModalDialogs,
this.vm.writeSetting.bind(this.vm));
} catch (ex) {
console.error(`Could not get desired version: ${ ex }`);
available = false;

return undefined;
} finally {
mainEvents.emit('diagnostics-event', { id: 'kube-versions-available', available });
}

return await BackendHelper.getDesiredVersion(
this.cfg as BackendSettings,
availableVersions,
this.vm.noModalDialogs,
this.vm.writeSetting.bind(this.vm));
})();
}

Expand Down
17 changes: 13 additions & 4 deletions pkg/rancher-desktop/backend/lima.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1813,14 +1813,23 @@ export default class LimaBackend extends events.EventEmitter implements VMBacken
// Start the VM; if it's already running, this does nothing.
await this.startVM();

// Clear the diagnostic about not having Kubernetes versions
mainEvents.emit('diagnostics-event', { id: 'kube-versions-available', available: true });

if (config.kubernetes.enabled) {
[kubernetesVersion, isDowngrade] = await this.kubeBackend.download(config);

if (typeof (kubernetesVersion) === 'undefined') {
// The desired version was unavailable, and the user declined a downgrade.
await this.setState(State.ERROR);
if (kubernetesVersion === undefined) {
if (isDowngrade) {
// The desired version was unavailable, and the user declined a downgrade.
await this.setState(State.ERROR);

return;
return;
}
// The desired version was unavailable, and we couldn't find a fallback.
// Notify the user, and turn off Kubernetes.
mainEvents.emit('diagnostics-event', { id: 'kube-versions-available', available: false });
this.writeSetting({ kubernetes: { enabled: false } });
}
}

Expand Down
20 changes: 15 additions & 5 deletions pkg/rancher-desktop/backend/wsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,7 @@ export default class WSLBackend extends events.EventEmitter implements VMBackend
const config = this.cfg = _.defaultsDeep(clone(config_),
{ containerEngine: { name: ContainerEngine.NONE } });
let kubernetesVersion: semver.SemVer | undefined;
let isDowngrade = false;

await this.setState(State.STARTING);
this.currentAction = Action.STARTING;
Expand All @@ -1203,16 +1204,25 @@ export default class WSLBackend extends events.EventEmitter implements VMBackend

if (config.kubernetes.enabled) {
prepActions.push((async() => {
[kubernetesVersion] = await this.kubeBackend.download(config);
[kubernetesVersion, isDowngrade] = await this.kubeBackend.download(config);
})());
}

// Clear the diagnostic about not having Kubernetes versions
mainEvents.emit('diagnostics-event', { id: 'kube-versions-available', available: true });

await this.progressTracker.action('Preparing to start', 0, Promise.all(prepActions));
if (config.kubernetes.enabled && typeof (kubernetesVersion) === 'undefined') {
// The desired version was unavailable, and the user declined a downgrade.
this.setState(State.ERROR);
if (config.kubernetes.enabled && kubernetesVersion === undefined) {
if (isDowngrade) {
// The desired version was unavailable, and the user declined a downgrade.
this.setState(State.ERROR);

return;
return;
}
// The desired version was unavailable, and we couldn't find a fallback.
// Notify the user, and turn off Kubernetes.
mainEvents.emit('diagnostics-event', { id: 'kube-versions-available', available: false });
this.writeSetting({ kubernetes: { enabled: false } });
}
if (this.currentAction !== Action.STARTING) {
// User aborted before we finished
Expand Down
1 change: 1 addition & 0 deletions pkg/rancher-desktop/main/diagnostics/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class DiagnosticsManager {
import('./dockerCliSymlinks'),
import('./kubeConfigSymlink'),
import('./kubeContext'),
import('./kubeVersionsAvailable'),
import('./limaDarwin'),
import('./mockForScreenshots'),
import('./pathManagement'),
Expand Down
44 changes: 44 additions & 0 deletions pkg/rancher-desktop/main/diagnostics/kubeVersionsAvailable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import mainEvents from '../mainEvents';
import { DiagnosticsCategory, DiagnosticsChecker, DiagnosticsCheckerResult } from './types';

let kubeVersionsAvailable = true;

mainEvents.on('diagnostics-event', (payload) => {
if (payload.id !== 'kube-versions-available') {
return;
}
kubeVersionsAvailable = payload.available;
mainEvents.invoke('diagnostics-trigger', instance.id);
});

/**
* KubeVersionsAvailable is a diagnostic that will be emitted when all of the
* following are met:
* - Kubernetes was configured to be enabled
* - The selected Kubernetes version is unavailable (e.g. user is offline)
* Once the diagnostic is triggered, it stays on until the backend is restarted.
*/
class KubeVersionsAvailable implements DiagnosticsChecker {
readonly id = 'KUBE_VERSIONS_AVAILABLE';
readonly category = DiagnosticsCategory.Kubernetes;
applicable(): Promise<boolean> {
return Promise.resolve(true);
}

check(): Promise<DiagnosticsCheckerResult> {
const description = [
'There are no issues with Kubernetes versions',
'Kubernetes has been disabled due to issues with fetching Kubernetes versions',
][kubeVersionsAvailable ? 0 : 1];

return Promise.resolve({
passed: kubeVersionsAvailable,
description,
fixes: [{ description: 'Check your network connection to update.k3s.io' }],
});
}
}

const instance = new KubeVersionsAvailable();

export default instance;
1 change: 1 addition & 0 deletions pkg/rancher-desktop/main/mainEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ interface MainEventNames {
* 'diagnostics-event' event.
*/
type DiagnosticsEventPayload =
{ id: 'kube-versions-available', available: boolean } |
{ id: 'path-management', fileName: string; error: Error | undefined };

/**
Expand Down

0 comments on commit 3766dc2

Please sign in to comment.