Skip to content

Commit bca23c1

Browse files
committed
Refactor Lima config management
Changes how the CLI generates and manages Lima VM configs to make changes like mounted directories be accurately reflected in Lima. trellis-cli used to generate a config for Lima in `.trellis/lima/{name}.yml` which was used for the initial creation of the VM. However, even when providing a config to `limactl create`, Lima creates a separate config in its managed directory (`$HOME/.lima/{name}/lima.yaml`). This meant that any subsequent config changes in Trellis might only be persisted to the CLI's managed config (in `.trellis`), which would do nothing, instead of Lima's config. For example, if you added a new site to Trellis (or changed the path to an existing one), you'd either have to delete the VM entirely and start over, or manually make the changes to Lima's config. This PR removes the old CLI managed config entirely and instead updates Lima's config directly (the one in `$HOME/.lima`). This means one less file to manage and a single source of truth which will allow proper VM udpates. Now if you change any wordpress sites in development, stopping and restarting the VM will apply the new config changes.
1 parent 4122c50 commit bca23c1

5 files changed

Lines changed: 204 additions & 59 deletions

File tree

cmd/vm_start.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,19 @@ func (c *VmStartCommand) Run(args []string) int {
7373
return 1
7474
}
7575

76-
// VM doesn't exist yet, create it
76+
// VM doesn't exist yet, create and start it
7777
if err = manager.CreateInstance(siteName); err != nil {
7878
c.UI.Error("Error creating VM.")
7979
c.UI.Error(err.Error())
8080
return 1
8181
}
8282

83+
if err = manager.StartInstance(siteName); err != nil {
84+
c.UI.Error("Error starting VM.")
85+
c.UI.Error(err.Error())
86+
return 1
87+
}
88+
8389
c.UI.Info("\nProvisioning VM...")
8490

8591
provisionCmd := NewProvisionCommand(c.UI, c.Trellis)

pkg/lima/instance.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package lima
22

