Repository: elastic/go-sysinfo
Pull request #231 (2024-07-23 16:27:51)

Do not lower-case FQDN
This PR reverts https://github.com/elastic/go-sysinfo/pull/180

Complying with ECS for `host.name` and `host.hostname` (https://www.elastic.co/guide/en/ecs/current/ecs-host.html#field-host-name) should not be handled by go-sysinfo. The users of go-sysinfo should ensure compliance with ECS if necessary.

Relates to https://github.com/elastic/beats/issues/39993

jlind23: @AndersonQ while testing did you check that: 
- host.hostname was left untouched
- host.name was lowercased
AndersonQ: > @AndersonQ while testing did you check that:
> 
> * host.hostname was left untouched
> * host.name was lowercased

Yes, I did:

![Screenshot from 2024-07-23 18-11-13](https://github.com/user-attachments/assets/69e03fea-1ff7-4d2b-b016-b7834b479934)

AndersonQ: @andrewkroh do you think it's worth adding a flag to choose to have FQDN lower-cased or not?
jlind23: @AndersonQ [ECS recommend a lowercaser FQDN](https://www.elastic.co/guide/en/ecs/current/ecs-host.html#:~:text=It%20can%20contain%20what%20hostname%20returns%20on%20Unix%20systems%2C%20the%20fully%20qualified%20domain%20name%20(FQDN)%2C%20or%20a%20name%20specified%20by%20the%20user.%20The%20recommended%20value%20is%20the%20lowercase%20FQDN%20of%20the%20host.) value so I think it should remain as is.


Patch:
diff --git a/.changelog/231.txt b/.changelog/231.txt
new file mode 100644
index 0000000..a499ec8
--- /dev/null
+++ b/.changelog/231.txt
@@ -0,0 +1,3 @@
+```release-note:breaking-change
+`Host.FQDNWithContext()` and the deprecated `Host.FQDN()` now return the FQDN as is; it isn't lowercased anymore. This also affects `types.HostInfo#Hostname` which, when it's the FQDN, won't be lowercased.
+```
diff --git a/providers/aix/host_aix_ppc64.go b/providers/aix/host_aix_ppc64.go
index 059a5f0..9af09e5 100644
--- a/providers/aix/host_aix_ppc64.go
+++ b/providers/aix/host_aix_ppc64.go
@@ -34,7 +34,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"strings"
 	"time"
 
 	"github.com/elastic/go-sysinfo/internal/registry"
@@ -191,7 +190,7 @@ func (r *reader) hostname(h *host) {
 	if r.addErr(err) {
 		return
 	}
-	h.info.Hostname = strings.ToLower(v)
+	h.info.Hostname = v
 }
 
 func (r *reader) network(h *host) {
diff --git a/providers/darwin/host_darwin.go b/providers/darwin/host_darwin.go
index 933959b..8b53eee 100644
--- a/providers/darwin/host_darwin.go
+++ b/providers/darwin/host_darwin.go
@@ -24,7 +24,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"strings"
 	"time"
 
 	"github.com/elastic/go-sysinfo/internal/registry"
@@ -226,7 +225,7 @@ func (r *reader) hostname(h *host) {
 	if r.addErr(err) {
 		return
 	}
-	h.info.Hostname = strings.ToLower(v)
+	h.info.Hostname = v
 }
 
 func (r *reader) network(h *host) {
diff --git a/providers/linux/host_linux.go b/providers/linux/host_linux.go
index 3a09f63..24e72d0 100644
--- a/providers/linux/host_linux.go
+++ b/providers/linux/host_linux.go
@@ -23,7 +23,6 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
-	"strings"
 	"time"
 
 	"github.com/prometheus/procfs"
@@ -234,7 +233,7 @@ func (r *reader) hostname(h *host) {
 	if r.addErr(err) {
 		return
 	}
-	h.info.Hostname = strings.ToLower(v)
+	h.info.Hostname = v
 }
 
 func (r *reader) network(h *host) {
diff --git a/providers/shared/fqdn.go b/providers/shared/fqdn.go
index 48043bf..b8bb455 100644
--- a/providers/shared/fqdn.go
+++ b/providers/shared/fqdn.go
@@ -51,6 +51,7 @@ func FQDNWithContext(ctx context.Context) (string, error) {
 }
 
 // FQDN just calls FQDNWithContext with a background context.
+// Deprecated.
 func FQDN() (string, error) {
 	return FQDNWithContext(context.Background())
 }
@@ -62,8 +63,18 @@ func fqdn(ctx context.Context, hostname string) (string, error) {
 		errs = fmt.Errorf("could not get FQDN, all methods failed: failed looking up CNAME: %w",
 			err)
 	}
+
 	if cname != "" {
-		return strings.ToLower(strings.TrimSuffix(cname, ".")), nil
+		cname = strings.TrimSuffix(cname, ".")
+
+		// Go might lowercase the cname "for convenience". Therefore, if cname
+		// is the same as hostname, return hostname as is.
+		// See https://github.com/golang/go/blob/go1.22.5/src/net/hosts.go#L38
+		if strings.ToLower(cname) == strings.ToLower(hostname) {
+			return hostname, nil
+		}
+
+		return cname, nil
 	}
 
 	ips, err := net.DefaultResolver.LookupIP(ctx, "ip", hostname)
@@ -76,7 +87,7 @@ func fqdn(ctx context.Context, hostname string) (string, error) {
 		if err != nil || len(names) == 0 {
 			continue
 		}
-		return strings.ToLower(strings.TrimSuffix(names[0], ".")), nil
+		return strings.TrimSuffix(names[0], "."), nil
 	}
 
 	return "", errs
diff --git a/providers/windows/host_windows.go b/providers/windows/host_windows.go
index 8cbc2ba..ed94881 100644
--- a/providers/windows/host_windows.go
+++ b/providers/windows/host_windows.go
@@ -89,7 +89,7 @@ func (h *host) FQDNWithContext(_ context.Context) (string, error) {
 		return "", fmt.Errorf("could not get windows FQDN: %s", err)
 	}
 
-	return strings.ToLower(strings.TrimSuffix(fqdn, ".")), nil
+	return strings.TrimSuffix(fqdn, "."), nil
 }
 
 func (h *host) FQDN() (string, error) {
@@ -161,7 +161,7 @@ func (r *reader) hostname(h *host) {
 	if r.addErr(err) {
 		return
 	}
-	h.info.Hostname = strings.ToLower(v)
+	h.info.Hostname = v
 }
 
 func getComputerNameEx(name uint32) (string, error) {
diff --git a/types/host.go b/types/host.go
index 9f3bc3b..9661ce7 100644
--- a/types/host.go
+++ b/types/host.go
@@ -30,7 +30,7 @@ type Host interface {
 	Info() HostInfo
 	Memory() (*HostMemoryInfo, error)
 
-	// FQDNWithContext returns the fully-qualified domain name of the host, lowercased.
+	// FQDNWithContext returns the fully-qualified domain name of the host.
 	FQDNWithContext(ctx context.Context) (string, error)
 
 	// FQDN calls FQDNWithContext with a background context.
@@ -77,7 +77,7 @@ type HostInfo struct {
 	NativeArchitecture string    `json:"native_architecture"`     // Native OS hardware architecture (e.g. x86_64, arm, ppc, mips).
 	BootTime           time.Time `json:"boot_time"`               // Host boot time.
 	Containerized      *bool     `json:"containerized,omitempty"` // Is the process containerized.
-	Hostname           string    `json:"name"`                    // Hostname, lowercased.
+	Hostname           string    `json:"name"`                    // Hostname.
 	IPs                []string  `json:"ip,omitempty"`            // List of all IPs.
 	KernelVersion      string    `json:"kernel_version"`          // Kernel version.
 	MACs               []string  `json:"mac"`                     // List of MAC addresses.


Test patch:
diff --git a/providers/shared/fqdn_test.go b/providers/shared/fqdn_test.go
index ed8bc5f..ffddd5a 100644
--- a/providers/shared/fqdn_test.go
+++ b/providers/shared/fqdn_test.go
@@ -36,7 +36,7 @@ func TestFQDN(t *testing.T) {
 		timeout          time.Duration
 	}{
 		// This test case depends on network, particularly DNS,
-		// being available.  If it starts to fail often enough
+		// being available. If it starts to fail often enough
 		// due to occasional network/DNS unavailability, we should
 		// probably just delete this test case.
 		"long_real_hostname": {
@@ -56,7 +56,7 @@ func TestFQDN(t *testing.T) {
 		},
 		"long_mixed_case_hostname": {
 			osHostname:       "eLaSTic.co",
-			expectedFQDN:     "elastic.co",
+			expectedFQDN:     "eLaSTic.co",
 			expectedErrRegex: "",
 		},
 		"nonexistent_timeout": {