diff --git a/.chloggen/mx-psi_os-description.yaml b/.chloggen/mx-psi_os-description.yaml new file mode 100755 index 000000000000..65521bcf5c31 --- /dev/null +++ b/.chloggen/mx-psi_os-description.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 os.description 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: [24541] + +# (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 8568ed5216f1..284b85b32255 100644 --- a/internal/metadataproviders/system/metadata.go +++ b/internal/metadataproviders/system/metadata.go @@ -13,6 +13,7 @@ import ( "github.com/Showmax/go-fqdn" conventions "go.opentelemetry.io/collector/semconv/v1.6.1" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/metadataproviders/internal" @@ -45,6 +46,9 @@ type Provider interface { // FQDN returns the fully qualified domain name FQDN() (string, error) + // OSDescription returns a human readable description of the OS. + OSDescription(ctx context.Context) (string, error) + // OSType returns the host operating system OSType() (string, error) @@ -123,29 +127,33 @@ func (p systemMetadataProvider) reverseLookup(ipAddresses []string) (string, err return "", fmt.Errorf("reverseLookup failed to convert IP addresses to name: %w", err) } -func (p systemMetadataProvider) HostID(ctx context.Context) (string, error) { - res, err := p.newResource(ctx, - resource.WithHostID(), - ) - +func (p systemMetadataProvider) fromOption(ctx context.Context, opt resource.Option, semconv string) (string, error) { + res, err := p.newResource(ctx, opt) if err != nil { - return "", fmt.Errorf("failed to obtain host id: %w", err) + return "", fmt.Errorf("failed to obtain %q: %w", semconv, err) } iter := res.Iter() - for iter.Next() { - if iter.Attribute().Key == conventions.AttributeHostID { + if iter.Attribute().Key == attribute.Key(semconv) { v := iter.Attribute().Value.Emit() if v == "" { - return "", fmt.Errorf("empty host id") + return "", fmt.Errorf("empty %q", semconv) } return v, nil } } - return "", fmt.Errorf("failed to obtain host id") + return "", fmt.Errorf("failed to obtain %q", semconv) +} + +func (p systemMetadataProvider) HostID(ctx context.Context) (string, error) { + return p.fromOption(ctx, resource.WithHostID(), conventions.AttributeHostID) +} + +func (p systemMetadataProvider) OSDescription(ctx context.Context) (string, error) { + return p.fromOption(ctx, resource.WithOSDescription(), conventions.AttributeOSDescription) } func (systemMetadataProvider) HostArch() (string, error) { diff --git a/internal/metadataproviders/system/metadata_test.go b/internal/metadataproviders/system/metadata_test.go index 3f28091a0ebb..559304f99601 100644 --- a/internal/metadataproviders/system/metadata_test.go +++ b/internal/metadataproviders/system/metadata_test.go @@ -62,14 +62,14 @@ func TestHostID(t *testing.T) { name: "empty host.id", resValue: "", resError: nil, - err: "failed to obtain host id", + err: `failed to obtain "host.id"`, expected: "", }, { name: "error", resValue: "", resError: fmt.Errorf("some error"), - err: "failed to obtain host id: some error", + err: `failed to obtain "host.id": some error`, expected: "", }, } diff --git a/processor/resourcedetectionprocessor/README.md b/processor/resourcedetectionprocessor/README.md index 05108f053ee2..9288e47b62c7 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 + * os.description * os.type By default `host.name` is being set to FQDN if possible, and a hostname provided by OS used as fallback. diff --git a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go index 1b35a15785c1..3abbed5b3b13 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config.go @@ -9,10 +9,11 @@ type ResourceAttributeConfig struct { // ResourceAttributesConfig provides config for resourcedetectionprocessor/system resource attributes. type ResourceAttributesConfig struct { - HostArch ResourceAttributeConfig `mapstructure:"host.arch"` - HostID ResourceAttributeConfig `mapstructure:"host.id"` - HostName ResourceAttributeConfig `mapstructure:"host.name"` - OsType ResourceAttributeConfig `mapstructure:"os.type"` + HostArch ResourceAttributeConfig `mapstructure:"host.arch"` + HostID ResourceAttributeConfig `mapstructure:"host.id"` + HostName ResourceAttributeConfig `mapstructure:"host.name"` + OsDescription ResourceAttributeConfig `mapstructure:"os.description"` + OsType ResourceAttributeConfig `mapstructure:"os.type"` } func DefaultResourceAttributesConfig() ResourceAttributesConfig { @@ -26,6 +27,9 @@ func DefaultResourceAttributesConfig() ResourceAttributesConfig { HostName: ResourceAttributeConfig{ Enabled: true, }, + OsDescription: ResourceAttributeConfig{ + Enabled: false, + }, OsType: 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 51531c7c2525..afa17c54c8a9 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config_test.go +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_config_test.go @@ -25,19 +25,21 @@ func TestResourceAttributesConfig(t *testing.T) { { name: "all_set", want: ResourceAttributesConfig{ - HostArch: ResourceAttributeConfig{Enabled: true}, - HostID: ResourceAttributeConfig{Enabled: true}, - HostName: ResourceAttributeConfig{Enabled: true}, - OsType: ResourceAttributeConfig{Enabled: true}, + HostArch: ResourceAttributeConfig{Enabled: true}, + HostID: ResourceAttributeConfig{Enabled: true}, + HostName: ResourceAttributeConfig{Enabled: true}, + OsDescription: ResourceAttributeConfig{Enabled: true}, + OsType: ResourceAttributeConfig{Enabled: true}, }, }, { name: "none_set", want: ResourceAttributesConfig{ - HostArch: ResourceAttributeConfig{Enabled: false}, - HostID: ResourceAttributeConfig{Enabled: false}, - HostName: ResourceAttributeConfig{Enabled: false}, - OsType: ResourceAttributeConfig{Enabled: false}, + HostArch: ResourceAttributeConfig{Enabled: false}, + HostID: 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 4c3acdf9edb7..b28dc2be6b0c 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource.go +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource.go @@ -42,6 +42,13 @@ func (rb *ResourceBuilder) SetHostName(val string) { } } +// SetOsDescription sets provided value as "os.description" attribute. +func (rb *ResourceBuilder) SetOsDescription(val string) { + if rb.config.OsDescription.Enabled { + rb.res.Attributes().PutStr("os.description", val) + } +} + // SetOsType sets provided value as "os.type" attribute. func (rb *ResourceBuilder) SetOsType(val string) { if rb.config.OsType.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 08a365c9980c..6d9bce9ea390 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource_test.go +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/generated_resource_test.go @@ -16,6 +16,7 @@ func TestResourceBuilder(t *testing.T) { rb.SetHostArch("host.arch-val") rb.SetHostID("host.id-val") rb.SetHostName("host.name-val") + rb.SetOsDescription("os.description-val") rb.SetOsType("os.type-val") res := rb.Emit() @@ -25,7 +26,7 @@ func TestResourceBuilder(t *testing.T) { case "default": assert.Equal(t, 2, res.Attributes().Len()) case "all_set": - assert.Equal(t, 4, res.Attributes().Len()) + assert.Equal(t, 5, res.Attributes().Len()) case "none_set": assert.Equal(t, 0, res.Attributes().Len()) return @@ -48,6 +49,11 @@ func TestResourceBuilder(t *testing.T) { if ok { assert.EqualValues(t, "host.name-val", val.Str()) } + val, ok = res.Attributes().Get("os.description") + assert.Equal(t, test == "all_set", ok) + if ok { + assert.EqualValues(t, "os.description-val", val.Str()) + } val, ok = res.Attributes().Get("os.type") 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 810e8a23e79e..83e04b4548f7 100644 --- a/processor/resourcedetectionprocessor/internal/system/internal/metadata/testdata/config.yaml +++ b/processor/resourcedetectionprocessor/internal/system/internal/metadata/testdata/config.yaml @@ -7,6 +7,8 @@ all_set: enabled: true host.name: enabled: true + os.description: + enabled: true os.type: enabled: true none_set: @@ -17,5 +19,7 @@ none_set: enabled: false host.name: enabled: false + os.description: + enabled: false os.type: enabled: false diff --git a/processor/resourcedetectionprocessor/internal/system/metadata.yaml b/processor/resourcedetectionprocessor/internal/system/metadata.yaml index 943beea7fb7e..f34204318193 100644 --- a/processor/resourcedetectionprocessor/internal/system/metadata.yaml +++ b/processor/resourcedetectionprocessor/internal/system/metadata.yaml @@ -11,6 +11,10 @@ resource_attributes: description: The host.id type: string enabled: false + os.description: + description: Human readable OS version information. + type: string + enabled: false os.type: description: The os.type type: string diff --git a/processor/resourcedetectionprocessor/internal/system/system.go b/processor/resourcedetectionprocessor/internal/system/system.go index ef08602d22f3..0366b6288709 100644 --- a/processor/resourcedetectionprocessor/internal/system/system.go +++ b/processor/resourcedetectionprocessor/internal/system/system.go @@ -69,6 +69,11 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem return pcommon.NewResource(), "", fmt.Errorf("failed getting host architecture: %w", err) } + osDescription, err := d.provider.OSDescription(ctx) + if err != nil { + return pcommon.NewResource(), "", fmt.Errorf("failed getting OS description: %w", err) + } + for _, source := range d.cfg.HostnameSources { getHostFromSource := hostnameSourcesMap[source] hostname, err = getHostFromSource(d) @@ -83,6 +88,7 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem } } d.rb.SetHostArch(hostArch) + d.rb.SetOsDescription(osDescription) return d.rb.Emit(), conventions.SchemaURL, nil } d.logger.Debug(err.Error()) diff --git a/processor/resourcedetectionprocessor/internal/system/system_test.go b/processor/resourcedetectionprocessor/internal/system/system_test.go index 22f43b6d6b67..03d8ef665064 100644 --- a/processor/resourcedetectionprocessor/internal/system/system_test.go +++ b/processor/resourcedetectionprocessor/internal/system/system_test.go @@ -36,6 +36,11 @@ func (m *mockMetadata) FQDN() (string, error) { return args.String(0), args.Error(1) } +func (m *mockMetadata) OSDescription(_ context.Context) (string, error) { + args := m.MethodCalled("OSDescription") + return args.String(0), args.Error(1) +} + func (m *mockMetadata) OSType() (string, error) { args := m.MethodCalled("OSType") return args.String(0), args.Error(1) @@ -92,12 +97,14 @@ func allEnabledConfig() metadata.ResourceAttributesConfig { cfg := metadata.DefaultResourceAttributesConfig() cfg.HostArch.Enabled = true cfg.HostID.Enabled = true + cfg.OsDescription.Enabled = true return cfg } func TestDetectFQDNAvailable(t *testing.T) { md := &mockMetadata{} md.On("FQDN").Return("fqdn", nil) + md.On("OSDescription").Return("Ubuntu 22.04.2 LTS (Jammy Jellyfish)", nil) md.On("OSType").Return("darwin", nil) md.On("HostID").Return("2", nil) md.On("HostArch").Return("amd64", nil) @@ -109,10 +116,11 @@ func TestDetectFQDNAvailable(t *testing.T) { md.AssertExpectations(t) expected := map[string]any{ - conventions.AttributeHostName: "fqdn", - conventions.AttributeOSType: "darwin", - conventions.AttributeHostID: "2", - conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, + conventions.AttributeHostName: "fqdn", + conventions.AttributeOSDescription: "Ubuntu 22.04.2 LTS (Jammy Jellyfish)", + conventions.AttributeOSType: "darwin", + conventions.AttributeHostID: "2", + conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, } assert.Equal(t, expected, res.Attributes().AsRaw()) @@ -123,6 +131,7 @@ func TestFallbackHostname(t *testing.T) { mdHostname := &mockMetadata{} mdHostname.On("Hostname").Return("hostname", nil) mdHostname.On("FQDN").Return("", errors.New("err")) + mdHostname.On("OSDescription").Return("Ubuntu 22.04.2 LTS (Jammy Jellyfish)", nil) mdHostname.On("OSType").Return("darwin", nil) mdHostname.On("HostArch").Return("amd64", nil) @@ -145,6 +154,7 @@ func TestEnableHostID(t *testing.T) { mdHostname := &mockMetadata{} mdHostname.On("Hostname").Return("hostname", nil) mdHostname.On("FQDN").Return("", errors.New("err")) + mdHostname.On("OSDescription").Return("Ubuntu 22.04.2 LTS (Jammy Jellyfish)", nil) mdHostname.On("OSType").Return("darwin", nil) mdHostname.On("HostID").Return("3", nil) mdHostname.On("HostArch").Return("amd64", nil) @@ -156,10 +166,11 @@ func TestEnableHostID(t *testing.T) { mdHostname.AssertExpectations(t) expected := map[string]any{ - conventions.AttributeHostName: "hostname", - conventions.AttributeOSType: "darwin", - conventions.AttributeHostID: "3", - conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, + conventions.AttributeHostName: "hostname", + conventions.AttributeOSDescription: "Ubuntu 22.04.2 LTS (Jammy Jellyfish)", + conventions.AttributeOSType: "darwin", + conventions.AttributeHostID: "3", + conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, } assert.Equal(t, expected, res.Attributes().AsRaw()) @@ -168,6 +179,7 @@ func TestEnableHostID(t *testing.T) { func TestUseHostname(t *testing.T) { mdHostname := &mockMetadata{} mdHostname.On("Hostname").Return("hostname", nil) + mdHostname.On("OSDescription").Return("Ubuntu 22.04.2 LTS (Jammy Jellyfish)", nil) mdHostname.On("OSType").Return("darwin", nil) mdHostname.On("HostID").Return("1", nil) mdHostname.On("HostArch").Return("amd64", nil) @@ -179,10 +191,11 @@ func TestUseHostname(t *testing.T) { mdHostname.AssertExpectations(t) expected := map[string]any{ - conventions.AttributeHostName: "hostname", - conventions.AttributeOSType: "darwin", - conventions.AttributeHostID: "1", - conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, + conventions.AttributeHostName: "hostname", + conventions.AttributeOSDescription: "Ubuntu 22.04.2 LTS (Jammy Jellyfish)", + conventions.AttributeOSType: "darwin", + conventions.AttributeHostID: "1", + conventions.AttributeHostArch: conventions.AttributeHostArchAMD64, } assert.Equal(t, expected, res.Attributes().AsRaw()) @@ -191,6 +204,7 @@ func TestUseHostname(t *testing.T) { func TestDetectError(t *testing.T) { // FQDN and hostname fail with 'hostnameSources' set to 'dns' mdFQDN := &mockMetadata{} + mdFQDN.On("OSDescription").Return("Ubuntu 22.04.2 LTS (Jammy Jellyfish)", nil) mdFQDN.On("OSType").Return("windows", nil) mdFQDN.On("FQDN").Return("", errors.New("err")) mdFQDN.On("Hostname").Return("", errors.New("err")) @@ -205,6 +219,7 @@ func TestDetectError(t *testing.T) { // hostname fail with 'hostnameSources' set to 'os' mdHostname := &mockMetadata{} + mdHostname.On("OSDescription").Return("Ubuntu 22.04.2 LTS (Jammy Jellyfish)", nil) mdHostname.On("OSType").Return("windows", nil) mdHostname.On("Hostname").Return("", errors.New("err")) mdHostname.On("HostID").Return("", errors.New("err")) @@ -219,6 +234,7 @@ func TestDetectError(t *testing.T) { // OS type fails mdOSType := &mockMetadata{} mdOSType.On("FQDN").Return("fqdn", nil) + mdOSType.On("OSDescription").Return("Ubuntu 22.04.2 LTS (Jammy Jellyfish)", nil) mdOSType.On("OSType").Return("", errors.New("err")) mdOSType.On("HostID").Return("1", nil) mdOSType.On("HostArch").Return("amd64", nil) @@ -232,6 +248,7 @@ func TestDetectError(t *testing.T) { // Host ID fails. All other attributes should be set. mdHostID := &mockMetadata{} mdHostID.On("Hostname").Return("hostname", nil) + mdHostID.On("OSDescription").Return("Ubuntu 22.04.2 LTS (Jammy Jellyfish)", nil) mdHostID.On("OSType").Return("linux", nil) mdHostID.On("HostID").Return("", errors.New("err")) mdHostID.On("HostArch").Return("arm64", nil) @@ -241,9 +258,10 @@ func TestDetectError(t *testing.T) { assert.NoError(t, err) assert.Equal(t, conventions.SchemaURL, schemaURL) assert.Equal(t, map[string]any{ - conventions.AttributeHostName: "hostname", - conventions.AttributeOSType: "linux", - conventions.AttributeHostArch: conventions.AttributeHostArchARM64, + conventions.AttributeHostName: "hostname", + conventions.AttributeOSDescription: "Ubuntu 22.04.2 LTS (Jammy Jellyfish)", + conventions.AttributeOSType: "linux", + conventions.AttributeHostArch: conventions.AttributeHostArchARM64, }, res.Attributes().AsRaw()) }