Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix fallback when Kubernetes versions list is unavailable #7515

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
16 changes: 12 additions & 4 deletions pkg/rancher-desktop/integrations/pathManagerImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,13 @@ export class RcFilePathManager implements PathManager {
protected async manageLinesInFile(fileName: string, filePath: string, lines: string[], desiredPresent: boolean) {
try {
await manageLinesInFile(filePath, lines, desiredPresent);
mainEvents.emit('diagnostics-event', 'path-management', { fileName, error: undefined });
mainEvents.emit('diagnostics-event', {
id: 'path-management', fileName, error: undefined,
});
} catch (error: any) {
mainEvents.emit('diagnostics-event', 'path-management', { fileName, error });
mainEvents.emit('diagnostics-event', {
id: 'path-management', fileName, error,
});
throw error;
}
}
Expand Down Expand Up @@ -96,10 +100,14 @@ export class RcFilePathManager implements PathManager {
} catch (error: any) {
if (error.code === 'ENOENT') {
// If the file does not exist, it is not an error.
mainEvents.emit('diagnostics-event', 'path-management', { fileName, error: undefined });
mainEvents.emit('diagnostics-event', {
id: 'path-management', fileName, error: undefined,
});
continue;
}
mainEvents.emit('diagnostics-event', 'path-management', { fileName, error });
mainEvents.emit('diagnostics-event', {
id: 'path-management', fileName, error,
});
throw error;
}
await this.manageLinesInFile(fileName, filePath, [pathLine], !linesAdded);
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;
7 changes: 3 additions & 4 deletions pkg/rancher-desktop/main/diagnostics/pathManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ const CheckPathManagement: DiagnosticsChecker = {
},
};

mainEvents.on('diagnostics-event', (id, state) => {
if (id !== 'path-management') {
mainEvents.on('diagnostics-event', (payload) => {
if (payload.id !== 'path-management') {
return;
}
const typedState: { fileName: string, error: Error | undefined } = state;
const { fileName, error } = typedState;
const { fileName, error } = payload;

cachedResults[fileName] = {
description: error?.message ?? error?.toString() ?? `Unknown error managing ${ fileName }`,
Expand Down
8 changes: 4 additions & 4 deletions pkg/rancher-desktop/main/mainEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ interface MainEventNames {
* @param id The diagnostic identifier.
* @param state The new state for the diagnostic.
*/
'diagnostics-event'<K extends keyof DiagnosticsEventPayload>(id: K, state: DiagnosticsEventPayload[K]): void;
'diagnostics-event'(payload: DiagnosticsEventPayload): void;

/**
* Emitted when an extension is uninstalled via the extension manager.
Expand Down Expand Up @@ -148,9 +148,9 @@ interface MainEventNames {
* DiagnosticsEventPayload defines the data that will be passed on a
* 'diagnostics-event' event.
*/
type DiagnosticsEventPayload = {
'path-management': { fileName: string; error: Error | undefined };
};
type DiagnosticsEventPayload =
{ id: 'kube-versions-available', available: boolean } |
{ id: 'path-management', fileName: string; error: Error | undefined };

/**
* Helper type definition to check if the given event name is a handler (i.e.
Expand Down
Loading