Skip to content

Commit 7fd113d

Browse files
authored
Forward Lima guest HTTP port to host (#600)
This isn't used yet but will give us more options in the future for different "host resolvers" instead of only relying on vzNAT's networking that makes the guest IP reachable on the host. On Linux hosts for example, vzNAT isn't available so we would some sort of rever proxy implementation and that can only work with the guest HTTP port forwarded on the host. There's a new CLI option to control this but it defaults to `true`.
1 parent 9d4b061 commit 7fd113d

File tree

4 files changed

+94
-15
lines changed

4 files changed

+94
-15
lines changed

cli_config/cli_config.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ type VmImage struct {
1717
}
1818

1919
type VmConfig struct {
20-
Manager string `yaml:"manager"`
21-
HostsResolver string `yaml:"hosts_resolver"`
22-
Images []VmImage `yaml:"images"`
23-
Ubuntu string `yaml:"ubuntu"`
24-
InstanceName string `yaml:"instance_name"`
20+
Manager string `yaml:"manager"`
21+
HostsResolver string `yaml:"hosts_resolver"`
22+
Images []VmImage `yaml:"images"`
23+
Ubuntu string `yaml:"ubuntu"`
24+
InstanceName string `yaml:"instance_name"`
25+
ForwardHttpPort bool `yaml:"forward_http_port"`
2526
}
2627

2728
type Config struct {

pkg/lima/manager.go

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"net"
89
"os"
910
"path/filepath"
1011
"strings"
@@ -27,10 +28,17 @@ var (
2728
ErrUnsupportedOS = errors.New("unsupported OS or macOS version. The macOS Virtualization Framework requires macOS 13.0 (Ventura) or later.")
2829
)
2930

31+
type PortFinder interface {
32+
Resolve() (int, error)
33+
}
34+
35+
type TCPPortFinder struct{}
36+
3037
type Manager struct {
3138
ConfigPath string
3239
Sites map[string]*trellis.Site
3340
HostsResolver vm.HostsResolver
41+
PortFinder PortFinder
3442
ui cli.Ui
3543
trellis *trellis.Trellis
3644
}
@@ -55,6 +63,7 @@ func NewManager(trellis *trellis.Trellis, ui cli.Ui) (manager *Manager, err erro
5563
ConfigPath: limaConfigPath,
5664
Sites: trellis.Environments["development"].WordPressSites,
5765
HostsResolver: hostsResolver,
66+
PortFinder: &TCPPortFinder{},
5867
trellis: trellis,
5968
ui: ui,
6069
}
@@ -78,7 +87,10 @@ func (m *Manager) GetInstance(name string) (Instance, bool) {
7887
}
7988

8089
func (m *Manager) CreateInstance(name string) error {
81-
instance := m.newInstance(name)
90+
instance, err := m.newInstance(name)
91+
if err != nil {
92+
return err
93+
}
8294

8395
cmd := command.WithOptions(
8496
command.WithTermOutput(),
@@ -172,7 +184,8 @@ func (m *Manager) StartInstance(name string) error {
172184

173185
instance.Username = string(user)
174186

175-
// Hydrate instance with data from limactl that is only available after starting (mainly the forwarded SSH local port)
187+
// Hydrate instance with data from limactl that is only available after
188+
// starting (mainly the forwarded local ports)
176189
err = m.hydrateInstance(&instance)
177190
if err != nil {
178191
return err
@@ -232,7 +245,7 @@ func (m *Manager) initInstance(instance *Instance) {
232245
instance.Sites = m.Sites
233246
}
234247

235-
func (m *Manager) newInstance(name string) Instance {
248+
func (m *Manager) newInstance(name string) (Instance, error) {
236249
instance := Instance{Name: name}
237250
m.initInstance(&instance)
238251

@@ -249,9 +262,24 @@ func (m *Manager) newInstance(name string) Instance {
249262
images = imagesFromVersion(m.trellis.CliConfig.Vm.Ubuntu)
250263
}
251264

252-
config := Config{Images: images}
265+
portForwards := []PortForward{}
266+
267+
if m.trellis.CliConfig.Vm.ForwardHttpPort {
268+
httpForwardPort, err := m.PortFinder.Resolve()
269+
if err != nil {
270+
return Instance{}, fmt.Errorf("Could not find a local free port for HTTP forwarding: %v", err)
271+
}
272+
273+
portForwards = append(portForwards, PortForward{
274+
GuestPort: 80,
275+
HostPort: httpForwardPort,
276+
},
277+
)
278+
}
279+
280+
config := Config{Images: images, PortForwards: portForwards}
253281
instance.Config = config
254-
return instance
282+
return instance, nil
255283
}
256284

257285
func (m *Manager) createConfigPath() error {
@@ -329,3 +357,31 @@ func ensureRequirements() error {
329357
func imagesFromVersion(version string) []Image {
330358
return UbuntuImages[version]
331359
}
360+
361+
func (p *TCPPortFinder) Resolve() (int, error) {
362+
lAddr0, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:0")
363+
if err != nil {
364+
return 0, err
365+
}
366+
367+
l, err := net.ListenTCP("tcp4", lAddr0)
368+
if err != nil {
369+
return 0, err
370+
}
371+
372+
defer func() { _ = l.Close() }()
373+
lAddr := l.Addr()
374+
375+
lTCPAddr, ok := lAddr.(*net.TCPAddr)
376+
if !ok {
377+
return 0, fmt.Errorf("expected *net.TCPAddr, got %v", lAddr)
378+
}
379+
380+
port := lTCPAddr.Port
381+
382+
if port <= 0 {
383+
return 0, fmt.Errorf("unexpected port %d", port)
384+
}
385+
386+
return port, nil
387+
}

pkg/lima/manager_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ type MockHostsResolver struct {
1515
Hosts map[string]string
1616
}
1717

18+
type MockPortFinder struct{}
19+
20+
func (p *MockPortFinder) Resolve() (int, error) {
21+
return 60720, nil
22+
}
23+
1824
func TestNewManager(t *testing.T) {
1925
defer trellis.LoadFixtureProject(t)()
2026
trellis := trellis.NewTrellis()
@@ -120,7 +126,13 @@ func TestNewInstanceUbuntuVersion(t *testing.T) {
120126
t.Fatal(err)
121127
}
122128

123-
instance := manager.newInstance("test")
129+
manager.PortFinder = &MockPortFinder{}
130+
131+
instance, err := manager.newInstance("test")
132+
133+
if err != nil {
134+
t.Fatal(err)
135+
}
124136

125137
if instance.Name != "test" {
126138
t.Errorf("expected instance name to be %q, got %q", "test", instance.Name)
@@ -133,6 +145,14 @@ func TestNewInstanceUbuntuVersion(t *testing.T) {
133145
if instance.Config.Images[0].Alias != "focal" {
134146
t.Errorf("expected instance config to have focal image, got %q", instance.Config.Images[0].Alias)
135147
}
148+
149+
if len(instance.Config.PortForwards) != 1 {
150+
t.Errorf("expected instance config to have 1 port forwards, got %d", len(instance.Config.PortForwards))
151+
}
152+
153+
if instance.Config.PortForwards[0].GuestPort != 80 || instance.Config.PortForwards[0].HostPort != 60720 {
154+
t.Errorf("expected instance config to have port forward guest 80 to host 60720, got guest %d to host %d", instance.Config.PortForwards[0].GuestPort, instance.Config.PortForwards[0].HostPort)
155+
}
136156
}
137157
func TestInstances(t *testing.T) {
138158
defer trellis.LoadFixtureProject(t)()
@@ -197,6 +217,7 @@ func TestCreateInstance(t *testing.T) {
197217

198218
hostsStorage := make(map[string]string)
199219
manager.HostsResolver = &MockHostsResolver{Hosts: hostsStorage}
220+
manager.PortFinder = &MockPortFinder{}
200221

201222
instanceName := "test"
202223
sshPort := 60720

trellis/trellis.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ var DefaultCliConfig = cli_config.Config{
3636
Open: make(map[string]string),
3737
VirtualenvIntegration: true,
3838
Vm: cli_config.VmConfig{
39-
Manager: "auto",
40-
HostsResolver: "hosts_file",
41-
Ubuntu: "24.04",
42-
InstanceName: "",
39+
Manager: "auto",
40+
HostsResolver: "hosts_file",
41+
Ubuntu: "24.04",
42+
InstanceName: "",
43+
ForwardHttpPort: true,
4344
},
4445
}
4546

0 commit comments

Comments
 (0)