diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 6cbf3add312..1b857a0a5a4 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -707,6 +707,8 @@ softmmu someothername somepaththatshouldnevereverexist sourced +spinkube +spinoperator splatform splunk ssd diff --git a/e2e/pages/preferences/kubernetes.ts b/e2e/pages/preferences/kubernetes.ts index 59bc61e6d3b..106cd0e8bb2 100644 --- a/e2e/pages/preferences/kubernetes.ts +++ b/e2e/pages/preferences/kubernetes.ts @@ -6,7 +6,7 @@ export class KubernetesNav { readonly kubernetesToggle: Locator; readonly kubernetesVersion: Locator; readonly kubernetesPort: Locator; - readonly traefikToggle: Locator; + readonly kubernetesOptions: Locator; readonly kubernetesVersionLockedFields: Locator; constructor(page: Page) { @@ -15,7 +15,7 @@ export class KubernetesNav { this.kubernetesToggle = page.locator('[data-test="kubernetesToggle"]'); this.kubernetesVersion = page.locator('[data-test="kubernetesVersion"]'); this.kubernetesPort = page.locator('[data-test="kubernetesPort"]'); - this.traefikToggle = page.locator('[data-test="traefikToggle"]'); + this.kubernetesOptions = page.locator('[data-test="kubernetesOptions"]'); this.kubernetesVersionLockedFields = page.locator('[data-test="kubernetesVersion"] > .select-k8s-version > .icon-lock'); } } diff --git a/e2e/preferences.e2e.spec.ts b/e2e/preferences.e2e.spec.ts index 8213a2c58c8..dbed9d1581f 100644 --- a/e2e/preferences.e2e.spec.ts +++ b/e2e/preferences.e2e.spec.ts @@ -208,7 +208,7 @@ test.describe.serial('Main App Test', () => { await expect(kubernetes.kubernetesToggle).toBeVisible(); await expect(kubernetes.kubernetesVersion).toBeVisible(); await expect(kubernetes.kubernetesPort).toBeVisible(); - await expect(kubernetes.traefikToggle).toBeVisible(); + await expect(kubernetes.kubernetesOptions).toBeVisible(); }); test('should navigate to WSL and render network tab', async() => { diff --git a/pkg/rancher-desktop/assets/dependencies.yaml b/pkg/rancher-desktop/assets/dependencies.yaml index 8b2d25a0cac..32e5ad18e29 100644 --- a/pkg/rancher-desktop/assets/dependencies.yaml +++ b/pkg/rancher-desktop/assets/dependencies.yaml @@ -21,3 +21,5 @@ wix: v3.14.1 hostSwitch: 1.2.2 moproxy: 0.5.0 wasmShims: 0.11.1 +spinOperator: 0.1.0 +certManager: 1.14.4 diff --git a/pkg/rancher-desktop/assets/scripts/install-k3s b/pkg/rancher-desktop/assets/scripts/install-k3s index 39d26240613..e93d435fd96 100755 --- a/pkg/rancher-desktop/assets/scripts/install-k3s +++ b/pkg/rancher-desktop/assets/scripts/install-k3s @@ -41,3 +41,14 @@ fi ln -s -f "${K3S_DIR}/${K3S}" /usr/local/bin/k3s # The file system may be readonly (on macOS) chmod a+x "${K3S_DIR}/${K3S}" || true + +# Make sure any old manifests are removed before configuring k3s again +# We need to create the directory before we run `k3s server ...` because +# we install additional manifests that k3s will install during startup. +MANIFESTS=/var/lib/rancher/k3s/server/manifests +rm -rf "$MANIFESTS" +mkdir -p "$MANIFESTS" + +STATIC=/var/lib/rancher/k3s/server/static/rancher-desktop +rm -rf "$STATIC" +mkdir -p "$STATIC" diff --git a/pkg/rancher-desktop/assets/scripts/spin-operator.helm-chart.yaml b/pkg/rancher-desktop/assets/scripts/spin-operator.helm-chart.yaml new file mode 100644 index 00000000000..1a10fa5fd7a --- /dev/null +++ b/pkg/rancher-desktop/assets/scripts/spin-operator.helm-chart.yaml @@ -0,0 +1,9 @@ +apiVersion: helm.cattle.io/v1 +kind: HelmChart +metadata: + name: spin-operator + namespace: kube-system +spec: + chart: "https://%{KUBERNETES_API}%/static/rancher-desktop/spin-operator.tgz" + targetNamespace: spin-operator + createNamespace: true diff --git a/pkg/rancher-desktop/assets/scripts/spin-operator.shim-executor.yaml b/pkg/rancher-desktop/assets/scripts/spin-operator.shim-executor.yaml new file mode 100644 index 00000000000..357046c1f55 --- /dev/null +++ b/pkg/rancher-desktop/assets/scripts/spin-operator.shim-executor.yaml @@ -0,0 +1,8 @@ +apiVersion: core.spinoperator.dev/v1alpha1 +kind: SpinAppExecutor +metadata: + name: containerd-shim-spin +spec: + createDeployment: true + deploymentConfig: + runtimeClassName: spin diff --git a/pkg/rancher-desktop/assets/specs/command-api.yaml b/pkg/rancher-desktop/assets/specs/command-api.yaml index e0974d64df1..d2cf5ebef6c 100644 --- a/pkg/rancher-desktop/assets/specs/command-api.yaml +++ b/pkg/rancher-desktop/assets/specs/command-api.yaml @@ -652,6 +652,15 @@ components: enabled: type: boolean x-rd-usage: enable support for containerd-wasm shims + kubernetes: + type: object + properties: + options: + type: object + properties: + spinkube: + type: boolean + x-rd-usage: install spin operator virtualMachine: type: object properties: diff --git a/pkg/rancher-desktop/backend/backendHelper.ts b/pkg/rancher-desktop/backend/backendHelper.ts index fbe42543b1d..faa451b347b 100644 --- a/pkg/rancher-desktop/backend/backendHelper.ts +++ b/pkg/rancher-desktop/backend/backendHelper.ts @@ -7,6 +7,8 @@ import yaml from 'yaml'; import INSTALL_CONTAINERD_SHIMS_SCRIPT from '@pkg/assets/scripts/install-containerd-shims'; import CONTAINERD_CONFIG from '@pkg/assets/scripts/k3s-containerd-config.toml'; +import SPIN_OPERATOR_HELM_CHART from '@pkg/assets/scripts/spin-operator.helm-chart.yaml'; +import SPIN_OPERATOR_SHIM_EXECUTOR from '@pkg/assets/scripts/spin-operator.shim-executor.yaml'; import { BackendSettings, VMExecutor } from '@pkg/backend/backend'; import { LockedFieldError } from '@pkg/config/commandLineOptions'; import { ContainerEngine, Settings } from '@pkg/config/settings'; @@ -19,8 +21,18 @@ import { showMessageBox } from '@pkg/window'; const CONTAINERD_CONFIG_TOML = '/etc/containerd/config.toml'; const DOCKER_DAEMON_JSON = '/etc/docker/daemon.json'; -// Don't use `runtimes.yaml` because k3s may overwrite it. -const MANIFESTS_RUNTIMES_YAML = '/var/lib/rancher/k3s/server/manifests/rd-runtimes.yaml'; + +const MANIFEST_DIR = '/var/lib/rancher/k3s/server/manifests'; +// Manifests are applied in sort order, so use a prefix to load them last, in the required sequence. +// Also: don't use `runtimes.yaml` because k3s may overwrite it. +const MANIFEST_RUNTIMES_YAML = `${ MANIFEST_DIR }/z100-rd-runtimes.yaml`; +const MANIFEST_CERT_MANAGER = `${ MANIFEST_DIR }/z110-cert-manager.yaml`; +const MANIFEST_SPIN_OPERATOR_CRDS = `${ MANIFEST_DIR }/z120-spin-operator.crds.yaml`; +const MANIFEST_SPIN_OPERATOR_SHIM_EXECUTOR = `${ MANIFEST_DIR }/z121-spin-operator.shim-executor.yaml`; +const MANIFEST_SPIN_OPERATOR_CHART = `${ MANIFEST_DIR }/z122-spin-operator.chart.yaml`; + +const STATIC_DIR = '/var/lib/rancher/k3s/server/static/rancher-desktop'; +const STATIC_SPIN_OPERATOR_CHART = `${ STATIC_DIR }/spin-operator.tgz`; const console = Logging.kube; @@ -269,21 +281,29 @@ export default class BackendHelper { }); } - await vmx.execCommand({ root: true }, 'mkdir', '-p', path.dirname(MANIFESTS_RUNTIMES_YAML)); // Don't let k3s define runtime classes, only use the ones defined by Rancher Desktop. - await vmx.execCommand({ root: true }, 'touch', `${ path.dirname(MANIFESTS_RUNTIMES_YAML) }/runtimes.yaml.skip`); + await vmx.execCommand({ root: true }, 'touch', `${ MANIFEST_DIR }/runtimes.yaml.skip`); - if (runtimes.length === 0) { - // We delete the manifest file, but we don't actually delete old runtime classes in k3s that no longer exist. - // They won't work though, as the symlinks in /usr/local/bin have been removed. - await vmx.execCommand({ root: true }, 'rm', '-f', MANIFESTS_RUNTIMES_YAML); - } else { + if (runtimes.length > 0) { const manifest = runtimes.map(r => yaml.stringify(r)).join('---\n'); - await vmx.writeFile(MANIFESTS_RUNTIMES_YAML, manifest, 0o644); + await vmx.writeFile(MANIFEST_RUNTIMES_YAML, manifest, 0o644); } } + /** + * Write k3s manifests to install cert-manager and spinkube operator + */ + static async configureSpinOperator(vmx: VMExecutor) { + await Promise.all([ + vmx.copyFileIn(path.join(paths.resources, 'cert-manager.yaml'), MANIFEST_CERT_MANAGER), + vmx.copyFileIn(path.join(paths.resources, 'spin-operator.crds.yaml'), MANIFEST_SPIN_OPERATOR_CRDS), + vmx.copyFileIn(path.join(paths.resources, 'spin-operator.tgz'), STATIC_SPIN_OPERATOR_CHART), + vmx.writeFile(MANIFEST_SPIN_OPERATOR_SHIM_EXECUTOR, SPIN_OPERATOR_SHIM_EXECUTOR, 0o644), + vmx.writeFile(MANIFEST_SPIN_OPERATOR_CHART, SPIN_OPERATOR_HELM_CHART, 0o644), + ]); + } + /** * Install containerd-wasm shims into /usr/local/containerd-shims (and symlinks into /usr/local/bin). */ diff --git a/pkg/rancher-desktop/backend/kube/lima.ts b/pkg/rancher-desktop/backend/kube/lima.ts index ef955781740..bf88156c242 100644 --- a/pkg/rancher-desktop/backend/kube/lima.ts +++ b/pkg/rancher-desktop/backend/kube/lima.ts @@ -126,9 +126,19 @@ export default class LimaKubernetesBackend extends events.EventEmitter implement */ async install(config: BackendSettings, desiredVersion: semver.SemVer, allowSudo: boolean) { await this.progressTracker.action('Installing k3s', 50, async() => { + // installK3s removes old config and makes sure the directories are recreated await this.installK3s(desiredVersion); - await this.writeServiceScript(config, desiredVersion, allowSudo); - await BackendHelper.configureRuntimeClasses(this.vm); + + const promises: Promise[] = []; + + promises.push(this.writeServiceScript(config, desiredVersion, allowSudo)); + if (config.experimental?.containerEngine?.webAssembly?.enabled) { + promises.push(BackendHelper.configureRuntimeClasses(this.vm)); + if (config.experimental?.kubernetes?.options?.spinkube) { + promises.push(BackendHelper.configureSpinOperator(this.vm)); + } + } + await Promise.all(promises); }); this.activeVersion = desiredVersion; @@ -388,6 +398,7 @@ export default class LimaKubernetesBackend extends events.EventEmitter implement 'containerEngine.allowedImages.enabled': undefined, 'containerEngine.name': undefined, 'experimental.containerEngine.webAssembly.enabled': undefined, + 'experimental.kubernetes.options.spinkube': undefined, 'kubernetes.port': undefined, 'kubernetes.enabled': undefined, 'kubernetes.options.traefik': undefined, diff --git a/pkg/rancher-desktop/backend/kube/wsl.ts b/pkg/rancher-desktop/backend/kube/wsl.ts index d12d1d5122e..c401caec7f0 100644 --- a/pkg/rancher-desktop/backend/kube/wsl.ts +++ b/pkg/rancher-desktop/backend/kube/wsl.ts @@ -170,7 +170,16 @@ export default class WSLKubernetesBackend extends events.EventEmitter implements async install(config: BackendSettings, version: semver.SemVer, allowSudo: boolean) { await this.vm.runInstallScript(INSTALL_K3S_SCRIPT, 'install-k3s', version.raw, await this.vm.wslify(path.join(paths.cache, 'k3s'))); - await BackendHelper.configureRuntimeClasses(this.vm); + + if (config.experimental?.containerEngine?.webAssembly?.enabled) { + const promises: Promise[] = []; + + promises.push(BackendHelper.configureRuntimeClasses(this.vm)); + if (config.experimental?.kubernetes?.options?.spinkube) { + promises.push(BackendHelper.configureSpinOperator(this.vm)); + } + await Promise.all(promises); + } } async start(config: BackendSettings, activeVersion: semver.SemVer, kubeClient?: () => KubeClient): Promise { @@ -295,6 +304,7 @@ export default class WSLKubernetesBackend extends events.EventEmitter implements 'containerEngine.allowedImages.enabled': undefined, 'containerEngine.name': undefined, 'experimental.containerEngine.webAssembly.enabled': undefined, + 'experimental.kubernetes.options.spinkube': undefined, 'kubernetes.enabled': undefined, 'kubernetes.ingress.localhostOnly': undefined, 'kubernetes.options.flannel': undefined, diff --git a/pkg/rancher-desktop/backend/lima.ts b/pkg/rancher-desktop/backend/lima.ts index b20f720be83..2fa9677d18c 100644 --- a/pkg/rancher-desktop/backend/lima.ts +++ b/pkg/rancher-desktop/backend/lima.ts @@ -1163,7 +1163,7 @@ export default class LimaBackend extends events.EventEmitter implements VMBacken return; } - const workdir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'rd-vmnet-install')); + const workdir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'rd-vmnet-install-')); const tarPath = path.join(workdir, 'vmnet.tar'); const commands: string[] = []; @@ -1557,8 +1557,6 @@ export default class LimaBackend extends events.EventEmitter implements VMBacken } protected async configureContainerEngine(): Promise { - const workdir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'rd-containerd-install-')); - try { const configureWASM = !!this.cfg?.experimental?.containerEngine?.webAssembly?.enabled; @@ -1572,8 +1570,6 @@ export default class LimaBackend extends events.EventEmitter implements VMBacken await BackendHelper.configureContainerEngine(this, configureWASM); } catch (err) { console.log(`Error trying to start/update containerd: ${ err }: `, err); - } finally { - await fs.promises.rm(workdir, { recursive: true }); } } @@ -1643,8 +1639,19 @@ export default class LimaBackend extends events.EventEmitter implements VMBacken } } - copyFileIn(hostPath: string, vmPath: string): Promise { - return this.lima('copy', hostPath, `${ MACHINE_NAME }:${ vmPath }`); + async copyFileIn(hostPath: string, vmPath: string): Promise { + // TODO This logic is copied from writeFile() above and should be simplified. + const workdir = await fs.promises.mkdtemp(path.join(os.tmpdir(), `rd-${ path.basename(hostPath) }-`)); + const tempPath = `/tmp/${ path.basename(workdir) }.${ path.basename(hostPath) }`; + + try { + await this.lima('copy', hostPath, `${ MACHINE_NAME }:${ tempPath }`); + await this.execCommand('chmod', '644', tempPath); + await this.execCommand({ root: true }, 'mv', tempPath, vmPath); + } finally { + await fs.promises.rm(workdir, { recursive: true }); + await this.execCommand({ root: true }, 'rm', '-f', tempPath); + } } copyFileOut(vmPath: string, hostPath: string): Promise { diff --git a/pkg/rancher-desktop/components/Preferences/BodyKubernetes.vue b/pkg/rancher-desktop/components/Preferences/BodyKubernetes.vue index caac352dcd0..9cd7773d11e 100644 --- a/pkg/rancher-desktop/components/Preferences/BodyKubernetes.vue +++ b/pkg/rancher-desktop/components/Preferences/BodyKubernetes.vue @@ -162,8 +162,8 @@ export default Vue.extend({ /> + + diff --git a/pkg/rancher-desktop/components/form/RdCheckbox.vue b/pkg/rancher-desktop/components/form/RdCheckbox.vue index 102494f651d..8c86824315f 100644 --- a/pkg/rancher-desktop/components/form/RdCheckbox.vue +++ b/pkg/rancher-desktop/components/form/RdCheckbox.vue @@ -2,11 +2,17 @@ import { Checkbox } from '@rancher/components'; import Vue from 'vue'; +import TooltipIcon from '@pkg/components/form/TooltipIcon.vue'; + export default Vue.extend({ name: 'rd-checkbox', - components: { Checkbox }, + components: { TooltipIcon, Checkbox }, inheritAttrs: false, props: { + isExperimental: { + type: Boolean, + default: false, + }, isLocked: { type: Boolean, default: false, @@ -39,25 +45,31 @@ export default Vue.extend({ v-on="$listeners" >