Skip to content

Commit 0ceb060

Browse files
authored
feat: add config to support additional directories (#128)
Signed-off-by: Ziwen Ning <[email protected]> Issue #, if available: #107 *Description of changes:* add config to support additional directories according to the doc added in #123 *Testing done:* - [ X ] I've reviewed the guidance in CONTRIBUTING.md #### License Acceptance By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: Ziwen Ning <[email protected]>
1 parent 1bac92a commit 0ceb060

File tree

5 files changed

+110
-11
lines changed

5 files changed

+110
-11
lines changed

README.md

+8-5
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,21 @@ The `run` command has a `-v` option for volume mounts. See `Volume flags` under
9494

9595
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.
9696

97-
Currently, the options are:
98-
99-
* CPUs [int]: the amount of vCPU to dedicate to the virtual machine
100-
* Memory [string]: the amount of memory to dedicate to the virtual machine
101-
10297
For a full list of configuration options, check [the struct here](pkg/config/config.go#L25).
10398

10499
An example `finch.yaml` looks like this:
105100

106101
```yaml
102+
# CPUs: the amount of vCPU to dedicate to the virtual machine. (required)
107103
cpus: 4
104+
# Memory: the amount of memory to dedicate to the virtual machine. (required)
108105
memory: 4GiB
106+
# AdditionalDirectories: the work directories that are not supported by default. In macOS, only home directory is supported by default.
107+
# For example, if you want to mount a directory into a container, and that directory is not under your home directory,
108+
# then you'll need to specify this field to add that directory or any ascendant of it as a work directory. (optional)
109+
additional_directories:
110+
# the path of each additional directory.
111+
- path: /Volumes
109112
```
110113
111114
## What's next?

e2e/config_test.go

+44-4
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ import (
1010
"os/exec"
1111
"path/filepath"
1212

13+
"github.com/lima-vm/lima/pkg/limayaml"
1314
"github.com/onsi/ginkgo/v2"
1415
"github.com/onsi/gomega"
1516
"github.com/onsi/gomega/gexec"
1617
"github.com/runfinch/common-tests/command"
1718
"github.com/runfinch/common-tests/option"
19+
"github.com/xorcare/pointer"
20+
"gopkg.in/yaml.v3"
1821
)
1922

2023
var finchConfigFilePath = os.Getenv("HOME") + "/.finch/finch.yaml"
@@ -86,7 +89,12 @@ var testConfig = func(o *option.Option, installed bool) {
8689
gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
8790
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
8891
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
89-
gomega.Expect(cfgBuf).Should(gomega.SatisfyAll(gomega.ContainSubstring("cpus: 6"), gomega.ContainSubstring("memory: 4GiB")))
92+
93+
var limaCfg limayaml.LimaYAML
94+
err = yaml.Unmarshal(cfgBuf, &limaCfg)
95+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
96+
gomega.Expect(*limaCfg.CPUs).Should(gomega.Equal(6))
97+
gomega.Expect(*limaCfg.Memory).Should(gomega.Equal("4GiB"))
9098
})
9199

92100
ginkgo.It("updates config values when partial config file is present", func() {
@@ -96,8 +104,12 @@ var testConfig = func(o *option.Option, installed bool) {
96104
gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
97105
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
98106
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
99-
// 4 CPUs is the default
100-
gomega.Expect(cfgBuf).Should(gomega.SatisfyAll(gomega.MatchRegexp(`cpus: \d`), gomega.ContainSubstring("memory: 6GiB")))
107+
108+
var limaCfg limayaml.LimaYAML
109+
err = yaml.Unmarshal(cfgBuf, &limaCfg)
110+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
111+
gomega.Expect(limaCfg.CPUs).ShouldNot(gomega.BeNil())
112+
gomega.Expect(*limaCfg.Memory).Should(gomega.Equal("6GiB"))
101113
})
102114

103115
ginkgo.It("uses the default config values when no config file is present", func() {
@@ -107,7 +119,12 @@ var testConfig = func(o *option.Option, installed bool) {
107119
gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
108120
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
109121
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
110-
gomega.Expect(cfgBuf).Should(gomega.SatisfyAll(gomega.MatchRegexp(`cpus: \d`), gomega.MatchRegexp(`memory: \dGiB`)))
122+
123+
var limaCfg limayaml.LimaYAML
124+
err = yaml.Unmarshal(cfgBuf, &limaCfg)
125+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
126+
gomega.Expect(limaCfg.CPUs).ShouldNot(gomega.BeNil())
127+
gomega.Expect(*limaCfg.Memory).Should(gomega.MatchRegexp(`\dGiB`))
111128
})
112129

113130
ginkgo.It("fails to launch when the config file is improperly formatted", func() {
@@ -124,5 +141,28 @@ var testConfig = func(o *option.Option, installed bool) {
124141
startCmdSession := updateAndApplyConfig(o, []byte("memory: 0GiB"))
125142
gomega.Expect(startCmdSession).Should(gexec.Exit(1))
126143
})
144+
145+
ginkgo.It("updates config values when a config file is present with additional directories", func() {
146+
startCmdSession := updateAndApplyConfig(o, []byte(`memory: 4GiB
147+
cpus: 6
148+
additional_directories:
149+
- path: /Volumes
150+
- path: /tmp/workspace`))
151+
gomega.Expect(startCmdSession).Should(gexec.Exit(0))
152+
153+
gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
154+
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
155+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
156+
var limaCfg limayaml.LimaYAML
157+
err = yaml.Unmarshal(cfgBuf, &limaCfg)
158+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
159+
gomega.Expect(*limaCfg.CPUs).Should(gomega.Equal(6))
160+
gomega.Expect(*limaCfg.Memory).Should(gomega.Equal("4GiB"))
161+
gomega.Expect(len(limaCfg.Mounts)).Should(gomega.Equal(2))
162+
gomega.Expect(limaCfg.Mounts[0].Location).Should(gomega.Equal("/Volumes"))
163+
gomega.Expect(limaCfg.Mounts[0].Writable).Should(gomega.Equal(pointer.Bool(true)))
164+
gomega.Expect(limaCfg.Mounts[1].Location).Should(gomega.Equal("/tmp/workspace"))
165+
gomega.Expect(limaCfg.Mounts[1].Writable).Should(gomega.Equal(pointer.Bool(true)))
166+
})
127167
})
128168
}

pkg/config/config.go

+9
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,19 @@ import (
2222
"github.com/runfinch/finch/pkg/system"
2323
)
2424

25+
// AdditionalDirectory represents the additional directory used in Finch config.
26+
type AdditionalDirectory struct {
27+
Path *string `yaml:"path"`
28+
}
29+
2530
// Finch represents the configuration file for Finch CLI.
2631
type Finch struct {
2732
CPUs *int `yaml:"cpus"`
2833
Memory *string `yaml:"memory"`
34+
// AdditionalDirectories are the work directories that are not supported by default. In macOS, only home directory is supported by default.
35+
// For example, if you want to mount a directory into a container, and that directory is not under your home directory,
36+
// then you'll need to specify this field to add that directory or any ascendant of it as a work directory.
37+
AdditionalDirectories []AdditionalDirectory `yaml:"additional_directories,omitempty"`
2938
}
3039

3140
// Nerdctl is a copy from github.com/containerd/nerdctl/cmd/nerdctl/main.go

pkg/config/lima_config_applier.go

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/lima-vm/lima/pkg/limayaml"
1010
"github.com/spf13/afero"
11+
"github.com/xorcare/pointer"
1112
"gopkg.in/yaml.v3"
1213
)
1314

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