33
import (
4+
"bytes"
45
_ "embed"
56
"errors"
67
"fmt"
78
"os"
9+
"path/filepath"
810
"regexp"
911
"text/template"
1012

@@ -40,7 +42,6 @@ type Config struct {
4042
}
4143

4244
type Instance struct {
43-
ConfigFile string
4445
InventoryFile string
4546
Sites map[string]*trellis.Site
4647
Name string `json:"name"`
@@ -55,16 +56,29 @@ type Instance struct {
5556
Username string `json:"username,omitempty"`
5657
}
5758

58-
func (i *Instance) CreateConfig() error {
59+
func (i *Instance) ConfigFile() string {
60+
return filepath.Join(i.Dir, "lima.yaml")
61+
}
62+
63+
func (i *Instance) GenerateConfig() (*bytes.Buffer, error) {
64+
var contents bytes.Buffer
65+
5966
tpl := template.Must(template.New("lima").Parse(ConfigTemplate))
6067

61-
file, err := os.Create(i.ConfigFile)
62-
if err != nil {
63-
return fmt.Errorf("%v: %w", ConfigErr, err)
68+
if err := tpl.Execute(&contents, i); err != nil {
69+
return &contents, fmt.Errorf("%v: %w", ConfigErr, err)
6470
}
6571

66-
err = tpl.Execute(file, i)
72+
return &contents, nil
73+
}
74+
75+
func (i *Instance) UpdateConfig() error {
76+
contents, err := i.GenerateConfig()
6777
if err != nil {
78+
return err
79+
}
80+
81+
if err := os.WriteFile(i.ConfigFile(), contents.Bytes(), 0666); err != nil {
6882
return fmt.Errorf("%v: %w", ConfigErr, err)
6983
}
7084

@@ -91,15 +105,6 @@ func (i *Instance) CreateInventoryFile() error {
91105
return nil
92106
}
93107

94-
func (i *Instance) DeleteConfig() error {
95-
err := os.Remove(i.ConfigFile)
96-
if err != nil {
97-
return fmt.Errorf("Could not delete config file: %v", err)
98-
}
99-
100-
return nil
101-
}
102-
103108
/*
104109
Gets the IP address of the instance using the output of `ip route`:
105110
default via 192.168.64.1 proto dhcp src 192.168.64.2 metric 100

pkg/lima/instance_test.go

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,17 @@ import (
1010
"github.com/roots/trellis-cli/trellis"
1111
)
1212

13-
func TestCreateConfig(t *testing.T) {
13+
func TestGenerateConfig(t *testing.T) {
1414
defer trellis.LoadFixtureProject(t)()
1515
trellis := trellis.NewTrellis()
1616
if err := trellis.LoadProject(); err != nil {
1717
t.Fatal(err)
1818
}
1919

2020
dir := t.TempDir()
21-
configFile := filepath.Join(dir, "lima.yaml")
2221

2322
instance := &Instance{
24-
Dir: dir,
25-
ConfigFile: configFile,
23+
Dir: dir,
2624
Config: Config{
2725
Images: []Image{
2826
{
@@ -40,12 +38,83 @@ func TestCreateConfig(t *testing.T) {
4038
Sites: trellis.Environments["development"].WordPressSites,
4139
}
4240

43-
err := instance.CreateConfig()
41+
content, err := instance.GenerateConfig()
4442
if err != nil {
4543
t.Fatal(err)
4644
}
4745

48-
content, err := os.ReadFile(configFile)
46+
absSitePath := filepath.Join(trellis.Path, "../site")
47+
48+
expected := fmt.Sprintf(`vmType: "vz"
49+
rosetta:
50+
enabled: false
51+
images:
52+
- location: http://ubuntu.com/focal
53+
arch: aarch64
54+
55+
mounts:
56+
- location: %s
57+
mountPoint: /srv/www/example.com/current
58+
writable: true
59+
60+
mountType: "virtiofs"
61+
ssh:
62+
forwardAgent: true
63+
networks:
64+
- vzNAT: true
65+
66+
portForwards:
67+
- guestPort: 80
68+
hostPort: 1234
69+
70+
containerd:
71+
user: false
72+
provision:
73+
- mode: system
74+
script: |
75+
#!/bin/bash
76+
echo "127.0.0.1 $(hostname)" >> /etc/hosts
77+
`, absSitePath)
78+
79+
if content.String() != expected {
80+
t.Errorf("expected %s\ngot %s", expected, content.String())
81+
}
82+
}
83+
84+
func TestUpdateConfig(t *testing.T) {
85+
defer trellis.LoadFixtureProject(t)()
86+
trellis := trellis.NewTrellis()
87+
if err := trellis.LoadProject(); err != nil {
88+
t.Fatal(err)
89+
}
90+
91+
dir := t.TempDir()
92+
93+
instance := &Instance{
94+
Dir: dir,
95+
Config: Config{
96+
Images: []Image{
97+
{
98+
Location: "http://ubuntu.com/focal",
99+
Arch: "aarch64",
100+
},
101+
},
102+
PortForwards: []PortForward{
103+
{
104+
HostPort: 1234,
105+
GuestPort: 80,
106+
},
107+
},
108+
},
109+
Sites: trellis.Environments["development"].WordPressSites,
110+
}
111+
112+
err := instance.UpdateConfig()
113+
if err != nil {
114+
t.Fatal(err)
115+
}
116+
117+
content, err := os.ReadFile(instance.ConfigFile())
49118

50119
if err != nil {
51120
t.Fatal(err)

pkg/lima/manager.go

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,18 @@ func (m *Manager) GetInstance(name string) (Instance, bool) {
8080
func (m *Manager) CreateInstance(name string) error {
8181
instance := m.newInstance(name)
8282

83-
if err := instance.CreateConfig(); err != nil {
84-
return err
85-
}
86-
87-
err := command.WithOptions(
83+
cmd := command.WithOptions(
8884
command.WithTermOutput(),
8985
command.WithLogging(m.ui),
90-
).Cmd("limactl", []string{"start", "--tty=false", "--name=" + instance.Name, instance.ConfigFile}).Run()
86+
).Cmd("limactl", []string{"create", "--tty=false", "--name=" + instance.Name, "-"})
9187

88+
configContents, err := instance.GenerateConfig()
9289
if err != nil {
9390
return err
9491
}
9592

96-
return postStart(m, instance)
93+
cmd.Stdin = configContents
94+
return cmd.Run()
9795
}
9896

9997
func (m *Manager) DeleteInstance(name string) error {
@@ -114,10 +112,6 @@ func (m *Manager) DeleteInstance(name string) error {
114112
return err
115113
}
116114

117-
if err := instance.DeleteConfig(); err != nil {
118-
return err
119-
}
120-
121115
return nil
122116
} else {
123117
return fmt.Errorf("Error: VM is running. Run `trellis vm stop` to stop it.")
@@ -158,6 +152,10 @@ func (m *Manager) StartInstance(name string) error {
158152
return nil
159153
}
160154

155+
if err := instance.UpdateConfig(); err != nil {
156+
return err
157+
}
158+
161159
err := command.WithOptions(
162160
command.WithTermOutput(),
163161
command.WithLogging(m.ui),
@@ -167,7 +165,24 @@ func (m *Manager) StartInstance(name string) error {
167165
return err
168166
}
169167

170-
return postStart(m, instance)
168+
user, err := instance.getUsername()
169+
if err != nil {
170+
return fmt.Errorf("Could not get username: %v", err)
171+
}
172+
173+
instance.Username = string(user)
174+
175+
// Hydrate instance with data from limactl that is only available after starting (mainly the forwarded SSH local port)
176+
err = m.hydrateInstance(&instance)
177+
if err != nil {
178+
return err
179+
}
180+
181+
if err = m.addHosts(instance); err != nil {
182+
return err
183+
}
184+
185+
return nil
171186
}
172187

173188
func (m *Manager) StopInstance(name string) error {
@@ -213,7 +228,6 @@ func (m *Manager) hydrateInstance(instance *Instance) error {
213228
}
214229

215230
func (m *Manager) initInstance(instance *Instance) {
216-
instance.ConfigFile = filepath.Join(m.ConfigPath, instance.Name+".yml")
217231
instance.InventoryFile = m.InventoryPath()
218232
instance.Sites = m.Sites
219233
}
@@ -281,27 +295,6 @@ func (m *Manager) removeHosts(instance Instance) error {
281295
return m.HostsResolver.RemoveHosts(instance.Name)
282296
}
283297

284-
func postStart(manager *Manager, instance Instance) error {
285-
user, err := instance.getUsername()
286-
if err != nil {
287-
return fmt.Errorf("Could not get username: %v", err)
288-
}
289-
290-
instance.Username = string(user)
291-
292-
// Hydrate instance with data from limactl that is only available after starting (mainly the forwarded SSH local port)
293-
err = manager.hydrateInstance(&instance)
294-
if err != nil {
295-
return err
296-
}
297-
298-
if err = manager.addHosts(instance); err != nil {
299-
return err
300-
}
301-
302-
return nil
303-
}
304-
305298
func getMacOSVersion() (string, error) {
306299
cmd := command.Cmd("sw_vers", []string{"-productVersion"})
307300
b, err := cmd.Output()

0 commit comments

Comments
 (0)