Skip to content
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
1 change: 1 addition & 0 deletions e2e/backend.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ test.describe.serial('KubernetesBackend', () => {
if (['darwin', 'linux'].includes(process.platform)) {
// Lima additions to expectedDefinition
expectedDefinition['application.adminAccess'] = false;
expectedDefinition['experimental.virtualMachine.diskSize'] = false;
expectedDefinition['virtualMachine.numberCPUs'] = false;
expectedDefinition['virtualMachine.memoryInGB'] = false;
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/rancher-desktop/assets/specs/command-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,12 @@ components:
virtualMachine:
type: object
properties:
diskSize:
type: string
x-rd-platforms: [darwin, linux]
x-rd-usage: >-
desired size of the disk; changing this setting will not
shrink existing disks (example: 10GiB)
mount:
type: object
x-rd-platforms: [darwin, linux]
Expand Down
10 changes: 6 additions & 4 deletions pkg/rancher-desktop/backend/lima.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export interface LimaConfiguration {
}[];
cpus?: number;
memory?: number;
disk?: number;
disk?: string;
mounts?: LimaMount[];
mountType: 'reverse-sshfs' | '9p' | 'virtiofs';
ssh: {
Expand Down Expand Up @@ -629,6 +629,7 @@ export default class LimaBackend extends events.EventEmitter implements VMBacken
}],
cpus: this.cfg?.virtualMachine.numberCPUs || 4,
memory: (this.cfg?.virtualMachine.memoryInGB || 4) * 1024 * 1024 * 1024,
disk: this.cfg?.experimental.virtualMachine.diskSize ?? '100GiB',
mounts: this.getMounts(),
mountType: this.cfg?.virtualMachine.mount.type,
ssh: { localPort: await this.sshPort },
Expand Down Expand Up @@ -2161,15 +2162,16 @@ CREDFWD_URL='http://${ SLIRP.HOST_GATEWAY }:${ stateInfo.port }'
'experimental.virtualMachine.mount.9p.msizeInKib': undefined,
'experimental.virtualMachine.mount.9p.protocolVersion': undefined,
'experimental.virtualMachine.mount.9p.securityModel': undefined,
'virtualMachine.mount.type': undefined,
'experimental.virtualMachine.sshPortForwarder': undefined,
'virtualMachine.mount.type': undefined,
'virtualMachine.type': undefined,
'virtualMachine.useRosetta': undefined,
}));
if (limaConfig) {
Object.assign(reasons, await this.kubeBackend.requiresRestartReasons(this.cfg, cfg, {
'virtualMachine.memoryInGB': { current: (limaConfig.memory ?? 4 * GiB) / GiB },
'virtualMachine.numberCPUs': { current: limaConfig.cpus ?? 2 },
'experimental.virtualMachine.diskSize': { current: limaConfig.disk ?? '100GiB' },
'virtualMachine.memoryInGB': { current: (limaConfig.memory ?? 4 * GiB) / GiB },
'virtualMachine.numberCPUs': { current: limaConfig.cpus ?? 2 },
}));
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/rancher-desktop/config/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ export const defaultSettings = {
/** can only be enabled if containerEngine.webAssembly.enabled is true */
kubernetes: { options: { spinkube: false } },
virtualMachine: {
mount: {
diskSize: '100GiB',
mount: {
'9p': {
securityModel: SecurityModel.NONE,
protocolVersion: ProtocolVersion.NINEP2000_L,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('SettingsValidator', () => {
['containerEngine', 'allowedImages', 'locked'],
['containerEngine', 'name'],
['experimental', 'kubernetes', 'options', 'spinkube'],
['experimental', 'virtualMachine', 'diskSize'], // Special parsing.
['experimental', 'virtualMachine', 'mount', '9p', 'cacheMode'],
['experimental', 'virtualMachine', 'mount', '9p', 'msizeInKib'],
['experimental', 'virtualMachine', 'mount', '9p', 'protocolVersion'],
Expand Down Expand Up @@ -708,6 +709,44 @@ describe('SettingsValidator', () => {
});
});

describe('experimental.virtual-machine.disk-size', () => {
describe('parsing inputs', () => {
test.each<['accept' | 'reject', string]>([
['accept', '100GiB'], // default value
['accept', '1.23GiB'], // with fractional number
['reject', '1.GiB'], // Trailing dot on number
['accept', '100 GiB'], // with space between number and unit
['accept', '1.23 GiB'], // fractional number with space
['accept', '1234'], // no units
['accept', '123k'], // kilobytes, no byte suffix
['accept', '123mb'], // megabytes, with b suffix
['accept', '1234 tib'], // terabytes (spellcheck-ignore-line)
['reject', '1234 stuff'], // extra trailing text
])('should %s %s', (outcome, input) => {
const errorMessage = `Invalid value for "experimental.virtualMachine.diskSize": <${ JSON.stringify(input) }>`;
const expectedErrors = outcome === 'accept' ? [] : [errorMessage];
const oldConfig = _.set(_.cloneDeep(cfg), 'experimental.virtualMachine.diskSize', 1);
const [, errors] = subject.validateSettings(oldConfig, { experimental: { virtualMachine: { diskSize: input } } });

expect(errors).toEqual(expectedErrors);
});
});
describe('rejecting smaller values', () => {
test.each<['accept' | 'reject', string, string]>([
['accept', '100GiB', '100GiB'],
['accept', '100GiB', '1TB'],
['reject', '1G', '100M'],
])('should %s going from %s to %s', (outcome, currentValue, desiredValue) => {
const errorMessage = `Cannot decrease "experimental.virtualMachine.diskSize" from ${ currentValue } to ${ desiredValue }`;
const expectedErrors = outcome === 'accept' ? [] : [errorMessage];
const oldConfig = _.set(_.cloneDeep(cfg), 'experimental.virtualMachine.diskSize', currentValue);
const [, errors] = subject.validateSettings(oldConfig, { experimental: { virtualMachine: { diskSize: desiredValue } } });

expect(errors).toEqual(expectedErrors);
});
});
});

it('should complain about unchangeable fields', () => {
const unchangeableFieldsAndValues = { version: settings.CURRENT_SETTINGS_VERSION + 1 };

Expand Down
43 changes: 42 additions & 1 deletion pkg/rancher-desktop/main/commandServer/settingsValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ export default class SettingsValidator {
containerEngine: { webAssembly: { enabled: this.checkBoolean } },
kubernetes: { options: { spinkube: this.checkMulti(this.checkBoolean, this.checkSpinkube) } },
virtualMachine: {
mount: {
diskSize: this.checkLima(this.checkByteUnits),
mount: {
'9p': {
securityModel: this.checkLima(this.check9P(this.checkEnum(...Object.values(SecurityModel)))),
protocolVersion: this.checkLima(this.check9P(this.checkEnum(...Object.values(ProtocolVersion)))),
Expand Down Expand Up @@ -491,6 +492,46 @@ export default class SettingsValidator {
return currentValue !== desiredValue;
}

/**
* Parse a string representing a number of bytes into a number, in a way that
* is compatible with `github.com/docker/go-units`.
* @param input The string to parse.
* @returns The parsed number, or `undefined` if the input is not valid.
*/
protected parseByteUnits(input: string): number | undefined {
const expression = /^(\d+(?:\.\d+)?) ?([kmgtpezy]?)(i?b)?$/i; // spellcheck-ignore-line
const prefix = ['', 'k', 'm', 'g', 't', 'p', 'e', 'z', 'y'];
const match = expression.exec(input);

if (!match) {
return undefined;
}

const [, number, scale, unit] = match;
const base = unit?.startsWith('i') ? 1_024 : 1_000;
const exponent = prefix.indexOf(scale.toLowerCase() ?? '');

return parseFloat(number) * base ** exponent;
}

/**
* Check that the setting is a valid number of bytes, per `github.com/docker/go-units`.
*/
protected checkByteUnits(_: Settings, currentValue: string, desiredValue: string, errors: string[], fqname: string): boolean {
const current = this.parseByteUnits(currentValue);
const desired = this.parseByteUnits(desiredValue);

if (typeof desired === 'undefined') {
errors.push(this.invalidSettingMessage(fqname, desiredValue));
} else if (typeof current !== 'undefined' && desired < current) {
errors.push(`Cannot decrease "${ fqname }" from ${ currentValue } to ${ desiredValue }`);
} else {
return currentValue !== desiredValue;
}

return false;
}

protected checkKubernetesVersion(mergedSettings: Settings, currentValue: string, desiredVersion: string, errors: string[], _: string): boolean {
/**
* desiredVersion can be an empty string when Kubernetes is disabled, but otherwise it must be a valid version.
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 @@ -59,6 +59,7 @@ export class DiagnosticsManager {
import('./kubeContext'),
import('./kubeVersionsAvailable'),
import('./limaDarwin'),
import('./limaOverrides'),
import('./mockForScreenshots'),
import('./pathManagement'),
import('./rdBinInShell'),
Expand Down
71 changes: 71 additions & 0 deletions pkg/rancher-desktop/main/diagnostics/limaOverrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import fs from 'node:fs';
import path from 'node:path';

import yaml from 'yaml';

import { DiagnosticsCategory, DiagnosticsChecker, DiagnosticsCheckerSingleResult } from './types';

import paths from '@pkg/utils/paths';

/**
* Check for things in the user's Lima overrides file. We never create the file
* ourselves, but the user may manually create it to adjust how Lima runs; it
* may end up conflicting with what we attempt to do.
*/
const CheckLimaOverrides: DiagnosticsChecker = {
id: 'LIMA_OVERRIDES',
category: DiagnosticsCategory.ContainerEngine,
applicable() {
return Promise.resolve(process.platform !== 'win32');
},
async check() {
const overridePath = path.join(paths.lima, '_config', 'override.yaml');
const checkers = {
/**
* Check if the user has an override for the lima disk size. We have built-in
* support for the feature now, and overrides would cause our settings to be
* ignored.
*/
DISK_SIZE: (override) => {
if ('disk' in override) {
return {
description: `Disk overrides are set in Lima override file \`${ overridePath }\``,
passed: false,
fixes: [{
description: `Remove Lima override file \`${ overridePath }\``,
}],
};
}
return {
description: `Disk size override not specified in Lima override file \`${ overridePath }\``,
passed: true,
fixes: [],
};
},
} satisfies Record<string, (override: any) => Omit<DiagnosticsCheckerSingleResult, 'id'>>;
const override = await (async function() {
try {
return yaml.parse(await fs.promises.readFile(overridePath, 'utf-8'));
} catch {
return undefined;
}
})();

if (!override || typeof override !== 'object') {
// Override file does not exist, or is not valid YAML
return Object.keys(checkers).map(id => ({
id,
description: `Override file \`${ overridePath }\` not loaded`,
passed: true,
fixes: [],
}));
}

return Object.entries(checkers).map(([id, checker]) => ({
id,
...checker(override),
}));
},
};

export default CheckLimaOverrides;
4 changes: 2 additions & 2 deletions pkg/rancher-desktop/utils/commandLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export default function getCommandLineArgs(): string[] {

return idx > -1 ? process.argv.slice(idx + 1) : [];
} else if ((process.env.NODE_ENV ?? '').startsWith('dev')) {
// Are we running in dev mode?
const idx = process.argv.findIndex(arg => /[\\\/]dev.ts$/.test(arg));
// If we're running in dev mode, look for the injected marker.
const idx = process.argv.indexOf('## Rancher Desktop Command Line Marker ##');

return idx >= 0 ? process.argv.slice(idx + 1) : [];
}
Expand Down
1 change: 1 addition & 0 deletions scripts/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class DevRunner extends events.EventEmitter {
...argv.filter(x => x.startsWith('--inspect')),
buildUtils.rootDir,
this.rendererPort.toString(),
'## Rancher Desktop Command Line Marker ##',
...argv.filter(x => !x.startsWith('--inspect')),
);
this.#mainProcess.on('exit', (code: number, signal: string) => {
Expand Down
Loading