diff --git a/.chloggen/mx-psi_host.ip-ref-impl.yaml b/.chloggen/mx-psi_host.ip-ref-impl.yaml new file mode 100755 index 000000000000..69b4d7b8957d --- /dev/null +++ b/.chloggen/mx-psi_host.ip-ref-impl.yaml @@ -0,0 +1,20 @@ +# Use this changelog template to create an entry for release notes. +# If your change doesn't affect end users, such as a test fix or a tooling change, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: resourcedetectionprocessor + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add detection of host.ip to system detector. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [24450] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: diff --git a/internal/metadataproviders/system/metadata.go b/internal/metadataproviders/system/metadata.go index 284b85b32255..e97b79b2092a 100644 --- a/internal/metadataproviders/system/metadata.go +++ b/internal/metadataproviders/system/metadata.go @@ -63,6 +63,9 @@ type Provider interface { // HostArch returns the host architecture HostArch() (string, error) + + // HostIPs returns the host's IP interfaces + HostIPs() ([]net.IP, error) } type systemMetadataProvider struct { @@ -159,3 +162,37 @@ func (p systemMetadataProvider) OSDescription(ctx context.Context) (string, erro func (systemMetadataProvider) HostArch() (string, error) { return internal.GOARCHtoHostArch(runtime.GOARCH), nil } + +func (p systemMetadataProvider) HostIPs() (ips []net.IP, err error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + for _, iface := range ifaces { + // skip if the interface is down or is a loopback interface + if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { + continue + } + + addrs, errAddr := iface.Addrs() + if errAddr != nil { + return nil, fmt.Errorf("failed to get addresses for interface %v: %w", iface, errAddr) + } + for _, addr := range addrs { + ip, _, parseErr := net.ParseCIDR(addr.String()) + if parseErr != nil { + return nil, fmt.Errorf("failed to parse address %q from interface %v: %w", addr, iface, parseErr) + } + + if ip.IsLoopback() { + // skip loopback IPs + continue + } + + ips = append(ips, ip) + } + + } + return ips, err +} diff --git a/processor/resourcedetectionprocessor/README.md b/processor/resourcedetectionprocessor/README.md index 87ff503f3d49..1e2deb0057ad 100644 --- a/processor/resourcedetectionprocessor/README.md +++ b/processor/resourcedetectionprocessor/README.md @@ -48,6 +48,7 @@ Queries the host machine to retrieve the following resource attributes: * host.arch * host.name * host.id + * host.ip * host.cpu.vendor.id * host.cpu.family * host.cpu.model.id diff --git a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go index da7b6f2f8bfd..121383329fc0 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go @@ -33,6 +33,7 @@ type ResourceAttributesConfig struct { HostCPUStepping ResourceAttributeConfig `mapstructure:"host.cpu.stepping"` HostCPUVendorID ResourceAttributeConfig `mapstructure:"host.cpu.vendor.id"` HostID ResourceAttributeConfig `mapstructure:"host.id"` + HostIP ResourceAttributeConfig `mapstructure:"host.ip"` HostName ResourceAttributeConfig `mapstructure:"host.name"` OsDescription ResourceAttributeConfig `mapstructure:"os.description"` OsType ResourceAttributeConfig `mapstructure:"os.type"` @@ -64,6 +65,9 @@ func DefaultResourceAttributesConfig() ResourceAttributesConfig { HostID: ResourceAttributeConfig{ Enabled: false, }, + HostIP: ResourceAttributeConfig{ + Enabled: false, + }, HostName: ResourceAttributeConfig{ Enabled: true, }, diff --git a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config_test.go b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config_test.go index 343eef8d19fb..22c74d9ed2f0 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config_test.go +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config_test.go @@ -33,6 +33,7 @@ func TestResourceAttributesConfig(t *testing.T) { HostCPUStepping: ResourceAttributeConfig{Enabled: true}, HostCPUVendorID: ResourceAttributeConfig{Enabled: true}, HostID: ResourceAttributeConfig{Enabled: true}, + HostIP: ResourceAttributeConfig{Enabled: true}, HostName: ResourceAttributeConfig{Enabled: true}, OsDescription: ResourceAttributeConfig{Enabled: true}, OsType: ResourceAttributeConfig{Enabled: true}, @@ -49,6 +50,7 @@ func TestResourceAttributesConfig(t *testing.T) { HostCPUStepping: ResourceAttributeConfig{Enabled: false}, HostCPUVendorID: ResourceAttributeConfig{Enabled: false}, HostID: ResourceAttributeConfig{Enabled: false}, + HostIP: ResourceAttributeConfig{Enabled: false}, HostName: ResourceAttributeConfig{Enabled: false}, OsDescription: ResourceAttributeConfig{Enabled: false}, OsType: ResourceAttributeConfig{Enabled: false}, diff --git a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource.go b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource.go index 6f4c3ab83d5a..0391f5824501 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource.go +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource.go @@ -77,6 +77,13 @@ func (rb *ResourceBuilder) SetHostID(val string) { } } +// SetHostIP sets provided value as "host.ip" attribute. +func (rb *ResourceBuilder) SetHostIP(val []any) { + if rb.config.HostIP.Enabled { + rb.res.Attributes().PutEmptySlice("host.ip").FromRaw(val) + } +} + // SetHostName sets provided value as "host.name" attribute. func (rb *ResourceBuilder) SetHostName(val string) { if rb.config.HostName.Enabled { diff --git a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource_test.go b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource_test.go index b7a089dc8150..38e5c08065bb 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource_test.go +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource_test.go @@ -21,6 +21,7 @@ func TestResourceBuilder(t *testing.T) { rb.SetHostCPUStepping(17) rb.SetHostCPUVendorID("host.cpu.vendor.id-val") rb.SetHostID("host.id-val") + rb.SetHostIP([]any{"host.ip-item1", "host.ip-item2"}) rb.SetHostName("host.name-val") rb.SetOsDescription("os.description-val") rb.SetOsType("os.type-val") @@ -32,7 +33,7 @@ func TestResourceBuilder(t *testing.T) { case "default": assert.Equal(t, 2, res.Attributes().Len()) case "all_set": - assert.Equal(t, 11, res.Attributes().Len()) + assert.Equal(t, 12, res.Attributes().Len()) case "none_set": assert.Equal(t, 0, res.Attributes().Len()) return @@ -80,6 +81,11 @@ func TestResourceBuilder(t *testing.T) { if ok { assert.EqualValues(t, "host.id-val", val.Str()) } + val, ok = res.Attributes().Get("host.ip") + assert.Equal(t, test == "all_set", ok) + if ok { + assert.EqualValues(t, []any{"host.ip-item1", "host.ip-item2"}, val.Slice().AsRaw()) + } val, ok = res.Attributes().Get("host.name") assert.True(t, ok) if ok { diff --git a/processor/resourcedetectionprocessor/internal/system/internal/metadata/testdata/config.yaml b/processor/resourcedetectionprocessor/internal/system/internal/metadata/testdata/config.yaml index 94a441d4590e..d258480c7b00 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/testdata/config.yaml +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/testdata/config.yaml @@ -17,6 +17,8 @@ all_set: enabled: true host.id: enabled: true + host.ip: + enabled: true host.name: enabled: true os.description: @@ -41,6 +43,8 @@ none_set: enabled: false host.id: enabled: false + host.ip: + enabled: false host.name: enabled: false os.description: diff --git a/processor/resourcedetectionprocessor/internal/system/metadata.yaml b/processor/resourcedetectionprocessor/internal/system/metadata.yaml index 6cf860cebc71..3b5acbcbef55 100644 --- a/processor/resourcedetectionprocessor/internal/system/metadata.yaml +++ b/processor/resourcedetectionprocessor/internal/system/metadata.yaml @@ -23,6 +23,10 @@ resource_attributes: description: The host.arch type: string enabled: false + host.ip: + description: IP addresses for the host + type: slice + enabled: false host.cpu.vendor.id: description: The host.cpu.vendor.id type: string diff --git a/processor/resourcedetectionprocessor/internal/system/system.go b/processor/resourcedetectionprocessor/internal/system/system.go index c7e5c754974d..a62eab4287e7 100644 --- a/processor/resourcedetectionprocessor/internal/system/system.go +++ b/processor/resourcedetectionprocessor/internal/system/system.go @@ -83,6 +83,17 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem return pcommon.NewResource(), "", fmt.Errorf("failed getting host architecture: %w", err) } + var hostIPAttribute []any + if d.cfg.ResourceAttributes.HostIP.Enabled { + hostIPs, errIPs := d.provider.HostIPs() + if errIPs != nil { + return pcommon.NewResource(), "", fmt.Errorf("failed getting host IP addresses: %w", errIPs) + } + for _, ip := range hostIPs { + hostIPAttribute = append(hostIPAttribute, ip.String()) + } + } + osDescription, err := d.provider.OSDescription(ctx) if err != nil { return pcommon.NewResource(), "", fmt.Errorf("failed getting OS description: %w", err) @@ -107,6 +118,7 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem } } d.rb.SetHostArch(hostArch) + d.rb.SetHostIP(hostIPAttribute) d.rb.SetOsDescription(osDescription) if len(cpuInfo) > 0 { err = setHostCPUInfo(d, cpuInfo[0]) diff --git a/processor/resourcedetectionprocessor/internal/system/system_test.go b/processor/resourcedetectionprocessor/internal/system/system_test.go index 03d8ef665064..497f134ddf8f 100644 --- a/processor/resourcedetectionprocessor/internal/system/system_test.go +++ b/processor/resourcedetectionprocessor/internal/system/system_test.go @@ -6,6 +6,7 @@ package system import ( "context" "errors" + "net" "testing" "github.com/stretchr/testify/assert" @@ -66,6 +67,16 @@ func (m *mockMetadata) ReverseLookupHost() (string, error) { return args.String(0), args.Error(1) } +func (m *mockMetadata) HostIPs() ([]net.IP, error) { + args := m.MethodCalled("HostIPs") + return args.Get(0).([]net.IP), args.Error(1) +} + +var ( + testIPsAttribute = []any{"192.168.1.140", "fe80::abc2:4a28:737a:609e"} + testIPsAddresses = []net.IP{net.ParseIP(testIPsAttribute[0].(string)), net.ParseIP(testIPsAttribute[1].(string))} +) + func TestNewDetector(t *testing.T) { tests := []struct { name string @@ -97,6 +108,7 @@ func allEnabledConfig() metadata.ResourceAttributesConfig { cfg := metadata.DefaultResourceAttributesConfig() cfg.HostArch.Enabled = true cfg.HostID.Enabled = true + cfg.HostIP.Enabled = true cfg.OsDescription.Enabled = true return cfg } @@ -108,6 +120,7 @@ func TestDetectFQDNAvailable(t *testing.T) { md.On("OSType").Return("darwin", nil) md.On("HostID").Return("2", nil) md.On("HostArch").Return("amd64", nil) + md.On("HostIPs").Return(testIPsAddresses, nil) detector := newTestDetector(md, []string{"dns"}, allEnabledConfig()) res, schemaURL, err := detector.Detect(context.Background()) @@ -121,6 +134,7 @@ func TestDetectFQDNAvailable(t *testing.T) { conventions.AttributeOSType: "darwin", conventions.AttributeHostID: "2", conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, + "host.ip": testIPsAttribute, } assert.Equal(t, expected, res.Attributes().AsRaw()) @@ -141,6 +155,7 @@ func TestFallbackHostname(t *testing.T) { assert.Equal(t, conventions.SchemaURL, schemaURL) mdHostname.AssertExpectations(t) mdHostname.AssertNotCalled(t, "HostID") + mdHostname.AssertNotCalled(t, "HostIPs") expected := map[string]any{ conventions.AttributeHostName: "hostname", @@ -158,6 +173,7 @@ func TestEnableHostID(t *testing.T) { mdHostname.On("OSType").Return("darwin", nil) mdHostname.On("HostID").Return("3", nil) mdHostname.On("HostArch").Return("amd64", nil) + mdHostname.On("HostIPs").Return(testIPsAddresses, nil) detector := newTestDetector(mdHostname, []string{"dns", "os"}, allEnabledConfig()) res, schemaURL, err := detector.Detect(context.Background()) @@ -171,6 +187,7 @@ func TestEnableHostID(t *testing.T) { conventions.AttributeOSType: "darwin", conventions.AttributeHostID: "3", conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, + "host.ip": testIPsAttribute, } assert.Equal(t, expected, res.Attributes().AsRaw()) @@ -183,6 +200,7 @@ func TestUseHostname(t *testing.T) { mdHostname.On("OSType").Return("darwin", nil) mdHostname.On("HostID").Return("1", nil) mdHostname.On("HostArch").Return("amd64", nil) + mdHostname.On("HostIPs").Return(testIPsAddresses, nil) detector := newTestDetector(mdHostname, []string{"os"}, allEnabledConfig()) res, schemaURL, err := detector.Detect(context.Background()) @@ -196,6 +214,7 @@ func TestUseHostname(t *testing.T) { conventions.AttributeOSType: "darwin", conventions.AttributeHostID: "1", conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, + "host.ip": testIPsAttribute, } assert.Equal(t, expected, res.Attributes().AsRaw()) @@ -210,6 +229,7 @@ func TestDetectError(t *testing.T) { mdFQDN.On("Hostname").Return("", errors.New("err")) mdFQDN.On("HostID").Return("", errors.New("err")) mdFQDN.On("HostArch").Return("amd64", nil) + mdFQDN.On("HostIPs").Return(testIPsAddresses, nil) detector := newTestDetector(mdFQDN, []string{"dns"}, allEnabledConfig()) res, schemaURL, err := detector.Detect(context.Background()) @@ -224,6 +244,7 @@ func TestDetectError(t *testing.T) { mdHostname.On("Hostname").Return("", errors.New("err")) mdHostname.On("HostID").Return("", errors.New("err")) mdHostname.On("HostArch").Return("amd64", nil) + mdHostname.On("HostIPs").Return(testIPsAddresses, nil) detector = newTestDetector(mdHostname, []string{"os"}, allEnabledConfig()) res, schemaURL, err = detector.Detect(context.Background()) @@ -238,6 +259,7 @@ func TestDetectError(t *testing.T) { mdOSType.On("OSType").Return("", errors.New("err")) mdOSType.On("HostID").Return("1", nil) mdOSType.On("HostArch").Return("amd64", nil) + mdOSType.On("HostIPs").Return(testIPsAddresses, nil) detector = newTestDetector(mdOSType, []string{"os"}, allEnabledConfig()) res, schemaURL, err = detector.Detect(context.Background()) @@ -252,6 +274,7 @@ func TestDetectError(t *testing.T) { mdHostID.On("OSType").Return("linux", nil) mdHostID.On("HostID").Return("", errors.New("err")) mdHostID.On("HostArch").Return("arm64", nil) + mdHostID.On("HostIPs").Return(testIPsAddresses, nil) detector = newTestDetector(mdHostID, []string{"os"}, allEnabledConfig()) res, schemaURL, err = detector.Detect(context.Background()) @@ -262,6 +285,7 @@ func TestDetectError(t *testing.T) { conventions.AttributeOSDescription: "Ubuntu 22.04.2 LTS (Jammy Jellyfish)", conventions.AttributeOSType: "linux", conventions.AttributeHostArch: conventions.AttributeHostArchARM64, + "host.ip": testIPsAttribute, }, res.Attributes().AsRaw()) }