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

Handle invalid k8s versions at startup #5199

Merged
merged 7 commits into from
Jul 24, 2023
Merged
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
28 changes: 28 additions & 0 deletions bats/tests/k8s/specify-invalid-k8s-version.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
load '../helpers/load'

@test 'factory reset' {
factory_reset
}

@test 'invalid k8s version' {
start_kubernetes --kubernetes.version=moose
wait_for_container_engine
# Can't use wait_for_api_server because it hard-wires a valid k8s version and we're specifying an invalid one here.
# and we're specifying an invalid one here
local timeout="$(($(date +%s) + 10 * 60))"
until kubectl get --raw /readyz &>/dev/null; do
assert [ "$(date +%s)" -lt "$timeout" ]
sleep 1
done
# No way there's a race-condition here.
# The version was checked and written to the log file before starting k8s,
# and we have to wait a few minutes before k8s is ready and we're at the next line.
assert_file_contains "$PATH_LOGS/kube.log" "Requested kubernetes version 'moose' is not a valid version. Falling back to the most recent stable version of"
}

# on macOS it still hangs without this
@test 'shutdown' {
if is_macos; then
rdctl shutdown
fi
}
68 changes: 68 additions & 0 deletions pkg/rancher-desktop/backend/backendHelper.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import Electron from 'electron';
import merge from 'lodash/merge';
import semver from 'semver';

import { BackendSettings } from '@pkg/backend/backend';
import { ContainerEngine } from '@pkg/config/settings';
import Logging from '@pkg/utils/logging';
import { showMessageBox } from '@pkg/window';

const console = Logging.kube;

export default class BackendHelper {
/**
Expand Down Expand Up @@ -116,4 +121,67 @@ export default class BackendHelper {
static requiresCRIDockerd(engineName: string, kubeVersion: string | semver.SemVer): boolean {
return engineName === ContainerEngine.MOBY && semver.gte(kubeVersion, '1.24.1') && semver.lte(kubeVersion, '1.24.3');
}

/**
* Validate the cfg.kubernetes.version string
* If it's valid and available, use it.
* Otherwise fall back to the first (recommended) available version.
*/
static async getDesiredVersion(currentConfigVersionString: string|undefined, availableVersions: semver.SemVer[], noModalDialogs: boolean, settingsWriter: (_: any) => void): Promise<semver.SemVer> {
let storedVersion: semver.SemVer|null;
let matchedVersion: semver.SemVer|undefined;
const invalidK8sVersionMainMessage = `Requested kubernetes version '${ currentConfigVersionString }' is not a valid version.`;

// If we're here either there's no existing cfg.k8s.version, or it isn't valid
if (!availableVersions.length) {
if (currentConfigVersionString) {
console.log(invalidK8sVersionMainMessage);
} else {
console.log('Internal error: no available kubernetes versions found.');
}
throw new Error('No kubernetes version available.');
}

if (currentConfigVersionString) {
storedVersion = semver.parse(currentConfigVersionString);
if (storedVersion) {
matchedVersion = availableVersions.find((v) => {
try {
return v.compare(storedVersion as semver.SemVer) === 0;
} catch (err: any) {
console.error(`Can't compare versions ${ storedVersion } and ${ v }: `, err);
if (!(err instanceof TypeError)) {
return false;
}
// We haven't seen a non-TypeError exception here, but it would be worthwhile to have it reported.
// This throw will cause the exception to appear in a non-fatal error reporting dialog box.
throw err;
}
});
if (matchedVersion) {
return matchedVersion;
}
}
const message = invalidK8sVersionMainMessage;
const detail = `Falling back to the most recent stable version of ${ availableVersions[0] }`;

if (noModalDialogs) {
console.log(`${ message } ${ detail }`);
} else {
const options: Electron.MessageBoxOptions = {
message,
detail,
type: 'warning',
buttons: ['OK'],
title: 'Invalid Kubernetes Version',
};

await showMessageBox(options, true);
}
}
// No (valid) stored version; save the default one.
settingsWriter({ kubernetes: { version: availableVersions[0].version } });

return availableVersions[0];
}
}
22 changes: 1 addition & 21 deletions pkg/rancher-desktop/backend/kube/lima.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,28 +298,8 @@ export default class LimaKubernetesBackend extends events.EventEmitter implement
protected get desiredVersion(): Promise<semver.SemVer> {
return (async() => {
const availableVersions = (await this.k3sHelper.availableVersions).map(v => v.version);
const storedVersion = semver.parse(this.cfg?.kubernetes?.version);
const version = storedVersion ?? availableVersions[0];

if (!version) {
throw new Error('No version available');
}

const matchedVersion = availableVersions.find(v => v.compare(version) === 0);

if (matchedVersion) {
if (!storedVersion) {
// No (valid) stored version; save the selected one.
this.vm.writeSetting({ kubernetes: { version: matchedVersion.version } });
}

return matchedVersion;
}

console.error(`Could not use saved version ${ version.raw }, not in ${ availableVersions }`);
this.vm.writeSetting({ kubernetes: { version: availableVersions[0].version } });

return availableVersions[0];
return await BackendHelper.getDesiredVersion(this.cfg?.kubernetes?.version, availableVersions, this.vm.noModalDialogs, this.vm.writeSetting.bind(this.vm));
})();
}

Expand Down
28 changes: 6 additions & 22 deletions pkg/rancher-desktop/backend/kube/wsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ import WSLBackend, { Action } from '../wsl';

import INSTALL_K3S_SCRIPT from '@pkg/assets/scripts/install-k3s';
import { BackendSettings, RestartReasons } from '@pkg/backend/backend';
import BackendHelper from '@pkg/backend/backendHelper';
import * as K8s from '@pkg/backend/k8s';
import { ContainerEngine } from '@pkg/config/settings';
import mainEvents from '@pkg/main/mainEvents';
import { checkConnectivity } from '@pkg/main/networking';
import Logging from '@pkg/utils/logging';
import paths from '@pkg/utils/paths';
import { RecursivePartial } from '@pkg/utils/typeUtils';
import { showMessageBox } from '@pkg/window';

const console = Logging.kube;

export default class WSLKubernetesBackend extends events.EventEmitter implements K8s.KubernetesBackend {
constructor(vm: WSLBackend) {
super();
Expand Down Expand Up @@ -67,31 +71,11 @@ export default class WSLKubernetesBackend extends events.EventEmitter implements
return await K3sHelper.cachedVersionsOnly();
}

get desiredVersion(): Promise<semver.SemVer> {
protected get desiredVersion(): Promise<semver.SemVer> {
return (async() => {
const availableVersions = (await this.k3sHelper.availableVersions).map(v => v.version);
const storedVersion = semver.parse(this.cfg?.kubernetes?.version);
const version = storedVersion ?? availableVersions[0];

if (!version) {
throw new Error('No version available');
}

const matchedVersion = availableVersions.find(v => v.compare(version) === 0);

if (matchedVersion) {
if (!storedVersion) {
// No (valid) stored version; save the selected one.
this.vm.writeSetting({ kubernetes: { version: matchedVersion.version } });
}

return matchedVersion;
}

console.error(`Could not use saved version ${ version.raw }, not in ${ availableVersions }`);
this.vm.writeSetting({ kubernetes: { version: availableVersions[0].version } });

return availableVersions[0];
return await BackendHelper.getDesiredVersion(this.cfg?.kubernetes?.version, availableVersions, this.vm.noModalDialogs, this.vm.writeSetting.bind(this.vm));
})();
}

Expand Down
Loading