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
2 changes: 1 addition & 1 deletion comp/metadata/hostsysteminfo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The payload contains physical system identification attributes collected from th

The system information is collected using platform-specific APIs:
- **Windows**: WMI queries (`Win32_ComputerSystem`, `Win32_BIOS`, `Win32_SystemEnclosure`)
- **MacOS**: Work in progress
- **MacOS**: IOKit queries (`IOPlatformExpertDevice`, `product`)
- **Linux/Unix**: Will not run as it is currently not implemented

Collection includes:
Expand Down
14 changes: 7 additions & 7 deletions comp/metadata/hostsysteminfo/impl/hostsysteminfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,19 @@ func NewSystemInfoProvider(deps Requires) Provides {
hh.InventoryPayload.MinInterval = 1 * time.Hour
hh.InventoryPayload.MaxInterval = 1 * time.Hour

// Only enable system info metadata collection for end user device infrastructure mode on Windows
// Only enable system info metadata collection for end user device infrastructure mode on Windows and Darwin
infraMode := deps.Config.GetString("infrastructure_mode")
isEndUserDevice := infraMode == "end_user_device"
isWindows := runtime.GOOS == "windows"
hh.InventoryPayload.Enabled = hh.InventoryPayload.Enabled && isEndUserDevice && isWindows
isSupportedOS := runtime.GOOS == "windows" || runtime.GOOS == "darwin"
hh.InventoryPayload.Enabled = hh.InventoryPayload.Enabled && isEndUserDevice && isSupportedOS

var provider runnerimpl.Provider
if hh.InventoryPayload.Enabled {
provider = hh.MetadataProvider()
deps.Log.Info("System info metadata collection enabled for end user device mode")
} else {
if !isWindows {
deps.Log.Debugf("System info metadata collection disabled: only supported on Windows (current OS: %s)", runtime.GOOS)
if !isSupportedOS {
deps.Log.Debugf("System info metadata collection disabled: only supported on Windows and macOS (current OS: %s)", runtime.GOOS)
} else {
deps.Log.Debugf("System info metadata collection disabled: infrastructure_mode is '%s' (requires 'end_user_device')", infraMode)
}
Expand All @@ -116,8 +116,8 @@ func NewSystemInfoProvider(deps Requires) Provides {
}

func (hh *hostSystemInfo) fillData() error {
// System info collection is only supported on Windows
if runtime.GOOS != "windows" {
// System info collection is only supported on Windows and Darwin
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" {
hh.log.Debugf("System information collection not supported on %s", runtime.GOOS)
hh.data = nil
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build test && !windows
//go:build test && !windows && !darwin

package hostsysteminfoimpl

Expand All @@ -16,6 +16,6 @@ import (
func TestNewSystemInfoProvider_EndUserDeviceMode(t *testing.T) {

hh := getTestHostSystemInfo(t, nil)
// Should be disabled for non-Windows platforms
assert.False(t, hh.InventoryPayload.Enabled, "Should not be enabled for non-Windows platforms")
// Should be disabled for non-Windows and non-Darwin platforms
assert.False(t, hh.InventoryPayload.Enabled, "Should not be enabled for non-Windows and non-Darwin platforms")
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build test && windows
//go:build test && (windows || darwin)

package hostsysteminfoimpl

Expand Down Expand Up @@ -88,12 +88,12 @@ func TestPayloadMarshalJSON(t *testing.T) {
Timestamp: time.Now().UnixNano(),
UUID: "test-uuid-12345",
Metadata: &hostSystemInfoMetadata{
Manufacturer: "Lenovo",
ModelNumber: "Thinkpad T14s",
SerialNumber: "ABC123XYZ",
ModelName: "Thinkpad",
ChassisType: "Laptop",
Identifier: "SKU123",
Manufacturer: "Test Manufacturer",
ModelNumber: "Test Model",
SerialNumber: "TEST123",
ModelName: "Test Name",
ChassisType: "Desktop",
Identifier: "ID123",
},
}

Expand All @@ -114,12 +114,12 @@ func TestPayloadMarshalJSON(t *testing.T) {

// Verify metadata fields
metadata := result["host_system_info_metadata"].(map[string]interface{})
assert.Equal(t, "Lenovo", metadata["manufacturer"])
assert.Equal(t, "Thinkpad T14s", metadata["model_number"])
assert.Equal(t, "ABC123XYZ", metadata["serial_number"])
assert.Equal(t, "Thinkpad", metadata["model_name"])
assert.Equal(t, "Laptop", metadata["chassis_type"])
assert.Equal(t, "SKU123", metadata["identifier"])
assert.Equal(t, "Test Manufacturer", metadata["manufacturer"])
assert.Equal(t, "Test Model", metadata["model_number"])
assert.Equal(t, "TEST123", metadata["serial_number"])
assert.Equal(t, "Test Name", metadata["model_name"])
assert.Equal(t, "Desktop", metadata["chassis_type"])
assert.Equal(t, "ID123", metadata["identifier"])
}

func TestFillData(t *testing.T) {
Expand Down
67 changes: 67 additions & 0 deletions pkg/inventory/systeminfo/collector_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build darwin

package systeminfo

/*
#cgo CFLAGS: -x objective-c -fobjc-arc
#cgo LDFLAGS: -framework Foundation -framework IOKit

#include <stdlib.h>
#include "systeminfo_darwin.h"
*/
import "C"
import (
"strings"
"unsafe"
)

func collect() (*SystemInfo, error) {
cInfo := C.getDeviceInfo()
defer C.free(unsafe.Pointer(cInfo.modelIdentifier))
defer C.free(unsafe.Pointer(cInfo.modelNumber))
defer C.free(unsafe.Pointer(cInfo.productName))
defer C.free(unsafe.Pointer(cInfo.serialNumber))

return &SystemInfo{
Manufacturer: "Apple Inc.",
ModelNumber: C.GoString(cInfo.modelNumber),
SerialNumber: C.GoString(cInfo.serialNumber),
ModelName: C.GoString(cInfo.productName),
ChassisType: getChassisType(C.GoString(cInfo.productName), C.GoString(cInfo.modelIdentifier)),
Identifier: C.GoString(cInfo.modelIdentifier),
}, nil
}

func getChassisType(productName string, modelIdentifier string) string {
lowerName := strings.ToLower(productName)
lowerModel := strings.ToLower(modelIdentifier)

// Check for virtual machines first
// VMware VMs have modelIdentifier like "VMware7,1"
// Apple Silicon VMs have modelIdentifier like "VirtualMac2,1" and productName "Apple Virtual Machine 1"
// Parallels VMs have "Parallels" in the modelIdentifier
if strings.Contains(lowerModel, "vmware") ||
strings.Contains(lowerModel, "virtual") ||
strings.Contains(lowerModel, "parallels") ||
strings.Contains(lowerName, "virtual") {
return "Virtual Machine"
}

if strings.HasPrefix(lowerName, "macbook") {
return "Laptop"
}

if strings.HasPrefix(lowerName, "imac") ||
strings.HasPrefix(lowerName, "mac mini") ||
strings.HasPrefix(lowerName, "mac pro") ||
strings.HasPrefix(lowerName, "mac studio") {
return "Desktop"
}

return "Other"
}
177 changes: 177 additions & 0 deletions pkg/inventory/systeminfo/collector_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build darwin

package systeminfo

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCollect(t *testing.T) {
info, err := Collect()
require.NoError(t, err, "Collect should not return an error")
require.NotNil(t, info, "Collect should return system info")

// On Darwin, manufacturer should always be Apple Inc.
assert.Equal(t, "Apple Inc.", info.Manufacturer, "Manufacturer should be Apple Inc.")

// Verify that required fields are populated
// Note: We don't test exact values as they depend on the test machine
t.Logf("Collected System Info:")
t.Logf(" Manufacturer: %s", info.Manufacturer)
t.Logf(" ModelNumber: %s", info.ModelNumber)
t.Logf(" SerialNumber: %s", info.SerialNumber)
t.Logf(" ModelName: %s", info.ModelName)
t.Logf(" ChassisType: %s", info.ChassisType)
t.Logf(" Identifier: %s", info.Identifier)

// Chassis type should be one of the expected values
validChassisTypes := []string{"Laptop", "Desktop", "Virtual Machine", "Other"}
assert.Contains(t, validChassisTypes, info.ChassisType, "ChassisType should be a valid type")
}

func TestGetChassisType(t *testing.T) {
tests := []struct {
name string
productName string
modelIdentifier string
expected string
}{
{
name: "MacBook Pro",
productName: "MacBook Pro",
modelIdentifier: "MacBookPro18,1",
expected: "Laptop",
},
{
name: "MacBook Air",
productName: "MacBook Air",
modelIdentifier: "MacBookAir10,1",
expected: "Laptop",
},
{
name: "MacBook (generic)",
productName: "MacBook",
modelIdentifier: "MacBook10,1",
expected: "Laptop",
},
{
name: "iMac",
productName: "iMac",
modelIdentifier: "iMac21,1",
expected: "Desktop",
},
{
name: "Mac mini",
productName: "Mac mini",
modelIdentifier: "Macmini9,1",
expected: "Desktop",
},
{
name: "Mac Pro",
productName: "Mac Pro",
modelIdentifier: "MacPro7,1",
expected: "Desktop",
},
{
name: "Mac Studio",
productName: "Mac Studio",
modelIdentifier: "Mac13,1",
expected: "Desktop",
},
{
name: "VMware VM",
productName: "VMware Virtual Platform",
modelIdentifier: "VMware7,1",
expected: "Virtual Machine",
},
{
name: "VMware VM (mixed case)",
productName: "VMware Virtual Platform",
modelIdentifier: "vmware7,1",
expected: "Virtual Machine",
},
{
name: "Apple Virtual Machine",
productName: "Apple Virtual Machine 1",
modelIdentifier: "VirtualMac2,1",
expected: "Virtual Machine",
},
{
name: "Virtual in model identifier",
productName: "Some Mac",
modelIdentifier: "VirtualMac2,1",
expected: "Virtual Machine",
},
{
name: "Virtual in product name",
productName: "Virtual Device",
modelIdentifier: "Mac1,1",
expected: "Virtual Machine",
},
{
name: "Parallels VM",
productName: "Parallels Virtual Platform",
modelIdentifier: "Parallels-ARM",
expected: "Virtual Machine",
},
{
name: "Parallels in identifier (mixed case)",
productName: "Some Device",
modelIdentifier: "parallels-x86",
expected: "Virtual Machine",
},
{
name: "Unknown Apple device",
productName: "Apple Device",
modelIdentifier: "Unknown1,1",
expected: "Other",
},
{
name: "Empty strings",
productName: "",
modelIdentifier: "",
expected: "Other",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getChassisType(tt.productName, tt.modelIdentifier)
assert.Equal(t, tt.expected, result, "getChassisType(%q, %q) = %q, want %q",
tt.productName, tt.modelIdentifier, result, tt.expected)
})
}
}

func TestGetChassisType_CaseInsensitive(t *testing.T) {
// Test that the function handles different cases correctly
testCases := []struct {
name string
productName string
modelIdentifier string
expected string
}{
{"Uppercase MacBook", "MACBOOK PRO", "MacBookPro18,1", "Laptop"},
{"Lowercase macbook", "macbook pro", "MacBookPro18,1", "Laptop"},
{"Mixed case macBook", "macBook Pro", "MacBookPro18,1", "Laptop"},
{"Uppercase iMac", "IMAC", "iMac21,1", "Desktop"},
{"Lowercase imac", "imac", "iMac21,1", "Desktop"},
{"Uppercase VM identifier", "Some Device", "VMWARE7,1", "Virtual Machine"},
{"Lowercase vm identifier", "Some Device", "vmware7,1", "Virtual Machine"},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
result := getChassisType(tt.productName, tt.modelIdentifier)
assert.Equal(t, tt.expected, result)
})
}
}
2 changes: 1 addition & 1 deletion pkg/inventory/systeminfo/collector_nix.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build !windows
//go:build !windows && !darwin

package systeminfo

Expand Down
15 changes: 15 additions & 0 deletions pkg/inventory/systeminfo/systeminfo_darwin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef SYSTEMINFO_DARWIN_H
#define SYSTEMINFO_DARWIN_H

#include <stdbool.h>

typedef struct {
char *modelNumber;
char *serialNumber;
char *productName;
char *modelIdentifier;
} DeviceInfo;

DeviceInfo getDeviceInfo(void);

#endif
Loading