533 lines
18 KiB
Go
533 lines
18 KiB
Go
package etchosts
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const baseFileContent1Spaces = `127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
|
|
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
|
|
`
|
|
|
|
const baseFileContent1Tabs = `127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
|
|
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
|
|
`
|
|
|
|
const baseFileContent1Mixed = `127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
|
|
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
|
|
`
|
|
|
|
const targetFileContent1 = `127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
|
|
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
|
|
`
|
|
|
|
const baseFileContent2 = `127.0.0.1 localhost
|
|
::1 localhost
|
|
1.1.1.1 name1
|
|
2.2.2.2 name2
|
|
`
|
|
|
|
const targetFileContent2 = `127.0.0.1 localhost
|
|
::1 localhost
|
|
1.1.1.1 name1
|
|
2.2.2.2 name2
|
|
`
|
|
|
|
const baseFileContent3Comments1 = `127.0.0.1 localhost #localhost
|
|
::1 localhost
|
|
# with comments
|
|
`
|
|
|
|
const baseFileContent3Comments2 = `#localhost
|
|
`
|
|
|
|
const targetFileContent3 = `127.0.0.1 localhost
|
|
::1 localhost
|
|
`
|
|
|
|
const baseFileContent4 = `127.0.0.1 localhost
|
|
`
|
|
|
|
const targetFileContent4 = `1.1.1.1 name1
|
|
2.2.2.2 name2
|
|
127.0.0.1 localhost
|
|
`
|
|
|
|
const targetFileContent5 = `1.1.1.1 name1
|
|
2.2.2.2 name2
|
|
127.0.1.1 localhost
|
|
`
|
|
|
|
const baseFileContent6 = `127.0.0.1 localhost
|
|
::1 localhost
|
|
1.1.1.1 host.containers.internal
|
|
`
|
|
|
|
const targetFileContent6 = `127.0.0.1 localhost
|
|
::1 localhost
|
|
1.1.1.1 host.containers.internal
|
|
`
|
|
|
|
const baseFileContent7 = `
|
|
1.1.1.1
|
|
`
|
|
|
|
const targetFileContent7 = `127.0.0.1 localhost
|
|
::1 localhost
|
|
`
|
|
|
|
func TestNew(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
// only used to trigger fails for not existing files
|
|
baseFileName string
|
|
baseFileContent string
|
|
noWriteBaseFile bool
|
|
extraHosts []string
|
|
containerIPs HostEntries
|
|
hostContainersInternal string
|
|
expectedTargetFileContent string
|
|
wantErrString string
|
|
}{
|
|
{
|
|
name: "with spaces",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
expectedTargetFileContent: targetFileContent1,
|
|
},
|
|
{
|
|
name: "with tabs",
|
|
baseFileContent: baseFileContent1Tabs,
|
|
expectedTargetFileContent: targetFileContent1,
|
|
},
|
|
{
|
|
name: "with spaces and tabs",
|
|
baseFileContent: baseFileContent1Mixed,
|
|
expectedTargetFileContent: targetFileContent1,
|
|
},
|
|
{
|
|
name: "with more entries",
|
|
baseFileContent: baseFileContent2,
|
|
expectedTargetFileContent: targetFileContent2,
|
|
},
|
|
{
|
|
name: "with no entries",
|
|
baseFileContent: "",
|
|
expectedTargetFileContent: targetFileContent3,
|
|
},
|
|
{
|
|
name: "base file is empty",
|
|
baseFileContent: "",
|
|
noWriteBaseFile: true,
|
|
expectedTargetFileContent: targetFileContent3,
|
|
},
|
|
{
|
|
name: "with comments 1",
|
|
baseFileContent: baseFileContent3Comments1,
|
|
expectedTargetFileContent: targetFileContent3,
|
|
},
|
|
{
|
|
name: "with comments 2",
|
|
baseFileContent: baseFileContent3Comments2,
|
|
expectedTargetFileContent: targetFileContent3,
|
|
},
|
|
{
|
|
name: "extra hosts",
|
|
baseFileContent: baseFileContent4,
|
|
extraHosts: []string{"name1:1.1.1.1", "name2:2.2.2.2"},
|
|
expectedTargetFileContent: targetFileContent4,
|
|
},
|
|
{
|
|
name: "extra hosts with localhost",
|
|
baseFileContent: "",
|
|
extraHosts: []string{"name1:1.1.1.1", "name2:2.2.2.2", "localhost:127.0.1.1"},
|
|
expectedTargetFileContent: targetFileContent5,
|
|
},
|
|
{
|
|
name: "with more entries and extra host",
|
|
baseFileContent: baseFileContent2,
|
|
extraHosts: []string{"name1:1.1.1.1"},
|
|
expectedTargetFileContent: "1.1.1.1\tname1\n" + targetFileContent2,
|
|
},
|
|
{
|
|
name: "with more entries and extra host",
|
|
baseFileContent: baseFileContent2,
|
|
extraHosts: []string{"name1:1.1.1.1"},
|
|
expectedTargetFileContent: "1.1.1.1\tname1\n" + targetFileContent2,
|
|
},
|
|
{
|
|
name: "with more entries and extra host",
|
|
baseFileContent: baseFileContent2,
|
|
extraHosts: []string{"name1:1.1.1.1"},
|
|
expectedTargetFileContent: "1.1.1.1\tname1\n" + targetFileContent2,
|
|
},
|
|
{
|
|
name: "container ips",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
containerIPs: []HostEntry{{IP: "1.2.3.4", Names: []string{"conname", "hostname"}}},
|
|
expectedTargetFileContent: targetFileContent1 + "1.2.3.4\tconname hostname\n",
|
|
},
|
|
{
|
|
name: "container ips 2",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
containerIPs: []HostEntry{
|
|
{IP: "1.2.3.4", Names: []string{"conname", "hostname"}},
|
|
{IP: "fd::1", Names: []string{"conname", "hostname"}},
|
|
},
|
|
expectedTargetFileContent: targetFileContent1 + "1.2.3.4\tconname hostname\nfd::1\tconname hostname\n",
|
|
},
|
|
{
|
|
name: "container ips and extra hosts",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
extraHosts: []string{"name1:1.1.1.1"},
|
|
containerIPs: []HostEntry{{IP: "1.2.3.4", Names: []string{"conname", "hostname"}}},
|
|
expectedTargetFileContent: "1.1.1.1\tname1\n" + targetFileContent1 + "1.2.3.4\tconname hostname\n",
|
|
},
|
|
{
|
|
name: "container ips and extra hosts 2",
|
|
baseFileContent: baseFileContent2,
|
|
extraHosts: []string{"name1:1.1.1.1"},
|
|
containerIPs: []HostEntry{{IP: "1.2.3.4", Names: []string{"conname", "hostname"}}},
|
|
expectedTargetFileContent: "1.1.1.1\tname1\n" + targetFileContent2 + "1.2.3.4\tconname hostname\n",
|
|
},
|
|
{
|
|
name: "container ip name is not added when name is already present",
|
|
baseFileContent: baseFileContent2,
|
|
containerIPs: []HostEntry{{IP: "1.2.3.4", Names: []string{"name1", "hostname"}}},
|
|
expectedTargetFileContent: targetFileContent2 + "1.2.3.4\thostname\n",
|
|
},
|
|
{
|
|
name: "container ip name is not added when name is already present 2",
|
|
baseFileContent: baseFileContent2,
|
|
containerIPs: []HostEntry{{IP: "1.2.3.4", Names: []string{"name1"}}},
|
|
expectedTargetFileContent: targetFileContent2,
|
|
},
|
|
{
|
|
name: "container ip name is not added when name is already present in extra hosts",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
extraHosts: []string{"somename:1.1.1.1"},
|
|
containerIPs: []HostEntry{{IP: "1.2.3.4", Names: []string{"somename", "hostname"}}},
|
|
expectedTargetFileContent: "1.1.1.1\tsomename\n" + targetFileContent1 + "1.2.3.4\thostname\n",
|
|
},
|
|
{
|
|
name: "with host.containers.internal ip",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
hostContainersInternal: "10.0.0.1",
|
|
expectedTargetFileContent: targetFileContent1 + "10.0.0.1\thost.containers.internal\n",
|
|
},
|
|
{
|
|
name: "host.containers.internal not added when already present in extra hosts",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
extraHosts: []string{"host.containers.internal:1.1.1.1"},
|
|
hostContainersInternal: "10.0.0.1",
|
|
expectedTargetFileContent: "1.1.1.1\thost.containers.internal\n" + targetFileContent1,
|
|
},
|
|
{
|
|
name: "host.containers.internal not added when already present in base hosts",
|
|
baseFileContent: baseFileContent6,
|
|
hostContainersInternal: "10.0.0.1",
|
|
expectedTargetFileContent: targetFileContent6,
|
|
},
|
|
{
|
|
name: "invalid hosts content",
|
|
baseFileContent: baseFileContent7,
|
|
expectedTargetFileContent: targetFileContent7,
|
|
},
|
|
// errors
|
|
{
|
|
name: "base file does not exists",
|
|
baseFileName: "does/not/exists123456789",
|
|
noWriteBaseFile: true,
|
|
wantErrString: "no such file or directory",
|
|
},
|
|
{
|
|
name: "invalid extra hosts hostname empty",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
extraHosts: []string{":1.1.1.1"},
|
|
wantErrString: "hostname in host entry \":1.1.1.1\" is empty",
|
|
},
|
|
{
|
|
name: "invalid extra hosts empty ip",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
extraHosts: []string{"name:"},
|
|
wantErrString: "IP address in host entry \"name:\" is empty",
|
|
},
|
|
{
|
|
name: "invalid extra hosts empty ip",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
extraHosts: []string{"name:"},
|
|
wantErrString: "IP address in host entry \"name:\" is empty",
|
|
},
|
|
{
|
|
name: "invalid extra hosts format",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
extraHosts: []string{"name"},
|
|
wantErrString: "unable to parse host entry \"name\": incorrect format",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
baseHostFile := tt.baseFileName
|
|
if !tt.noWriteBaseFile {
|
|
f, err := ioutil.TempFile(t.TempDir(), "basehosts")
|
|
assert.NoErrorf(t, err, "failed to create base host file: %v", err)
|
|
defer f.Close()
|
|
baseHostFile = f.Name()
|
|
_, err = f.WriteString(tt.baseFileContent)
|
|
assert.NoError(t, err, "failed to write base host file: %v", err)
|
|
}
|
|
|
|
targetFile := filepath.Join(t.TempDir(), "target")
|
|
|
|
params := &Params{
|
|
BaseFile: baseHostFile,
|
|
ExtraHosts: tt.extraHosts,
|
|
ContainerIPs: tt.containerIPs,
|
|
HostContainersInternalIP: tt.hostContainersInternal,
|
|
TargetFile: targetFile,
|
|
}
|
|
|
|
err := New(params)
|
|
if tt.wantErrString != "" {
|
|
assert.ErrorContains(t, err, tt.wantErrString)
|
|
return
|
|
}
|
|
assert.NoError(t, err, "New() failed")
|
|
|
|
content, err := ioutil.ReadFile(targetFile)
|
|
assert.NoErrorf(t, err, "failed to read target host file: %v", err)
|
|
assert.Equal(t, tt.expectedTargetFileContent, string(content), "check hosts content")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdd(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
baseFileContent string
|
|
entries HostEntries
|
|
expectedTargetFileContent string
|
|
wantErrString string
|
|
}{
|
|
{
|
|
name: "add entry",
|
|
baseFileContent: baseFileContent1Mixed,
|
|
entries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: targetFileContent1 + "1.1.1.1\tname1 name2\n",
|
|
},
|
|
{
|
|
name: "add two entries",
|
|
baseFileContent: baseFileContent1Mixed,
|
|
entries: HostEntries{
|
|
{IP: "1.1.1.1", Names: []string{"name1", "name2"}},
|
|
{IP: "1.1.1.2", Names: []string{"name3", "name4"}},
|
|
},
|
|
expectedTargetFileContent: targetFileContent1 + "1.1.1.1\tname1 name2\n1.1.1.2\tname3 name4\n",
|
|
},
|
|
{
|
|
name: "add entry to empty file",
|
|
baseFileContent: "",
|
|
entries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: "1.1.1.1\tname1 name2\n",
|
|
},
|
|
{
|
|
name: "add entry which already exists",
|
|
baseFileContent: baseFileContent2,
|
|
entries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: targetFileContent2,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f, err := ioutil.TempFile(t.TempDir(), "hosts")
|
|
assert.NoErrorf(t, err, "failed to create base host file: %v", err)
|
|
defer f.Close()
|
|
hostFile := f.Name()
|
|
_, err = f.WriteString(tt.baseFileContent)
|
|
assert.NoError(t, err, "failed to write base host file: %v", err)
|
|
|
|
var st unix.Stat_t
|
|
err = unix.Stat(hostFile, &st)
|
|
assert.NoError(t, err, "stat host file: %v", err)
|
|
|
|
err = Add(hostFile, tt.entries)
|
|
if tt.wantErrString != "" {
|
|
assert.ErrorContains(t, err, tt.wantErrString)
|
|
return
|
|
}
|
|
assert.NoError(t, err, "Add() failed")
|
|
|
|
content, err := ioutil.ReadFile(hostFile)
|
|
assert.NoErrorf(t, err, "failed to read host file: %v", err)
|
|
assert.Equal(t, tt.expectedTargetFileContent, string(content), "check hosts content")
|
|
|
|
var st2 unix.Stat_t
|
|
err = unix.Stat(hostFile, &st2)
|
|
assert.NoError(t, err, "stat host file: %v", err)
|
|
assert.Equal(t, st.Ino, st2.Ino, "inode before and after Add() must match")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddIfExists(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
baseFileContent string
|
|
existsEntries HostEntries
|
|
newEntries HostEntries
|
|
expectedTargetFileContent string
|
|
wantErrString string
|
|
}{
|
|
{
|
|
name: "add entry",
|
|
baseFileContent: baseFileContent1Mixed,
|
|
newEntries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: targetFileContent1 + "1.1.1.1\tname1 name2\n",
|
|
},
|
|
{
|
|
name: "add entry with existing entries match",
|
|
baseFileContent: baseFileContent1Mixed,
|
|
existsEntries: HostEntries{{IP: "::1", Names: []string{"localhost"}}},
|
|
newEntries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: targetFileContent1 + "1.1.1.1\tname1 name2\n",
|
|
},
|
|
{
|
|
name: "existing entries with no match should not add",
|
|
baseFileContent: baseFileContent1Mixed,
|
|
existsEntries: HostEntries{{IP: "::1", Names: []string{"name"}}},
|
|
newEntries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: targetFileContent1,
|
|
},
|
|
{
|
|
name: "add two entries",
|
|
baseFileContent: baseFileContent1Mixed,
|
|
newEntries: HostEntries{
|
|
{IP: "1.1.1.1", Names: []string{"name1", "name2"}},
|
|
{IP: "1.1.1.2", Names: []string{"name3", "name4"}},
|
|
},
|
|
expectedTargetFileContent: targetFileContent1 + "1.1.1.1\tname1 name2\n1.1.1.2\tname3 name4\n",
|
|
},
|
|
{
|
|
name: "add two entries with existing entries match",
|
|
baseFileContent: baseFileContent1Mixed,
|
|
existsEntries: HostEntries{{IP: "127.0.0.1", Names: []string{"localhost"}}},
|
|
newEntries: HostEntries{
|
|
{IP: "1.1.1.1", Names: []string{"name1", "name2"}},
|
|
{IP: "1.1.1.2", Names: []string{"name3", "name4"}},
|
|
},
|
|
expectedTargetFileContent: targetFileContent1 + "1.1.1.1\tname1 name2\n1.1.1.2\tname3 name4\n",
|
|
},
|
|
{
|
|
name: "add entry to empty file",
|
|
baseFileContent: "",
|
|
newEntries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: "1.1.1.1\tname1 name2\n",
|
|
},
|
|
{
|
|
name: "add entry to empty file with no existing match",
|
|
baseFileContent: "",
|
|
existsEntries: HostEntries{{IP: "127.0.0.1", Names: []string{"localhost"}}},
|
|
newEntries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f, err := ioutil.TempFile(t.TempDir(), "hosts")
|
|
assert.NoErrorf(t, err, "failed to create base host file: %v", err)
|
|
defer f.Close()
|
|
hostFile := f.Name()
|
|
_, err = f.WriteString(tt.baseFileContent)
|
|
assert.NoError(t, err, "failed to write base host file: %v", err)
|
|
|
|
var st unix.Stat_t
|
|
err = unix.Stat(hostFile, &st)
|
|
assert.NoError(t, err, "stat host file: %v", err)
|
|
|
|
err = AddIfExists(hostFile, tt.existsEntries, tt.newEntries)
|
|
if tt.wantErrString != "" {
|
|
assert.ErrorContains(t, err, tt.wantErrString)
|
|
return
|
|
}
|
|
assert.NoError(t, err, "AddIfExists() failed")
|
|
|
|
content, err := ioutil.ReadFile(hostFile)
|
|
assert.NoErrorf(t, err, "failed to read host file: %v", err)
|
|
assert.Equal(t, tt.expectedTargetFileContent, string(content), "check hosts content")
|
|
|
|
var st2 unix.Stat_t
|
|
err = unix.Stat(hostFile, &st2)
|
|
assert.NoError(t, err, "stat host file: %v", err)
|
|
assert.Equal(t, st.Ino, st2.Ino, "inode before and after AddIfExists() must match")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemove(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
baseFileContent string
|
|
entries HostEntries
|
|
expectedTargetFileContent string
|
|
}{
|
|
{
|
|
name: "remove entry which does not exists",
|
|
baseFileContent: baseFileContent1Spaces,
|
|
entries: HostEntries{{IP: "1.1.1.1", Names: []string{"name1", "name2"}}},
|
|
expectedTargetFileContent: targetFileContent1,
|
|
},
|
|
{
|
|
name: "do not remove entry when only ip matches",
|
|
baseFileContent: baseFileContent2,
|
|
entries: HostEntries{{IP: "1.1.1.1", Names: []string{"new1", "new2"}}},
|
|
expectedTargetFileContent: targetFileContent2,
|
|
},
|
|
{
|
|
name: "remove two entries",
|
|
baseFileContent: baseFileContent2,
|
|
entries: HostEntries{
|
|
{IP: "1.1.1.1", Names: []string{"name1"}},
|
|
{IP: "2.2.2.2", Names: []string{"name2", "name4"}},
|
|
},
|
|
expectedTargetFileContent: targetFileContent3,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f, err := ioutil.TempFile(t.TempDir(), "hosts")
|
|
assert.NoErrorf(t, err, "failed to create base host file: %v", err)
|
|
defer f.Close()
|
|
hostFile := f.Name()
|
|
_, err = f.WriteString(tt.baseFileContent)
|
|
assert.NoError(t, err, "failed to write base host file: %v", err)
|
|
|
|
var st unix.Stat_t
|
|
err = unix.Stat(hostFile, &st)
|
|
assert.NoError(t, err, "stat host file: %v", err)
|
|
|
|
err = Remove(hostFile, tt.entries)
|
|
assert.NoError(t, err, "Remove() failed")
|
|
|
|
content, err := ioutil.ReadFile(hostFile)
|
|
assert.NoErrorf(t, err, "failed to read host file: %v", err)
|
|
assert.Equal(t, tt.expectedTargetFileContent, string(content), "check hosts content")
|
|
|
|
var st2 unix.Stat_t
|
|
err = unix.Stat(hostFile, &st2)
|
|
assert.NoError(t, err, "stat host file: %v", err)
|
|
assert.Equal(t, st.Ino, st2.Ino, "inode before and after Remove() must match")
|
|
})
|
|
}
|
|
}
|