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

feat: add config to support additional directories #128

Merged
merged 12 commits into from
Dec 26, 2022
Merged
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,21 @@ The `run` command has a `-v` option for volume mounts. See `Volume flags` under

Finch has a simple and extensible configuration. A configuration file at `${HOME}/.finch/finch.yaml` will be generated on first run. Currently, this config file has options for system resource limits for the underlying virtual machine. These default limits are generated dynamically based on the resources available on the host system, but can be changed by manually editing the config file.

Currently, the options are:

* CPUs [int]: the amount of vCPU to dedicate to the virtual machine
* Memory [string]: the amount of memory to dedicate to the virtual machine

For a full list of configuration options, check [the struct here](pkg/config/config.go#L25).

An example `finch.yaml` looks like this:

```yaml
# CPUs: the amount of vCPU to dedicate to the virtual machine. (required)
cpus: 4
# Memory: the amount of memory to dedicate to the virtual machine. (required)
memory: 4GiB
# AdditionalDirectories: the work directories that are not supported by default. In macOS, only home directory is supported by default.
# For example, if you want to mount a directory into a container, and that directory is not under your home directory,
# then you'll need to specify this field to add that directory or any ascendant of it as a work directory. (optional)
additional_directories:
# the path of each additional directory.
- path: /Volumes
```

## What's next?
Expand Down
48 changes: 44 additions & 4 deletions e2e/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
"os/exec"
"path/filepath"

"github.com/lima-vm/lima/pkg/limayaml"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/runfinch/common-tests/command"
"github.com/runfinch/common-tests/option"
"github.com/xorcare/pointer"
"gopkg.in/yaml.v3"
)

var finchConfigFilePath = os.Getenv("HOME") + "/.finch/finch.yaml"
Expand Down Expand Up @@ -86,7 +89,12 @@ var testConfig = func(o *option.Option, installed bool) {
gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(cfgBuf).Should(gomega.SatisfyAll(gomega.ContainSubstring("cpus: 6"), gomega.ContainSubstring("memory: 4GiB")))

var limaCfg limayaml.LimaYAML
err = yaml.Unmarshal(cfgBuf, &limaCfg)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(*limaCfg.CPUs).Should(gomega.Equal(6))
gomega.Expect(*limaCfg.Memory).Should(gomega.Equal("4GiB"))
})

ginkgo.It("updates config values when partial config file is present", func() {
Expand All @@ -96,8 +104,12 @@ var testConfig = func(o *option.Option, installed bool) {
gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
// 4 CPUs is the default
gomega.Expect(cfgBuf).Should(gomega.SatisfyAll(gomega.MatchRegexp(`cpus: \d`), gomega.ContainSubstring("memory: 6GiB")))

var limaCfg limayaml.LimaYAML
err = yaml.Unmarshal(cfgBuf, &limaCfg)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(limaCfg.CPUs).ShouldNot(gomega.BeNil())
gomega.Expect(*limaCfg.Memory).Should(gomega.Equal("6GiB"))
})

ginkgo.It("uses the default config values when no config file is present", func() {
Expand All @@ -107,7 +119,12 @@ var testConfig = func(o *option.Option, installed bool) {
gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(cfgBuf).Should(gomega.SatisfyAll(gomega.MatchRegexp(`cpus: \d`), gomega.MatchRegexp(`memory: \dGiB`)))

var limaCfg limayaml.LimaYAML
err = yaml.Unmarshal(cfgBuf, &limaCfg)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(limaCfg.CPUs).ShouldNot(gomega.BeNil())
gomega.Expect(*limaCfg.Memory).Should(gomega.MatchRegexp(`\dGiB`))
})

ginkgo.It("fails to launch when the config file is improperly formatted", func() {
Expand All @@ -124,5 +141,28 @@ var testConfig = func(o *option.Option, installed bool) {
startCmdSession := updateAndApplyConfig(o, []byte("memory: 0GiB"))
gomega.Expect(startCmdSession).Should(gexec.Exit(1))
})

ginkgo.It("updates config values when a config file is present with additional directories", func() {
mharwani marked this conversation as resolved.
Show resolved Hide resolved
startCmdSession := updateAndApplyConfig(o, []byte(`memory: 4GiB
cpus: 6
additional_directories:
- path: /Volumes
- path: /tmp/workspace`))
gomega.Expect(startCmdSession).Should(gexec.Exit(0))

gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
var limaCfg limayaml.LimaYAML
davidhsingyuchen marked this conversation as resolved.
Show resolved Hide resolved
err = yaml.Unmarshal(cfgBuf, &limaCfg)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(*limaCfg.CPUs).Should(gomega.Equal(6))
gomega.Expect(*limaCfg.Memory).Should(gomega.Equal("4GiB"))
gomega.Expect(len(limaCfg.Mounts)).Should(gomega.Equal(2))
gomega.Expect(limaCfg.Mounts[0].Location).Should(gomega.Equal("/Volumes"))
gomega.Expect(limaCfg.Mounts[0].Writable).Should(gomega.Equal(pointer.Bool(true)))
gomega.Expect(limaCfg.Mounts[1].Location).Should(gomega.Equal("/tmp/workspace"))
gomega.Expect(limaCfg.Mounts[1].Writable).Should(gomega.Equal(pointer.Bool(true)))
})
})
}
9 changes: 9 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,19 @@ import (
"github.com/runfinch/finch/pkg/system"
)

