From a5c87b8651ed65556b7d5aa60cabdca26f9351c0 Mon Sep 17 00:00:00 2001 From: Craig MacKenzie Date: Tue, 4 Mar 2025 11:03:18 -0500 Subject: [PATCH] Add the full set of ECS metadata sent to Fleet to Diagnostics (#7029) * Add metadata to diagnostics * Add changelog. * Update agent info mock. * Add missing method to manual mock. (cherry picked from commit d6687876cb0ac8671719aecca7336cdeff671710) --- ...data-that-is-sent-to-Fleet-by-default.yaml | 32 ++++++++++ .../application/coordinator/coordinator.go | 25 ++++---- .../coordinator/diagnostics_test.go | 63 ++++++++++++++++--- .../pkg/agent/application/info/agent_info.go | 3 + pkg/component/runtime/runtime_comm_test.go | 3 + .../pkg/agent/application/info/agent_mock.go | 63 ++++++++++++++++++- 6 files changed, 168 insertions(+), 21 deletions(-) create mode 100644 changelog/fragments/1740521494-diagnostics-include-all-metadata-that-is-sent-to-Fleet-by-default.yaml diff --git a/changelog/fragments/1740521494-diagnostics-include-all-metadata-that-is-sent-to-Fleet-by-default.yaml b/changelog/fragments/1740521494-diagnostics-include-all-metadata-that-is-sent-to-Fleet-by-default.yaml new file mode 100644 index 00000000000..f59a3708871 --- /dev/null +++ b/changelog/fragments/1740521494-diagnostics-include-all-metadata-that-is-sent-to-Fleet-by-default.yaml @@ -0,0 +1,32 @@ +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: enhancement + +# Change summary; a 80ish characters long description of the change. +summary: Include all metadata that is sent to Fleet in the agent-info.yaml file in diagnostics by default. + +# Long description; in case the summary is not enough to describe the change +# this field accommodate a description without length limits. +# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. +#description: + +# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. +component: "elastic-agent" + +# PR URL; optional; the PR number that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +pr: https://github.com/elastic/elastic-agent/pull/7029 + +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +#issue: https://github.com/owner/repo/1234 diff --git a/internal/pkg/agent/application/coordinator/coordinator.go b/internal/pkg/agent/application/coordinator/coordinator.go index 30c7396b25e..410f22f1e47 100644 --- a/internal/pkg/agent/application/coordinator/coordinator.go +++ b/internal/pkg/agent/application/coordinator/coordinator.go @@ -791,20 +791,21 @@ func (c *Coordinator) DiagnosticHooks() diagnostics.Hooks { Description: "current state of the agent information of the running Elastic Agent", ContentType: "application/yaml", Hook: func(_ context.Context) []byte { + meta, err := c.agentInfo.ECSMetadata(c.logger) + if err != nil { + c.logger.Errorw("Error getting ECS metadata", "error.message", err) + } + output := struct { - AgentID string `yaml:"agent_id"` - Headers map[string]string `yaml:"headers"` - LogLevel string `yaml:"log_level"` - Snapshot bool `yaml:"snapshot"` - Version string `yaml:"version"` - Unprivileged bool `yaml:"unprivileged"` + Headers map[string]string `yaml:"headers"` + LogLevel string `yaml:"log_level"` + RawLogLevel string `yaml:"log_level_raw"` + Metadata *info.ECSMeta `yaml:"metadata"` }{ - AgentID: c.agentInfo.AgentID(), - Headers: c.agentInfo.Headers(), - LogLevel: c.agentInfo.LogLevel(), - Snapshot: c.agentInfo.Snapshot(), - Version: c.agentInfo.Version(), - Unprivileged: c.agentInfo.Unprivileged(), + Headers: c.agentInfo.Headers(), + LogLevel: c.agentInfo.LogLevel(), + RawLogLevel: c.agentInfo.RawLogLevel(), + Metadata: meta, } o, err := yaml.Marshal(output) if err != nil { diff --git a/internal/pkg/agent/application/coordinator/diagnostics_test.go b/internal/pkg/agent/application/coordinator/diagnostics_test.go index 95adc0408e3..b29d1884549 100644 --- a/internal/pkg/agent/application/coordinator/diagnostics_test.go +++ b/internal/pkg/agent/application/coordinator/diagnostics_test.go @@ -18,6 +18,7 @@ import ( "github.com/elastic/elastic-agent-client/v7/pkg/client" "github.com/elastic/elastic-agent-client/v7/pkg/proto" + "github.com/elastic/elastic-agent/internal/pkg/agent/application/info" "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/details" "github.com/elastic/elastic-agent/internal/pkg/agent/configuration" "github.com/elastic/elastic-agent/internal/pkg/agent/transpiler" @@ -27,6 +28,7 @@ import ( "github.com/elastic/elastic-agent/pkg/component" "github.com/elastic/elastic-agent/pkg/component/runtime" agentclient "github.com/elastic/elastic-agent/pkg/control/v2/client" + "github.com/elastic/elastic-agent/pkg/core/logger" "github.com/elastic/elastic-agent/pkg/utils/broadcaster" ) @@ -144,21 +146,61 @@ func TestDiagnosticAgentInfo(t *testing.T) { "header1": "value1", "header2": "value2", }, - logLevel: "trace", - snapshot: true, - version: "8.14.0", - unprivileged: true, + logLevel: "trace", + meta: &info.ECSMeta{ + Elastic: &info.ElasticECSMeta{ + Agent: &info.AgentECSMeta{ + BuildOriginal: "8.14.0-SNAPSHOT", + ID: "agent-id", + LogLevel: "trace", + Snapshot: true, + Version: "8.14.0", + Unprivileged: true, + Upgradeable: true, + }, + }, + Host: &info.HostECSMeta{ + Arch: "arm64", + Hostname: "Test-Macbook-Pro.local", + }, + OS: &info.SystemECSMeta{ + Name: "macos", + Platform: "darwin", + }, + }, }} expected := ` -agent_id: agent-id headers: header1: value1 header2: value2 log_level: trace -snapshot: true -version: 8.14.0 -unprivileged: true +log_level_raw: trace +metadata: + elastic: + agent: + buildoriginal: "8.14.0-SNAPSHOT" + complete: false + id: agent-id + loglevel: trace + snapshot: true + unprivileged: true + upgradeable: true + version: 8.14.0 + host: + arch: arm64 + hostname: Test-Macbook-Pro.local + name: "" + id: "" + ip: [] + mac: [] + os: + family: "" + kernel: "" + platform: darwin + version: "" + name: macos + fullname: "" ` hook, ok := diagnosticHooksMap(coord)["agent-info"] @@ -606,6 +648,7 @@ type fakeAgentInfo struct { version string unprivileged bool isStandalone bool + meta *info.ECSMeta } func (a fakeAgentInfo) AgentID() string { @@ -640,5 +683,9 @@ func (a fakeAgentInfo) IsStandalone() bool { return a.isStandalone } +func (a fakeAgentInfo) ECSMetadata(l *logger.Logger) (*info.ECSMeta, error) { + return a.meta, nil +} + func (a fakeAgentInfo) ReloadID(ctx context.Context) error { panic("implement me") } func (a fakeAgentInfo) SetLogLevel(ctx context.Context, level string) error { panic("implement me") } diff --git a/internal/pkg/agent/application/info/agent_info.go b/internal/pkg/agent/application/info/agent_info.go index 56371dbf121..42f65df573a 100644 --- a/internal/pkg/agent/application/info/agent_info.go +++ b/internal/pkg/agent/application/info/agent_info.go @@ -43,6 +43,9 @@ type Agent interface { // IsStandalone returns true is the agent is running in standalone mode, i.e, without fleet IsStandalone() bool + + // ECSMetadata returns the ECS metadata that is attached as part of every Fleet checkin. + ECSMetadata(*logger.Logger) (*ECSMeta, error) } // AgentInfo is a collection of information about agent. diff --git a/pkg/component/runtime/runtime_comm_test.go b/pkg/component/runtime/runtime_comm_test.go index 4aa1e0eada4..b8c72910b6c 100644 --- a/pkg/component/runtime/runtime_comm_test.go +++ b/pkg/component/runtime/runtime_comm_test.go @@ -15,7 +15,9 @@ import ( "github.com/elastic/elastic-agent-client/v7/pkg/client" "github.com/elastic/elastic-agent-client/v7/pkg/proto" + "github.com/elastic/elastic-agent/internal/pkg/agent/application/info" "github.com/elastic/elastic-agent/internal/pkg/core/authority" + "github.com/elastic/elastic-agent/pkg/core/logger" ) type agentInfoMock struct { @@ -50,6 +52,7 @@ func (a agentInfoMock) LogLevel() string { pa func (a agentInfoMock) RawLogLevel() string { panic("implement me") } func (a agentInfoMock) ReloadID(ctx context.Context) error { panic("implement me") } func (a agentInfoMock) SetLogLevel(ctx context.Context, level string) error { panic("implement me") } +func (a agentInfoMock) ECSMetadata(l *logger.Logger) (*info.ECSMeta, error) { panic("implement me") } func TestCheckinExpected(t *testing.T) { ca, err := authority.NewCA() diff --git a/testing/mocks/internal_/pkg/agent/application/info/agent_mock.go b/testing/mocks/internal_/pkg/agent/application/info/agent_mock.go index c61b0bab787..32d403a7d5c 100644 --- a/testing/mocks/internal_/pkg/agent/application/info/agent_mock.go +++ b/testing/mocks/internal_/pkg/agent/application/info/agent_mock.go @@ -2,13 +2,16 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.51.1. DO NOT EDIT. +// Code generated by mockery v2.53.0. DO NOT EDIT. package info import ( context "context" + logp "github.com/elastic/elastic-agent-libs/logp" + info "github.com/elastic/elastic-agent/internal/pkg/agent/application/info" + mock "github.com/stretchr/testify/mock" ) @@ -70,6 +73,64 @@ func (_c *Agent_AgentID_Call) RunAndReturn(run func() string) *Agent_AgentID_Cal return _c } +// ECSMetadata provides a mock function with given fields: _a0 +func (_m *Agent) ECSMetadata(_a0 *logp.Logger) (*info.ECSMeta, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for ECSMetadata") + } + + var r0 *info.ECSMeta + var r1 error + if rf, ok := ret.Get(0).(func(*logp.Logger) (*info.ECSMeta, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(*logp.Logger) *info.ECSMeta); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*info.ECSMeta) + } + } + + if rf, ok := ret.Get(1).(func(*logp.Logger) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Agent_ECSMetadata_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ECSMetadata' +type Agent_ECSMetadata_Call struct { + *mock.Call +} + +// ECSMetadata is a helper method to define mock.On call +// - _a0 *logp.Logger +func (_e *Agent_Expecter) ECSMetadata(_a0 interface{}) *Agent_ECSMetadata_Call { + return &Agent_ECSMetadata_Call{Call: _e.mock.On("ECSMetadata", _a0)} +} + +func (_c *Agent_ECSMetadata_Call) Run(run func(_a0 *logp.Logger)) *Agent_ECSMetadata_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*logp.Logger)) + }) + return _c +} + +func (_c *Agent_ECSMetadata_Call) Return(_a0 *info.ECSMeta, _a1 error) *Agent_ECSMetadata_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Agent_ECSMetadata_Call) RunAndReturn(run func(*logp.Logger) (*info.ECSMeta, error)) *Agent_ECSMetadata_Call { + _c.Call.Return(run) + return _c +} + // Headers provides a mock function with no fields func (_m *Agent) Headers() map[string]string { ret := _m.Called()