4445
limaCfg.CPUs = lca.cfg.CPUs
4546
limaCfg.Memory = lca.cfg.Memory
47+
limaCfg.Mounts = []limayaml.Mount{}
48+
for _, ad := range lca.cfg.AdditionalDirectories {
49+
limaCfg.Mounts = append(limaCfg.Mounts, limayaml.Mount{
50+
Location: *ad.Path, Writable: pointer.Bool(true),
51+
})
52+
}
4653

4754
limaCfgBytes, err := yaml.Marshal(limaCfg)
4855
if err != nil {

pkg/config/lima_config_applier_test.go

+42-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111

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

47-
// limayaml.LimaYAML has a required "images" field which will also get marshaled
48-
require.Equal(t, buf, []byte("images: []\ncpus: 4\nmemory: 2GiB\n"))
48+
var limaCfg limayaml.LimaYAML
49+
err = yaml.Unmarshal(buf, &limaCfg)
50+
require.NoError(t, err)
51+
require.Equal(t, 4, *limaCfg.CPUs)
52+
require.Equal(t, "2GiB", *limaCfg.Memory)
4953
},
5054
want: nil,
5155
},
@@ -81,6 +85,42 @@ func TestDiskLimaConfigApplier_Apply(t *testing.T) {
8185
&yaml.TypeError{Errors: []string{"line 1: cannot unmarshal !!str `this is...` into limayaml.LimaYAML"}},
8286
),
8387
},
88+
{
89+
name: "lima config file with additional directories",
90+
config: &Finch{
91+
Memory: pointer.String("2GiB"),
92+
CPUs: pointer.Int(4),
93+
AdditionalDirectories: []AdditionalDirectory{{pointer.String("/Volumes")}},
94+
},
95+
path: "/lima.yaml",
96+
mockSvc: func(fs afero.Fs, l *mocks.Logger) {
97+
err := afero.WriteFile(fs, "/lima.yaml", []byte("memory: 4GiB\ncpus: 8"), 0o600)
98+
require.NoError(t, err)
99+
},
100+
postRunCheck: func(t *testing.T, fs afero.Fs) {
101+
buf, err := afero.ReadFile(fs, "/lima.yaml")
102+
require.NoError(t, err)
103+
104+
// limayaml.LimaYAML has a required "images" field which will also get marshaled
105+
wantYaml := `images: []
106+
cpus: 4
107+
memory: 2GiB
108+
mounts:
109+
- location: /Volumes
110+
writable: true
111+
`
112+
require.Equal(t, wantYaml, string(buf))
113+
var limaCfg limayaml.LimaYAML
114+
err = yaml.Unmarshal(buf, &limaCfg)
115+
require.NoError(t, err)
116+
require.Equal(t, 4, *limaCfg.CPUs)
117+
require.Equal(t, "2GiB", *limaCfg.Memory)
118+
require.Equal(t, 1, len(limaCfg.Mounts))
119+
require.Equal(t, "/Volumes", limaCfg.Mounts[0].Location)
120+
require.Equal(t, true, *limaCfg.Mounts[0].Writable)
121+
},
122+
want: nil,
123+
},
84124
}
85125

86126
for _, tc := range testCases {

0 commit comments

Comments
 (0)