// AdditionalDirectory represents the additional directory used in Finch config.
davidhsingyuchen marked this conversation as resolved.
Show resolved Hide resolved
type AdditionalDirectory struct {
Path *string `yaml:"path"`
}

// Finch represents the configuration file for Finch CLI.
type Finch struct {
CPUs *int `yaml:"cpus"`
Memory *string `yaml:"memory"`
// AdditionalDirectories are the work directories that are not supported by default. In macOS, only home directory is supported by default.
// For example, if you want to mount a directory into a container, and that directory is not under your home directory,
// then you'll need to specify this field to add that directory or any ascendant of it as a work directory.
AdditionalDirectories []AdditionalDirectory `yaml:"additional_directories,omitempty"`
}

// Nerdctl is a copy from github.com/containerd/nerdctl/cmd/nerdctl/main.go
Expand Down
7 changes: 7 additions & 0 deletions pkg/config/lima_config_applier.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/lima-vm/lima/pkg/limayaml"
"github.com/spf13/afero"
"github.com/xorcare/pointer"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -43,6 +44,12 @@ func (lca *limaConfigApplier) Apply() error {

limaCfg.CPUs = lca.cfg.CPUs
limaCfg.Memory = lca.cfg.Memory
limaCfg.Mounts = []limayaml.Mount{}
davidhsingyuchen marked this conversation as resolved.
Show resolved Hide resolved
for _, ad := range lca.cfg.AdditionalDirectories {
ningziwen marked this conversation as resolved.
Show resolved Hide resolved
limaCfg.Mounts = append(limaCfg.Mounts, limayaml.Mount{
Location: *ad.Path, Writable: pointer.Bool(true),
})
}

limaCfgBytes, err := yaml.Marshal(limaCfg)
if err != nil {
Expand Down
44 changes: 42 additions & 2 deletions pkg/config/lima_config_applier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/golang/mock/gomock"
"github.com/lima-vm/lima/pkg/limayaml"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
"github.com/xorcare/pointer"
Expand Down Expand Up @@ -44,8 +45,11 @@ func TestDiskLimaConfigApplier_Apply(t *testing.T) {
buf, err := afero.ReadFile(fs, "/lima.yaml")
require.NoError(t, err)

// limayaml.LimaYAML has a required "images" field which will also get marshaled
require.Equal(t, buf, []byte("images: []\ncpus: 4\nmemory: 2GiB\n"))
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)
},
want: nil,
},
Expand Down Expand Up @@ -81,6 +85,42 @@ func TestDiskLimaConfigApplier_Apply(t *testing.T) {
&yaml.TypeError{Errors: []string{"line 1: cannot unmarshal !!str `this is...` into limayaml.LimaYAML"}},
),
},
{
name: "lima config file with additional directories",
config: &Finch{
Memory: pointer.String("2GiB"),
CPUs: pointer.Int(4),
AdditionalDirectories: []AdditionalDirectory{{pointer.String("/Volumes")}},
},
path: "/lima.yaml",
mockSvc: func(fs afero.Fs, l *mocks.Logger) {
err := afero.WriteFile(fs, "/lima.yaml", []byte("memory: 4GiB\ncpus: 8"), 0o600)
require.NoError(t, err)
},
postRunCheck: func(t *testing.T, fs afero.Fs) {
buf, err := afero.ReadFile(fs, "/lima.yaml")
require.NoError(t, err)

// limayaml.LimaYAML has a required "images" field which will also get marshaled
wantYaml := `images: []
davidhsingyuchen marked this conversation as resolved.
Show resolved Hide resolved
cpus: 4
memory: 2GiB
mounts:
- location: /Volumes
writable: true
`
require.Equal(t, wantYaml, string(buf))
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, 1, len(limaCfg.Mounts))
require.Equal(t, "/Volumes", limaCfg.Mounts[0].Location)
require.Equal(t, true, *limaCfg.Mounts[0].Writable)
},
want: nil,
},
}

for _, tc := range testCases {
Expand Down