Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 74 additions & 20 deletions providers/os/id/hostname/hostname.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package hostname

import (
"fmt"
"io"
"strings"

Expand All @@ -20,36 +21,41 @@ import (
// On Windows the `hostname` command (without the -f flag) works more reliable than `powershell -c "$env:computername"`
// since it will return a non-zero exit code.
func Hostname(conn shared.Connection, pf *inventory.Platform) (string, bool) {
var hostname string

if !pf.IsFamily(inventory.FAMILY_UNIX) && !pf.IsFamily(inventory.FAMILY_WINDOWS) {
log.Warn().Msg("your platform is not supported for hostname detection")
return hostname, false
return "", false
}

// on unix systems we try to get the hostname via `hostname -f` first since it returns the fqdn
// On unix systems we try to get the hostname via `hostname -f` first since it returns the fqdn.
if pf.IsFamily(inventory.FAMILY_UNIX) {
cmd, err := conn.RunCommand("hostname -f")
if err == nil && cmd.ExitStatus == 0 {
data, err := io.ReadAll(cmd.Stdout)
if err == nil {
return strings.TrimSpace(string(data)), true
}
} else {
log.Debug().Err(err).Msg("could not run `hostname -f` command")
fqdn, err := runCommand(conn, "hostname -f")
if err == nil && fqdn != "localhost" && fqdn != "" {
return fqdn, true
}
log.Debug().Err(err).Msg("could not detect hostname via `hostname -f` command")

// If the output of `hostname -f` is localhost, we try to fetch it via `getent hosts`,
// start with the most common protocol IPv4.
hostname, err := parseGetentHosts(conn, "127.0.0.1")
if err == nil && hostname != "" {
return hostname, true
}
log.Debug().Err(err).Str("ipversion", "IPv4").Msg("could not detect hostname")

// When IPv4 is not configured, try IPv6.
hostname, err = parseGetentHosts(conn, "::1")
if err == nil && hostname != "" {
return hostname, true
}
log.Debug().Err(err).Str("ipversion", "IPv6").Msg("could not detect hostname")
}

// This is the preferred way to get the hostname on windows, it is important to not use the -f flag here
cmd, err := conn.RunCommand("hostname")
if err == nil && cmd.ExitStatus == 0 {
data, err := io.ReadAll(cmd.Stdout)
if err == nil {
return strings.TrimSpace(string(data)), true
}
} else {
log.Debug().Err(err).Msg("could not run `hostname` command")
hostname, err := runCommand(conn, "hostname")
if err == nil && hostname != "" {
return hostname, true
}
log.Debug().Err(err).Msg("could not run `hostname` command")

// Fallback to for unix systems to /etc/hostname, since hostname command is not available on all systems
// This mechanism is also working for static analysis
Expand Down Expand Up @@ -101,3 +107,51 @@ func Hostname(conn shared.Connection, pf *inventory.Platform) (string, bool) {

return "", false
}

// runCommand is a wrapper around shared.Connection.RunCommand that helps execute commands
// and read the standard output all in one function.
func runCommand(conn shared.Connection, commandString string) (string, error) {
cmd, err := conn.RunCommand(commandString)
if err != nil && cmd.ExitStatus == 0 {
return "", err
}

data, err := io.ReadAll(cmd.Stdout)
if err != nil {
return "", err
}

return strings.TrimSpace(string(data)), nil
}

// parseGetentHosts runs `getent hosts <address>` and returns the first valid hostname
// that is not a variant of "localhost".
func parseGetentHosts(conn shared.Connection, ip string) (string, error) {
output, err := runCommand(conn, fmt.Sprintf("getent hosts %s", ip))
if err != nil {
return "", err
}

fields := strings.Fields(output)

if len(fields) < 2 {
return "", fmt.Errorf("no hostnames found for IP %s", ip)
}

for _, host := range fields[1:] {
if !isLocalhostVariant(host) {
return host, nil
}
}

return "", fmt.Errorf("no non-localhost hostname found for IP %s", ip)
}

// isLocalhostVariant returns true if the given hostname is a variant of "localhost"
func isLocalhostVariant(host string) bool {
lh := strings.ToLower(host)
return lh == "localhost" ||
lh == "localhost.localdomain" ||
lh == "ip6-localhost" ||
lh == "ip6-loopback"
}
24 changes: 24 additions & 0 deletions providers/os/id/hostname/hostname_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ func TestHostnameLinuxFqdn(t *testing.T) {
assert.Equal(t, "myhost.example.com", hostame)
}

func TestHostnameLinuxGetentIPv4(t *testing.T) {
conn, err := mock.New(0, "./testdata/hostname_getent_hosts_ipv4.toml", &inventory.Asset{})
require.NoError(t, err)
platform, ok := detector.DetectOS(conn)
require.True(t, ok)

hostame, ok := hostname.Hostname(conn, platform)
require.True(t, ok)

assert.Equal(t, "myhost.example.com", hostame)
}

func TestHostnameLinuxGetentIPv6(t *testing.T) {
conn, err := mock.New(0, "./testdata/hostname_getent_hosts_ipv6.toml", &inventory.Asset{})
require.NoError(t, err)
platform, ok := detector.DetectOS(conn)
require.True(t, ok)

hostame, ok := hostname.Hostname(conn, platform)
require.True(t, ok)

assert.Equal(t, "myhost.example.com", hostame)
}

func TestHostnameWindows(t *testing.T) {
conn, err := mock.New(0, "./testdata/hostname_windows.toml", &inventory.Asset{})
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion providers/os/id/hostname/testdata/hostname_arch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ LOGO=archlinux
"""

[files."/etc/hostname"]
content = "9be843c4be9f"
content = "9be843c4be9f"
39 changes: 39 additions & 0 deletions providers/os/id/hostname/testdata/hostname_getent_hosts_ipv4.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[commands."hostname -f"]
stdout="localhost"

[commands."getent hosts 127.0.0.1"]
stdout="127.0.0.1 localhost myhost.example.com"

[commands."hostname"]
stdout="myhost"

[commands."uname -s"]
stdout = "Linux"

[commands."uname -m"]
stdout = "x86_64"

[commands."uname -r"]
stdout = "4.9.125-linuxkit"

[files."/etc/redhat-release"]
content = "Red Hat Enterprise Linux Server release 7.2 (Maipo)"

[files."/etc/os-release"]
content = """
NAME="Red Hat Enterprise Linux Server"
VERSION="7.2 (Maipo)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="7.2"
PRETTY_NAME="Red Hat Enterprise Linux Server 7.2 (Maipo)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:7.2:GA:server"
HOME_URL="https://www.redhat.com/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"

REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"
REDHAT_BUGZILLA_PRODUCT_VERSION=7.2
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="7.2"
"""
42 changes: 42 additions & 0 deletions providers/os/id/hostname/testdata/hostname_getent_hosts_ipv6.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[commands."hostname -f"]
stdout="localhost"

[commands."getent hosts 127.0.0.1"]
stdout="127.0.0.1 localhost"

[commands."getent hosts ::1"]
stdout="::1 localhost ip6-localhost ip6-loopback myhost.example.com"

[commands."hostname"]
stdout="myhost"

[commands."uname -s"]
stdout = "Linux"

[commands."uname -m"]
stdout = "x86_64"

[commands."uname -r"]
stdout = "4.9.125-linuxkit"

[files."/etc/redhat-release"]
content = "Red Hat Enterprise Linux Server release 7.2 (Maipo)"

[files."/etc/os-release"]
content = """
NAME="Red Hat Enterprise Linux Server"
VERSION="7.2 (Maipo)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="7.2"
PRETTY_NAME="Red Hat Enterprise Linux Server 7.2 (Maipo)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:7.2:GA:server"
HOME_URL="https://www.redhat.com/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"

REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"
REDHAT_BUGZILLA_PRODUCT_VERSION=7.2
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="7.2"
"""
2 changes: 1 addition & 1 deletion providers/os/id/hostname/testdata/hostname_linux.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7"
REDHAT_BUGZILLA_PRODUCT_VERSION=7.2
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="7.2"
"""
"""
Loading