Skip to content

Commit 345d535

Browse files
committed
Add ~/.wsl2hosts feature for adding aliases
1 parent 9a1681e commit 345d535

File tree

9 files changed

+232
-14
lines changed

9 files changed

+232
-14
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
*.exe
1+
*.exe
2+
vendor/
3+
example/
4+
foo.go
5+
foo_test.go

cmd/wsl2host/pkg/service/service.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"regexp"
66
"strings"
77

8+
"github.com/shayne/go-wsl2-host/internal/wsl2hosts"
9+
810
"github.com/shayne/go-wsl2-host/pkg/hostsapi"
911

1012
"github.com/shayne/go-wsl2-host/pkg/wslapi"
@@ -29,13 +31,14 @@ func Run() error {
2931
return fmt.Errorf("failed to get infos: %w", err)
3032
}
3133

32-
hapi, err := hostsapi.CreateAPI(tld)
34+
hapi, err := hostsapi.CreateAPI("wsl2-host") // filtere only managed host entries
3335
if err != nil {
3436
return fmt.Errorf("failed to create hosts api: %w", err)
3537
}
3638

3739
updated := false
3840
hostentries := hapi.Entries()
41+
3942
for _, i := range infos {
4043
hostname := distroNameToHostname(i.Name)
4144
// remove stopped distros
@@ -58,12 +61,64 @@ func Run() error {
5861
he.IP = ip
5962
}
6063
} else {
61-
updated = true
6264
// add running distros not present
63-
hapi.AddEntry(&hostsapi.HostEntry{
65+
err := hapi.AddEntry(&hostsapi.HostEntry{
6466
Hostname: hostname,
6567
IP: ip,
68+
Comment: wsl2hosts.DefaultComment(),
69+
})
70+
if err == nil {
71+
updated = true
72+
}
73+
}
74+
}
75+
76+
// process aliases
77+
defdistro, _ := wslapi.GetDefaultDistro()
78+
if err != nil {
79+
return fmt.Errorf("GetDefaultDistro failed: %w", err)
80+
}
81+
var aliasmap = make(map[string]interface{})
82+
defdistroip, _ := wslapi.GetIP(defdistro.Name)
83+
if defdistro.Running {
84+
aliases, err := wslapi.GetHostAliases()
85+
if err == nil {
86+
for _, a := range aliases {
87+
aliasmap[a] = nil
88+
}
89+
}
90+
}
91+
// update entries after distro processing
92+
hostentries = hapi.Entries()
93+
for _, he := range hostentries {
94+
if !wsl2hosts.IsAlias(he.Comment) {
95+
continue
96+
}
97+
// update IP for aliases when running and if it exists in aliasmap
98+
if _, ok := aliasmap[he.Hostname]; ok && defdistro.Running {
99+
if he.IP != defdistroip {
100+
updated = true
101+
he.IP = defdistroip
102+
}
103+
} else { // remove entry when not running or not in aliasmap
104+
err := hapi.RemoveEntry(he.Hostname)
105+
if err == nil {
106+
updated = true
107+
}
108+
}
109+
}
110+
111+
for hostname := range aliasmap {
112+
// add new aliases
113+
if _, ok := hostentries[hostname]; !ok && defdistro.Running {
114+
err := hapi.AddEntry(&hostsapi.HostEntry{
115+
IP: defdistroip,
116+
Hostname: hostname,
117+
Comment: wsl2hosts.DistroComment(defdistro.Name),
66118
})
119+
if err == nil {
120+
updated = true
121+
}
67122
}
68123
}
69124

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/shayne/go-wsl2-host
33
go 1.12
44

55
require (
6+
github.com/stretchr/testify v1.4.0
67
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
78
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
89
golang.org/x/text v0.3.0

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
6+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7+
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
8+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
19
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
210
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
311
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -8,3 +16,7 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSF
816
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
917
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
1018
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
19+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
20+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
21+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
22+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

internal/wsl2hosts/wsl2hosts.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Package wsl2hosts provides helpers for managing alias
2+
// host entries
3+
package wsl2hosts
4+
5+
import (
6+
"fmt"
7+
"strings"
8+
)
9+
10+
const prefix = "alias:"
11+
const defaultComment = "managed by wsl2-host"
12+
13+
// IsAlias returns true if given string matches alias pattern
14+
func IsAlias(comment string) bool {
15+
return strings.HasPrefix(comment, prefix)
16+
}
17+
18+
// DistroName returns the name of the WSL distro the host
19+
// entry is an alias for
20+
func DistroName(comment string) (string, error) {
21+
if !IsAlias(comment) {
22+
return "", fmt.Errorf("comment is not alias: %s", comment)
23+
}
24+
25+
var name string
26+
for _, c := range comment[len(prefix):] {
27+
if c == ';' {
28+
break
29+
}
30+
name += string(c)
31+
}
32+
33+
name = strings.TrimSpace(name)
34+
return name, nil
35+
}
36+
37+
// DistroComment returns hosts file comment alias
38+
// for given distro name
39+
func DistroComment(distroname string) string {
40+
return fmt.Sprintf("%s %s; %s", prefix, distroname, defaultComment)
41+
}
42+
43+
// DefaultComment returns basic comment for managed host entires
44+
func DefaultComment() string {
45+
return defaultComment
46+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package wsl2hosts
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestIsAlias(t *testing.T) {
10+
assert.True(t, IsAlias("alias: Ubuntu-18.04; managed by wsl2-host"))
11+
assert.False(t, IsAlias("managed by wsl2-host"))
12+
}
13+
14+
func TestDistroName(t *testing.T) {
15+
name, err := DistroName("alias: Ubuntu-18.04; managed by wsl2-host")
16+
assert.Nil(t, err)
17+
assert.Equal(t, "Ubuntu-18.04", name)
18+
name, err = DistroName("alias: ClearLinux; managed by wsl2-host")
19+
assert.Nil(t, err)
20+
assert.Equal(t, "ClearLinux", name)
21+
name, err = DistroName("alias: Foo Bar; managed by wsl2-host")
22+
assert.Nil(t, err)
23+
assert.Equal(t, "Foo Bar", name)
24+
name, err = DistroName("managed by wsl2-host")
25+
assert.NotNil(t, err)
26+
assert.Equal(t, "", name)
27+
}
28+
29+
func TestDistroComment(t *testing.T) {
30+
comment := DistroComment("Ubuntu-18.04")
31+
assert.Equal(t, "alias: Ubuntu-18.04; managed by wsl2-host", comment)
32+
}
33+
34+
func TestDefaultComment(t *testing.T) {
35+
assert.Equal(t, "managed by wsl2-host", DefaultComment())
36+
}

pkg/hostsapi/hostsapi.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type HostEntry struct {
1616
idx int
1717
IP string
1818
Hostname string
19+
Comment string
1920
}
2021

2122
// HostsAPI data structure
@@ -35,25 +36,37 @@ func parseHostfileLine(idx int, line string) ([]*HostEntry, error) {
3536
return nil, errors.New("comment line")
3637
}
3738
fields := strings.Fields(line)
38-
var validfields []string
39-
for _, f := range fields {
40-
if len(f) <= 0 {
39+
var ip string
40+
var hostnames []string
41+
var comment string
42+
var commentidx int
43+
for i, f := range fields {
44+
if f == "" {
4145
continue
4246
}
4347
if f[0] == '#' { // inline comment
48+
commentidx = i + 1
4449
break // don't process any more
4550
}
46-
validfields = append(validfields, f)
51+
if i == 0 {
52+
ip = f
53+
} else {
54+
hostnames = append(hostnames, f)
55+
}
56+
}
57+
if commentidx > 0 {
58+
comment = strings.Join(fields[commentidx:], " ")
4759
}
48-
if len(validfields) <= 1 {
60+
if ip == "" || len(hostnames) == 0 {
4961
return nil, fmt.Errorf("invalid fields for line: %q", line)
5062
}
5163
var entries []*HostEntry
52-
for _, hostname := range validfields[1:] {
64+
for _, hostname := range hostnames {
5365
entries = append(entries, &HostEntry{
5466
idx: idx,
55-
IP: validfields[0],
67+
IP: ip,
5668
Hostname: hostname,
69+
Comment: comment,
5770
})
5871
}
5972

@@ -72,7 +85,7 @@ func (h *HostsAPI) loadAndParse() error {
7285
continue
7386
}
7487
for _, e := range entries {
75-
if h.filter == "" || strings.Contains(e.Hostname, h.filter) {
88+
if h.filter == "" || strings.Contains(e.Comment, h.filter) {
7689
h.entries[e.Hostname] = e
7790
h.remidxs[e.idx] = nil
7891
}
@@ -155,10 +168,13 @@ func (h *HostsAPI) Write() error {
155168

156169
// append entries to file
157170
for _, e := range h.entries {
158-
outbuf.WriteString(fmt.Sprintf("%s %s # managed by wsl2-host\r\n", e.IP, e.Hostname))
171+
var comment string
172+
if e.Comment != "" {
173+
comment = fmt.Sprintf(" # %s", e.Comment)
174+
}
175+
outbuf.WriteString(fmt.Sprintf("%s %s%s\r\n", e.IP, e.Hostname, comment))
159176
}
160177

161-
fmt.Println(string(outbuf.Bytes()))
162178
f, err := os.OpenFile(hostspath, os.O_WRONLY|os.O_TRUNC, 0600)
163179
if err != nil {
164180
return fmt.Errorf("failed to open hosts file for writing: %w", err)

pkg/wslapi/wslapi.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ func GetAllInfo() ([]*DistroInfo, error) {
6262
return infos, nil
6363
}
6464

65+
// GetDefaultDistro returns the info for the default distro
66+
func GetDefaultDistro() (*DistroInfo, error) {
67+
infos, err := GetAllInfo()
68+
if err != nil {
69+
return nil, fmt.Errorf("GetAllInfo failed: %w", err)
70+
}
71+
for _, i := range infos {
72+
if i.Default == true {
73+
return i, nil
74+
}
75+
}
76+
return nil, errors.New("failed to find default")
77+
}
78+
6579
// IsRunning returns whether or not a given WSL distro is running
6680
func IsRunning(name string) (bool, error) {
6781
running, err := wslcli.RunningDistros()
@@ -87,3 +101,24 @@ func GetIP(name string) (string, error) {
87101
}
88102
return "", fmt.Errorf("GetIP failed, distro '%s' is not running", name)
89103
}
104+
105+
// GetHostAliases returns custom hosts referenced in `~/.wsl2hosts`
106+
// of default WSL distro
107+
func GetHostAliases() ([]string, error) {
108+
info, err := GetDefaultDistro()
109+
if err != nil {
110+
return nil, fmt.Errorf("GetDefaultDistro failed: %w", err)
111+
}
112+
if !info.Running {
113+
return nil, errors.New("default distro not running")
114+
}
115+
out, err := wslcli.RunCommand("cat", "~/.wsl2hosts")
116+
if err != nil {
117+
return nil, fmt.Errorf("RunCommand failed: %w", err)
118+
}
119+
out = strings.TrimSpace(out)
120+
if out == "" {
121+
return nil, fmt.Errorf("no host aliases")
122+
}
123+
return strings.Split(out, " "), nil
124+
}

pkg/wslcli/wslcli.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ func GetIP(name string) (string, error) {
5959
return ips[0], nil
6060
}
6161

62+
// RunCommand runs the given command via `bash -c` under
63+
// the default WSL distro
64+
func RunCommand(command string, args ...string) (string, error) {
65+
cmdstr := fmt.Sprintf("%s %s", command, strings.Join(args, " "))
66+
cmd := exec.Command("wsl.exe", "--", "bash", "-c", cmdstr)
67+
out, err := cmd.Output()
68+
if err != nil {
69+
return "", err
70+
}
71+
sout := string(out)
72+
return sout, nil
73+
}
74+
6275
// GetHostIP returns the IP address of Hyper-V Switch on the host connected to WSL
6376
func GetHostIP() (string, error) {
6477
cmd := exec.Command("netsh", "interface", "ip", "show", "address", "vEthernet (WSL)") //, "|", "findstr", "IP Address", "|", "%", "{", "$_", "-replace", "IP Address:", "", "}", "|", "%", "{", "$_", "-replace", " ", "", "}")

0 commit comments

Comments
 (0)