diff --git a/README.md b/README.md index 8f67b71d2..7657c26d5 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,16 @@ An example `finch.yaml` looks like this: cpus: 4 # memory: the amount of memory to dedicate to the virtual machine. (required) memory: 4GiB +# snapshotters: the snapshotters a user wants to use (the first snapshotter will be set as the default snapshotter) +# Supported Snapshotters List: +# - soci https://github.com/awslabs/soci-snapshotter/tree/main +# Once the option has been set the snapshotters will be installed on either finch vm init or finch vm start. +# The snapshotters binary will be downloaded on the virtual machine and will be configured and ready for use. +# To change your default snpahotter back to overlayfs, simply remove the snapshotters value from finch.yaml or set snapshotters to `overlayfs` +# To completely remove the snapshotters' binaries, shell into your VM and remove /usr/local/bin/{snapshotter binary} +# and remove the snapshotter configuration in the containerd config file found at /etc/containerd/config.toml +snapshotters: + - soci # creds_helpers: a list of credential helpers that will be installed and configured automatically. # Supported Credential Helpers List: # - ecr-login https://github.com/awslabs/amazon-ecr-credential-helper diff --git a/e2e/vm/soci_test.go b/e2e/vm/soci_test.go new file mode 100644 index 000000000..ada22cb47 --- /dev/null +++ b/e2e/vm/soci_test.go @@ -0,0 +1,76 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package vm + +import ( + "os" + "path/filepath" + "strings" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "github.com/runfinch/common-tests/command" + "github.com/runfinch/common-tests/option" +) + +const ( + ffmpegSociImage = "public.ecr.aws/soci-workshop-examples/ffmpeg:latest" + sociMountString = "fuse.rawBridge" +) + +var testSoci = func(o *option.Option, installed bool) { + ginkgo.Describe("SOCI", func() { + var limactlO *option.Option + var limaHomePathEnv string + var wd string + var err error + + ginkgo.BeforeEach(func() { + wd, err = os.Getwd() + gomega.Expect(err).Should(gomega.BeNil()) + limaHomePathEnv = "LIMA_HOME=" + filepath.Join(wd, "../../_output/lima/data") + limactlO, err = option.New([]string{filepath.Join(wd, "../../_output/lima/bin/limactl")}, + option.Env([]string{limaHomePathEnv})) + gomega.Expect(err).Should(gomega.BeNil()) + }) + + ginkgo.It("finch pull should have same mounts as nerdctl pull with SOCI", func() { + resetVM(o, installed) + resetDisks(o, installed) + writeFile(finchConfigFilePath, []byte("cpus: 6\nmemory: 4GiB\nsnapshotters:\n "+ + "- soci\nvmType: qemu\nrosetta: false")) + command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(600).Run() + command.New(o, "pull", ffmpegSociImage).WithTimeoutInSeconds(30).Run() + finchPullMounts := countMounts(limactlO) + command.Run(o, "rmi", "-f", ffmpegSociImage) + command.New(limactlO, "shell", "finch", + "sudo", "nerdctl", "--snapshotter=soci", "pull", ffmpegSociImage).WithTimeoutInSeconds(30).Run() + nerdctlPullMounts := countMounts(limactlO) + command.Run(o, "rmi", "-f", ffmpegSociImage) + gomega.Expect(finchPullMounts).Should(gomega.Equal(nerdctlPullMounts)) + }) + + ginkgo.It("finch run should have same mounts as nerdctl run with SOCI", func() { + resetVM(o, installed) + resetDisks(o, installed) + writeFile(finchConfigFilePath, []byte("cpus: 6\nmemory: 4GiB\nsnapshotters:\n "+ + "- soci\nvmType: qemu\nrosetta: false")) + command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(600).Run() + command.New(o, "run", ffmpegSociImage).WithTimeoutInSeconds(30).Run() + finchPullMounts := countMounts(limactlO) + command.Run(o, "rmi", "-f", ffmpegSociImage) + command.New(limactlO, "shell", "finch", + "sudo", "nerdctl", "--snapshotter=soci", "run", ffmpegSociImage).WithTimeoutInSeconds(30).Run() + nerdctlPullMounts := countMounts(limactlO) + command.Run(o, "rmi", "-f", ffmpegSociImage) + gomega.Expect(finchPullMounts).Should(gomega.Equal(nerdctlPullMounts)) + }) + }) +} + +// counts the mounts present in the VM after pulling an image. +func countMounts(limactlO *option.Option) int { + mountOutput := command.StdoutStr(limactlO, "shell", "finch", "mount") + return strings.Count(mountOutput, sociMountString) +} diff --git a/e2e/vm/vm_test.go b/e2e/vm/vm_test.go index e0813f0f8..02245e356 100644 --- a/e2e/vm/vm_test.go +++ b/e2e/vm/vm_test.go @@ -53,6 +53,7 @@ func TestVM(t *testing.T) { testVirtualizationFrameworkAndRosetta(o, *e2e.Installed) testSupportBundle(o) testCredHelper(o, *e2e.Installed, *e2e.Registry) + testSoci(o, *e2e.Installed) }) gomega.RegisterFailHandler(ginkgo.Fail) diff --git a/go.mod b/go.mod index d123877a3..c969e0c70 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/xorcare/pointer v1.2.2 golang.org/x/crypto v0.11.0 + golang.org/x/exp v0.0.0-20230810033253-352e893a4cad golang.org/x/tools v0.11.1 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.27.4 diff --git a/go.sum b/go.sum index f48c097cc..3a3dd014a 100644 --- a/go.sum +++ b/go.sum @@ -400,6 +400,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU= +golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/pkg/config/config.go b/pkg/config/config.go index aaef3e748..88416b03a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -35,6 +35,9 @@ type AdditionalDirectory struct { type Finch struct { CPUs *int `yaml:"cpus"` Memory *string `yaml:"memory"` + // Snapshotters: the snapshotters that will be installed and configured automatically on vm init or on vm start. + // Values: `soci` for SOCI snapshotter; `overlayfs` for default overlay snapshotter. + Snapshotters []string `yaml:"snapshotters,omitempty"` // CredsHelper: the list of credential helpers that will be installed and configured automatically on vm init or on vm start CredsHelpers []string `yaml:"creds_helpers,omitempty"` // AdditionalDirectories are the work directories that are not supported by default. In macOS, only home directory is supported by default. diff --git a/pkg/config/lima_config_applier.go b/pkg/config/lima_config_applier.go index fa2028af9..02d3a3262 100644 --- a/pkg/config/lima_config_applier.go +++ b/pkg/config/lima_config_applier.go @@ -10,13 +10,40 @@ import ( "github.com/lima-vm/lima/pkg/limayaml" "github.com/spf13/afero" "github.com/xorcare/pointer" + "golang.org/x/exp/slices" "gopkg.in/yaml.v3" "github.com/runfinch/finch/pkg/command" "github.com/runfinch/finch/pkg/system" ) -const userModeEmulationProvisioningScriptHeader = "# cross-arch tools" +const ( + sociVersion = "0.3.0" + sociInstallationProvisioningScriptHeader = "# soci installation and configuring" + sociFileNameFormat = "soci-snapshotter-%s-linux-%s.tar.gz" + sociDownloadURLFormat = "https://github.com/awslabs/soci-snapshotter/releases/download/v%s/%s" + sociInstallationScriptFormat = `%s +if [ ! -f /usr/local/bin/soci ]; then + # download soci + set -e + curl --retry 2 --retry-max-time 120 -OL "%s" + # move to usr/local/bin + tar -C /usr/local/bin -xvf %s soci soci-snapshotter-grpc +fi + +# changing containerd config +export config=etc/containerd/config.toml +echo " [proxy_plugins.soci] + type = \"snapshot\" + address = \"/run/soci-snapshotter-grpc/soci-snapshotter-grpc.sock\" " >> $config + +sudo systemctl restart containerd.service +sudo soci-snapshotter-grpc &> ~/soci-snapshotter-logs & + + ` + + userModeEmulationProvisioningScriptHeader = "# cross-arch tools" +) // LimaConfigApplierSystemDeps contains the system dependencies for LimaConfigApplier. // @@ -96,6 +123,24 @@ func (lca *limaConfigApplier) Apply(isInit bool) error { limaCfg = *cfgAfterInit } + supportedSnapshotters := []string{"overlayfs", "soci"} + snapshotters := make(map[string][2]bool) + for i, snapshotter := range lca.cfg.Snapshotters { + if !slices.Contains(supportedSnapshotters, snapshotter) { + return fmt.Errorf("invalid snapshotter config value: %s", snapshotter) + } + + isDefaultSnapshotter := false + if i == 0 { + isDefaultSnapshotter = true + } + + isEnabled := true + snapshotters[snapshotter] = [2]bool{isEnabled, isDefaultSnapshotter} + } + + toggleSnaphotters(&limaCfg, snapshotters) + limaCfgBytes, err := yaml.Marshal(limaCfg) if err != nil { return fmt.Errorf("failed to marshal the lima config file: %w", err) @@ -188,3 +233,54 @@ func hasUserModeEmulationInstallationScript(limaCfg *limayaml.LimaYAML) (int, bo return scriptIdx, hasCrossArchToolInstallationScript } + +// toggles snapshotters and sets default snapshotter. +func toggleSnaphotters(limaCfg *limayaml.LimaYAML, snapshotters map[string][2]bool) { + toggleOverlayFs(limaCfg, snapshotters["overlayfs"][1]) + toggleSoci(limaCfg, snapshotters["soci"][0], snapshotters["soci"][1], sociVersion) +} + +// sets overlayfs as the default snapshotter. +func toggleOverlayFs(limaCfg *limayaml.LimaYAML, isDefault bool) { + if isDefault { + limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": ""} + } +} + +func toggleSoci(limaCfg *limayaml.LimaYAML, enabled bool, isDefault bool, sociVersion string) { + idx, hasScript := findSociInstallationScript(limaCfg) + sociFileName := fmt.Sprintf(sociFileNameFormat, sociVersion, system.NewStdLib().Arch()) + sociDownloadURL := fmt.Sprintf(sociDownloadURLFormat, sociVersion, sociFileName) + sociInstallationScript := fmt.Sprintf(sociInstallationScriptFormat, sociInstallationProvisioningScriptHeader, sociDownloadURL, sociFileName) + if !hasScript && enabled { + limaCfg.Provision = append(limaCfg.Provision, limayaml.Provision{ + Mode: "system", + Script: sociInstallationScript, + }) + } else if hasScript && !enabled { + if len(limaCfg.Provision) > 0 { + limaCfg.Provision = append(limaCfg.Provision[:idx], limaCfg.Provision[idx+1:]...) + } + } + + if isDefault { + limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": "soci"} + } else { + limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": ""} + } +} + +func findSociInstallationScript(limaCfg *limayaml.LimaYAML) (int, bool) { + hasSociInstallationScript := false + var scriptIdx int + for idx, prov := range limaCfg.Provision { + trimmed := strings.Trim(prov.Script, " ") + if !hasSociInstallationScript && strings.HasPrefix(trimmed, sociInstallationProvisioningScriptHeader) { + hasSociInstallationScript = true + scriptIdx = idx + break + } + } + + return scriptIdx, hasSociInstallationScript +} diff --git a/pkg/config/lima_config_applier_test.go b/pkg/config/lima_config_applier_test.go index cfd891242..6ea05586b 100644 --- a/pkg/config/lima_config_applier_test.go +++ b/pkg/config/lima_config_applier_test.go @@ -15,6 +15,7 @@ import ( "gopkg.in/yaml.v3" "github.com/runfinch/finch/pkg/mocks" + "github.com/runfinch/finch/pkg/system" ) func TestDiskLimaConfigApplier_Apply(t *testing.T) { @@ -79,6 +80,300 @@ elif [ ! -f /usr/bin/qemu-aarch64-static ]; then qemu_pkgs="$qemu_pkgs qemu-user-static-x86" fi +if [[ $qemu_pkgs ]]; then + dnf install -y --setopt=install_weak_deps=False ${qemu_pkgs} +fi +`, limaCfg.Provision[0].Script) + }, + want: nil, + }, + { + name: "adds soci script and sets soci as default snapshotter when soci is first in snapshotters array", + config: &Finch{ + Memory: pointer.String("2GiB"), + CPUs: pointer.Int(4), + VMType: pointer.String("qemu"), + Rosetta: pointer.Bool(false), + Snapshotters: []string{"soci"}, + }, + path: "/lima.yaml", + isInit: true, + mockSvc: func( + fs afero.Fs, + l *mocks.Logger, + cmd *mocks.Command, + creator *mocks.CommandCreator, + deps *mocks.LimaConfigApplierSystemDeps, + ) { + err := afero.WriteFile(fs, "/lima.yaml", []byte("memory: 4GiB\ncpus: 8"), 0o600) + require.NoError(t, err) + cmd.EXPECT().Output().Return([]byte("13.0.0"), nil) + creator.EXPECT().Create("sw_vers", "-productVersion").Return(cmd) + }, + postRunCheck: func(t *testing.T, fs afero.Fs) { + buf, err := afero.ReadFile(fs, "/lima.yaml") + require.NoError(t, err) + + sociFileName := fmt.Sprintf(sociFileNameFormat, sociVersion, system.NewStdLib().Arch()) + sociDownloadURL := fmt.Sprintf(sociDownloadURLFormat, sociVersion, sociFileName) + sociInstallationScript := fmt.Sprintf(sociInstallationScriptFormat, + sociInstallationProvisioningScriptHeader, + sociDownloadURL, + sociFileName) + + var limaCfg limayaml.LimaYAML + err = yaml.Unmarshal(buf, &limaCfg) + require.NoError(t, err) + require.Equal(t, 4, *limaCfg.CPUs) + require.Equal(t, "2GiB", *limaCfg.Memory) + require.Equal(t, "reverse-sshfs", *limaCfg.MountType) + require.Equal(t, "system", limaCfg.Provision[1].Mode) + require.Equal(t, "soci", limaCfg.Env["CONTAINERD_SNAPSHOTTER"]) + require.Equal(t, sociInstallationScript, limaCfg.Provision[1].Script) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, `# cross-arch tools +#!/bin/bash +qemu_pkgs="" +if [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-aarch64" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-arm" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-x86" +fi + +if [[ $qemu_pkgs ]]; then + dnf install -y --setopt=install_weak_deps=False ${qemu_pkgs} +fi +`, limaCfg.Provision[0].Script) + }, + want: nil, + }, + { + name: "doesn't add soci script and doesn't change default snapshotter when snapshotters is not set in config", + config: &Finch{ + Memory: pointer.String("2GiB"), + CPUs: pointer.Int(4), + VMType: pointer.String("qemu"), + Rosetta: pointer.Bool(false), + Snapshotters: []string{}, + }, + path: "/lima.yaml", + isInit: true, + mockSvc: func( + fs afero.Fs, + l *mocks.Logger, + cmd *mocks.Command, + creator *mocks.CommandCreator, + deps *mocks.LimaConfigApplierSystemDeps, + ) { + err := afero.WriteFile(fs, "/lima.yaml", []byte("memory: 4GiB\ncpus: 8"), 0o600) + require.NoError(t, err) + cmd.EXPECT().Output().Return([]byte("13.0.0"), nil) + creator.EXPECT().Create("sw_vers", "-productVersion").Return(cmd) + }, + postRunCheck: func(t *testing.T, fs afero.Fs) { + buf, err := afero.ReadFile(fs, "/lima.yaml") + require.NoError(t, err) + + var limaCfg limayaml.LimaYAML + err = yaml.Unmarshal(buf, &limaCfg) + require.NoError(t, err) + require.Equal(t, 4, *limaCfg.CPUs) + require.Equal(t, "2GiB", *limaCfg.Memory) + require.Equal(t, "reverse-sshfs", *limaCfg.MountType) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, "", limaCfg.Env["CONTAINERD_SNAPSHOTTER"]) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, `# cross-arch tools +#!/bin/bash +qemu_pkgs="" +if [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-aarch64" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-arm" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-x86" +fi + +if [[ $qemu_pkgs ]]; then + dnf install -y --setopt=install_weak_deps=False ${qemu_pkgs} +fi +`, limaCfg.Provision[0].Script) + }, + want: nil, + }, + { + name: "doesn't add soci script when soci is not in snapshotters array", + config: &Finch{ + Memory: pointer.String("2GiB"), + CPUs: pointer.Int(4), + VMType: pointer.String("qemu"), + Rosetta: pointer.Bool(false), + Snapshotters: []string{"overlayfs"}, + }, + path: "/lima.yaml", + isInit: true, + mockSvc: func( + fs afero.Fs, + l *mocks.Logger, + cmd *mocks.Command, + creator *mocks.CommandCreator, + deps *mocks.LimaConfigApplierSystemDeps, + ) { + err := afero.WriteFile(fs, "/lima.yaml", []byte("memory: 4GiB\ncpus: 8"), 0o600) + require.NoError(t, err) + cmd.EXPECT().Output().Return([]byte("13.0.0"), nil) + creator.EXPECT().Create("sw_vers", "-productVersion").Return(cmd) + }, + postRunCheck: func(t *testing.T, fs afero.Fs) { + buf, err := afero.ReadFile(fs, "/lima.yaml") + require.NoError(t, err) + + var limaCfg limayaml.LimaYAML + err = yaml.Unmarshal(buf, &limaCfg) + require.NoError(t, err) + require.Equal(t, 4, *limaCfg.CPUs) + require.Equal(t, "2GiB", *limaCfg.Memory) + require.Equal(t, "reverse-sshfs", *limaCfg.MountType) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, "", limaCfg.Env["CONTAINERD_SNAPSHOTTER"]) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, `# cross-arch tools +#!/bin/bash +qemu_pkgs="" +if [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-aarch64" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-arm" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-x86" +fi + +if [[ $qemu_pkgs ]]; then + dnf install -y --setopt=install_weak_deps=False ${qemu_pkgs} +fi +`, limaCfg.Provision[0].Script) + }, + want: nil, + }, + { + name: "adds soci script but keeps overlayfs as default when soci is present in snapshotters array but not first element", + config: &Finch{ + Memory: pointer.String("2GiB"), + CPUs: pointer.Int(4), + VMType: pointer.String("qemu"), + Rosetta: pointer.Bool(false), + Snapshotters: []string{"overlayfs", "soci"}, + }, + path: "/lima.yaml", + isInit: true, + mockSvc: func( + fs afero.Fs, + l *mocks.Logger, + cmd *mocks.Command, + creator *mocks.CommandCreator, + deps *mocks.LimaConfigApplierSystemDeps, + ) { + err := afero.WriteFile(fs, "/lima.yaml", []byte("memory: 4GiB\ncpus: 8"), 0o600) + require.NoError(t, err) + cmd.EXPECT().Output().Return([]byte("13.0.0"), nil) + creator.EXPECT().Create("sw_vers", "-productVersion").Return(cmd) + }, + postRunCheck: func(t *testing.T, fs afero.Fs) { + buf, err := afero.ReadFile(fs, "/lima.yaml") + require.NoError(t, err) + + sociFileName := fmt.Sprintf(sociFileNameFormat, sociVersion, system.NewStdLib().Arch()) + sociDownloadURL := fmt.Sprintf(sociDownloadURLFormat, sociVersion, sociFileName) + sociInstallationScript := fmt.Sprintf(sociInstallationScriptFormat, + sociInstallationProvisioningScriptHeader, + sociDownloadURL, + sociFileName) + + var limaCfg limayaml.LimaYAML + err = yaml.Unmarshal(buf, &limaCfg) + require.NoError(t, err) + require.Equal(t, 4, *limaCfg.CPUs) + require.Equal(t, "2GiB", *limaCfg.Memory) + require.Equal(t, "reverse-sshfs", *limaCfg.MountType) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, "", limaCfg.Env["CONTAINERD_SNAPSHOTTER"]) + require.Equal(t, sociInstallationScript, limaCfg.Provision[1].Script) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, `# cross-arch tools +#!/bin/bash +qemu_pkgs="" +if [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-aarch64" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-arm" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-x86" +fi + +if [[ $qemu_pkgs ]]; then + dnf install -y --setopt=install_weak_deps=False ${qemu_pkgs} +fi +`, limaCfg.Provision[0].Script) + }, + want: nil, + }, + { + name: "doesn't add soci script when snapshotter is not set in config", + config: &Finch{ + Memory: pointer.String("2GiB"), + CPUs: pointer.Int(4), + VMType: pointer.String("qemu"), + Rosetta: pointer.Bool(false), + Snapshotters: []string{"soci", "overlayfs"}, + }, + path: "/lima.yaml", + isInit: true, + mockSvc: func( + fs afero.Fs, + l *mocks.Logger, + cmd *mocks.Command, + creator *mocks.CommandCreator, + deps *mocks.LimaConfigApplierSystemDeps, + ) { + err := afero.WriteFile(fs, "/lima.yaml", []byte("memory: 4GiB\ncpus: 8"), 0o600) + require.NoError(t, err) + cmd.EXPECT().Output().Return([]byte("13.0.0"), nil) + creator.EXPECT().Create("sw_vers", "-productVersion").Return(cmd) + }, + postRunCheck: func(t *testing.T, fs afero.Fs) { + buf, err := afero.ReadFile(fs, "/lima.yaml") + require.NoError(t, err) + + sociFileName := fmt.Sprintf(sociFileNameFormat, sociVersion, system.NewStdLib().Arch()) + sociDownloadURL := fmt.Sprintf(sociDownloadURLFormat, sociVersion, sociFileName) + sociInstallationScript := fmt.Sprintf(sociInstallationScriptFormat, + sociInstallationProvisioningScriptHeader, + sociDownloadURL, + sociFileName) + + var limaCfg limayaml.LimaYAML + err = yaml.Unmarshal(buf, &limaCfg) + require.NoError(t, err) + require.Equal(t, 4, *limaCfg.CPUs) + require.Equal(t, "2GiB", *limaCfg.Memory) + require.Equal(t, "reverse-sshfs", *limaCfg.MountType) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, "soci", limaCfg.Env["CONTAINERD_SNAPSHOTTER"]) + require.Equal(t, sociInstallationScript, limaCfg.Provision[1].Script) + require.Equal(t, "system", limaCfg.Provision[0].Mode) + require.Equal(t, `# cross-arch tools +#!/bin/bash +qemu_pkgs="" +if [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-aarch64" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-arm" +elif [ ! -f /usr/bin/qemu-aarch64-static ]; then + qemu_pkgs="$qemu_pkgs qemu-user-static-x86" +fi + if [[ $qemu_pkgs ]]; then dnf install -y --setopt=install_weak_deps=False ${qemu_pkgs} fi