Skip to content

Commit 39b8599

Browse files
djeebusjakubno
andauthored
Ensure that host file parsing doesn't drop entries when parsing. (#1261)
Co-authored-by: Jakub Novák <[email protected]>
1 parent 476cfa5 commit 39b8599

File tree

3 files changed

+109
-8
lines changed

3 files changed

+109
-8
lines changed

packages/envd/internal/api/init.go

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10+
"net/netip"
11+
"os"
1012
"time"
1113

1214
"github.com/rs/zerolog"
@@ -15,8 +17,11 @@ import (
1517

1618
"github.com/e2b-dev/infra/packages/envd/internal/host"
1719
"github.com/e2b-dev/infra/packages/envd/internal/logs"
20+
"github.com/e2b-dev/infra/packages/shared/pkg/utils"
1821
)
1922

23+
const hostsFilePermissions = 0o644
24+
2025
var ErrAccessTokenAlreadySet = errors.New("access token is already set")
2126

2227
const (
@@ -119,19 +124,68 @@ func (a *API) SetupHyperloop(address string) {
119124
a.hyperloopLock.Lock()
120125
defer a.hyperloopLock.Unlock()
121126

122-
hosts, err := txeh.NewHosts(&txeh.HostsConfig{ReadFilePath: "/etc/hosts", WriteFilePath: "/etc/hosts"})
127+
if err := rewriteHostsFile(address, "/etc/hosts"); err != nil {
128+
a.logger.Error().Err(err).Msg("failed to modify hosts file")
129+
} else {
130+
a.envVars.Store("E2B_EVENTS_ADDRESS", fmt.Sprintf("http://%s", address))
131+
}
132+
}
133+
134+
const eventsHost = "events.e2b.local"
135+
136+
func rewriteHostsFile(address, path string) error {
137+
data, err := os.ReadFile(path)
138+
if err != nil {
139+
return fmt.Errorf("failed to read hosts file: %w", err)
140+
}
141+
142+
// the txeh library drops an entry if the file does not end with a newline
143+
if len(data) > 0 && data[len(data)-1] != '\n' {
144+
data = append(data, '\n')
145+
}
146+
147+
hosts, err := txeh.NewHosts(&txeh.HostsConfig{RawText: utils.ToPtr(string(data))})
123148
if err != nil {
124-
a.logger.Error().Msgf("Failed to create hosts: %v", err)
125-
return
149+
return fmt.Errorf("failed to create hosts: %w", err)
126150
}
127151

128152
// Update /etc/hosts to point events.e2b.local to the hyperloop IP
129153
// This will remove any existing entries for events.e2b.local first
130-
hosts.AddHost(address, "events.e2b.local")
131-
err = hosts.Save()
154+
ipFamily, err := getIPFamily(address)
155+
if err != nil {
156+
return fmt.Errorf("failed to get ip family: %w", err)
157+
}
158+
159+
if ok, current, _ := hosts.HostAddressLookup(eventsHost, ipFamily); ok && current == address {
160+
return nil // nothing to be done
161+
}
162+
163+
hosts.AddHost(address, eventsHost)
164+
165+
if err = os.WriteFile(path, []byte(hosts.RenderHostsFile()), hostsFilePermissions); err != nil {
166+
return fmt.Errorf("failed to save hosts file: %w", err)
167+
}
168+
169+
return nil
170+
}
171+
172+
var (
173+
ErrInvalidAddress = errors.New("invalid IP address")
174+
ErrUnknownAddressFormat = errors.New("unknown IP address format")
175+
)
176+
177+
func getIPFamily(address string) (txeh.IPFamily, error) {
178+
addressIP, err := netip.ParseAddr(address)
132179
if err != nil {
133-
a.logger.Error().Msgf("Failed to add events host entry: %v", err)
180+
return txeh.IPFamilyV4, fmt.Errorf("failed to parse IP address: %w", err)
134181
}
135182

136-
a.envVars.Store("E2B_EVENTS_ADDRESS", fmt.Sprintf("http://%s", address))
183+
switch {
184+
case addressIP.Is4():
185+
return txeh.IPFamilyV4, nil
186+
case addressIP.Is6():
187+
return txeh.IPFamilyV6, nil
188+
default:
189+
return txeh.IPFamilyV4, fmt.Errorf("%w: %s", ErrUnknownAddressFormat, address)
190+
}
137191
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package api
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestSimpleCases(t *testing.T) {
14+
testCases := map[string]func(string) string{
15+
"both newlines": func(s string) string { return s },
16+
"no newline prefix": func(s string) string { return strings.TrimPrefix(s, "\n") },
17+
"no newline suffix": func(s string) string { return strings.TrimSuffix(s, "\n") },
18+
"no newline prefix or suffix": strings.TrimSpace,
19+
}
20+
21+
for name, preprocessor := range testCases {
22+
t.Run(name, func(t *testing.T) {
23+
tempDir := t.TempDir()
24+
25+
value := `
26+
# comment
27+
127.0.0.1 one.host
28+
127.0.0.2 two.host
29+
`
30+
value = preprocessor(value)
31+
inputPath := filepath.Join(tempDir, "hosts")
32+
err := os.WriteFile(inputPath, []byte(value), hostsFilePermissions)
33+
require.NoError(t, err)
34+
35+
err = rewriteHostsFile("127.0.0.3", inputPath)
36+
require.NoError(t, err)
37+
38+
data, err := os.ReadFile(inputPath)
39+
require.NoError(t, err)
40+
41+
assert.Equal(t, `# comment
42+
127.0.0.1 one.host
43+
127.0.0.2 two.host
44+
127.0.0.3 events.e2b.local`, strings.TrimSpace(string(data)))
45+
})
46+
}
47+
}

packages/envd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const (
3838
)
3939

4040
var (
41-
Version = "0.3.5"
41+
Version = "0.3.6"
4242

4343
commitSHA string
4444

0 commit comments

Comments
 (0)