From d4d8d19cec793e70cda87800cffe5393a90558ea Mon Sep 17 00:00:00 2001 From: Channing Gaddy Date: Fri, 11 Aug 2023 00:14:57 -0700 Subject: [PATCH] making config option a slice instead of string + adding as conifg value to make it easier for users to switch back to overlayfs from soci Signed-off-by: Channing Gaddy --- README.md | 13 +- e2e/vm/soci_test.go | 8 +- go.mod | 1 + go.sum | 2 + pkg/config/config.go | 4 +- pkg/config/lima_config_applier.go | 67 ++++++-- pkg/config/lima_config_applier_test.go | 202 +++++++++++++++++++++++-- 7 files changed, 264 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 55710fc3a..7657c26d5 100644 --- a/README.md +++ b/README.md @@ -99,15 +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 -# snapshotter: the snapshotter a user want to use as their default snapshotter +# 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 snapshotter will be installed on either finch vm init or finch vm start. -# The snapshotter 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 snapshotter value from finch.yaml -# To completely remove the snapshotter binary, shell into your VM and remove /usr/local/bin/{snapshotter binary} +# 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 -snapshotter: soci +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 index ec033e95b..ada22cb47 100644 --- a/e2e/vm/soci_test.go +++ b/e2e/vm/soci_test.go @@ -38,8 +38,8 @@ var testSoci = func(o *option.Option, installed bool) { 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\nsnapshotter: soci\n"+ - "vmType: qemu\nrosetta: false")) + 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) @@ -54,8 +54,8 @@ var testSoci = func(o *option.Option, installed bool) { 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\nsnapshotter: soci\n"+ - "vmType: qemu\nrosetta: false")) + 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) 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 a8752252f..276d0e6fb 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -36,8 +36,8 @@ type Finch struct { CPUs *int `yaml:"cpus"` Memory *string `yaml:"memory"` // Snapshotter: the snapshotter that will be installed and configured automatically on vm init or on vm start - // Values: `soci` for SOCI snapshotter or empty for default overlay snapshotter. - Snapshotter *string `yaml:"snapshotter,omitempty"` + // 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 501177ecb..7c837ec45 100644 --- a/pkg/config/lima_config_applier.go +++ b/pkg/config/lima_config_applier.go @@ -10,6 +10,7 @@ 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" @@ -122,14 +123,25 @@ func (lca *limaConfigApplier) Apply(isInit bool) error { limaCfg = *cfgAfterInit } - var sociEnabled bool - if lca.cfg.Snapshotter == nil { - sociEnabled = false - } else { - sociEnabled = (*lca.cfg.Snapshotter == "soci") + supportedSnapshotters := []string{"overlayfs", "soci"} + enabledSnapshotters := initializeEnabledSnapshotterSlice(len(supportedSnapshotters)) + + for i, snapshotter := range lca.cfg.Snapshotters { + supportedIdx := slices.Index(supportedSnapshotters, snapshotter) + if supportedIdx < 0 { + return fmt.Errorf("invalid snapshotter config value: %s", snapshotter) + } + + isDefaultSnapshotter := false + if i == 0 { + isDefaultSnapshotter = true + } + + isEnabled := true + enabledSnapshotters[supportedIdx] = [2]bool{isEnabled, isDefaultSnapshotter} } - toggleSoci(&limaCfg, sociEnabled, sociVersion) + toggleSnaphotters(&limaCfg, supportedSnapshotters, enabledSnapshotters) limaCfgBytes, err := yaml.Marshal(limaCfg) if err != nil { @@ -224,23 +236,60 @@ func hasUserModeEmulationInstallationScript(limaCfg *limayaml.LimaYAML) (int, bo return scriptIdx, hasCrossArchToolInstallationScript } -func toggleSoci(limaCfg *limayaml.LimaYAML, enabled bool, sociVersion string) { +// initializes the bool slice for what snapshotter the user has enabled to all false +// this will be changed later depending on the user's snapshotters config values. +func initializeEnabledSnapshotterSlice(numSupportedSnapshotters int) [2][2]bool { + var enabledSnapshotters [2][2]bool + + for i := 0; i < numSupportedSnapshotters; i++ { + enabledSnapshotters[i] = [2]bool{false, false} + } + + return enabledSnapshotters +} + +// toggles enabled snapshotters and sets default snapshotter. +func toggleSnaphotters(limaCfg *limayaml.LimaYAML, supportedSnapshotters []string, enabledSnapshotters [2][2]bool) { + for i := len(enabledSnapshotters) - 1; i > 0; i-- { + enabledSlice := enabledSnapshotters[i] + if enabledSlice[0] { + if supportedSnapshotters[i] == "overlayfs" { + toggleOverlayFs(limaCfg, enabledSlice[1]) + } else if supportedSnapshotters[i] == "soci" { + toggleSoci(limaCfg, enabledSlice[0], enabledSlice[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.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": "soci"} limaCfg.Provision = append(limaCfg.Provision, limayaml.Provision{ Mode: "system", Script: sociInstallationScript, }) } else if hasScript && !enabled { - limaCfg.Env = map[string]string{"CONTAINERD_SNAPSHOTTER": ""} 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) { diff --git a/pkg/config/lima_config_applier_test.go b/pkg/config/lima_config_applier_test.go index 97aeb7b8f..6ea05586b 100644 --- a/pkg/config/lima_config_applier_test.go +++ b/pkg/config/lima_config_applier_test.go @@ -88,13 +88,13 @@ fi want: nil, }, { - name: "adds soci script when snapshotter is set to soci in config", + 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), - Snapshotter: pointer.String("soci"), + Memory: pointer.String("2GiB"), + CPUs: pointer.Int(4), + VMType: pointer.String("qemu"), + Rosetta: pointer.Bool(false), + Snapshotters: []string{"soci"}, }, path: "/lima.yaml", isInit: true, @@ -150,13 +150,121 @@ fi want: nil, }, { - name: "doesn't add soci script when snapshotter is not set in config", + 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), - Snapshotter: pointer.String(""), + 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, @@ -176,6 +284,13 @@ fi 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) @@ -184,6 +299,69 @@ fi 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