From 78344946e79406204a4e88c65904d103c8e2953f Mon Sep 17 00:00:00 2001 From: Sean Breen Date: Thu, 24 Jul 2025 13:27:51 +0100 Subject: [PATCH 1/7] move internal/datasource/host to pkg --- internal/config/config.go | 3 ++- internal/grpc/grpc.go | 3 ++- internal/resource/nginx_instance_operator.go | 3 ++- internal/resource/nginx_instance_operator_test.go | 3 ++- internal/resource/resource_service.go | 4 ++-- internal/resource/resource_service_test.go | 4 ++-- internal/watcher/health/nginx_health_watcher_operator.go | 3 ++- internal/watcher/instance/instance_watcher_service.go | 3 ++- internal/watcher/instance/instance_watcher_service_test.go | 3 ++- internal/watcher/instance/nginx_process_parser.go | 3 ++- internal/watcher/instance/nginx_process_parser_test.go | 3 ++- {internal/datasource => pkg}/host/exec/exec.go | 0 {internal/datasource => pkg}/host/exec/exec_test.go | 0 .../host/exec/execfakes/fake_exec_interface.go | 2 +- .../datasource => pkg}/host/hostfakes/fake_info_interface.go | 2 +- {internal/datasource => pkg}/host/info.go | 3 ++- {internal/datasource => pkg}/host/info_test.go | 3 ++- 17 files changed, 28 insertions(+), 17 deletions(-) rename {internal/datasource => pkg}/host/exec/exec.go (100%) rename {internal/datasource => pkg}/host/exec/exec_test.go (100%) rename {internal/datasource => pkg}/host/exec/execfakes/fake_exec_interface.go (99%) rename {internal/datasource => pkg}/host/hostfakes/fake_info_interface.go (99%) rename {internal/datasource => pkg}/host/info.go (99%) rename {internal/datasource => pkg}/host/info_test.go (99%) diff --git a/internal/config/config.go b/internal/config/config.go index 867865d0c..25f7b9a24 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,8 +19,9 @@ import ( "strings" "time" + "github.com/nginx/agent/v3/pkg/host" + "github.com/nginx/agent/v3/internal/datasource/file" - "github.com/nginx/agent/v3/internal/datasource/host" "github.com/nginx/agent/v3/internal/logger" "github.com/goccy/go-yaml" diff --git a/internal/grpc/grpc.go b/internal/grpc/grpc.go index fe0feaf62..d79df1731 100644 --- a/internal/grpc/grpc.go +++ b/internal/grpc/grpc.go @@ -16,6 +16,8 @@ import ( "strings" "sync" + "github.com/nginx/agent/v3/pkg/host" + "github.com/nginx/agent/v3/internal/datasource/file" "github.com/cenkalti/backoff/v4" @@ -27,7 +29,6 @@ import ( mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" "github.com/nginx/agent/v3/internal/config" - "github.com/nginx/agent/v3/internal/datasource/host" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/keepalive" diff --git a/internal/resource/nginx_instance_operator.go b/internal/resource/nginx_instance_operator.go index fa515c081..b05912c3b 100644 --- a/internal/resource/nginx_instance_operator.go +++ b/internal/resource/nginx_instance_operator.go @@ -12,9 +12,10 @@ import ( "fmt" "log/slog" + "github.com/nginx/agent/v3/pkg/host/exec" + mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" "github.com/nginx/agent/v3/internal/config" - "github.com/nginx/agent/v3/internal/datasource/host/exec" ) type NginxInstanceOperator struct { diff --git a/internal/resource/nginx_instance_operator_test.go b/internal/resource/nginx_instance_operator_test.go index 186aa05bb..7d52d8364 100644 --- a/internal/resource/nginx_instance_operator_test.go +++ b/internal/resource/nginx_instance_operator_test.go @@ -14,7 +14,8 @@ import ( "testing" "time" - "github.com/nginx/agent/v3/internal/datasource/host/exec/execfakes" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" + "github.com/nginx/agent/v3/test/helpers" "github.com/nginx/agent/v3/test/protos" "github.com/nginx/agent/v3/test/types" diff --git a/internal/resource/resource_service.go b/internal/resource/resource_service.go index 792c28c7a..a2cfb15f2 100644 --- a/internal/resource/resource_service.go +++ b/internal/resource/resource_service.go @@ -19,6 +19,8 @@ import ( "strings" "sync" + "github.com/nginx/agent/v3/pkg/host" + parser "github.com/nginx/agent/v3/internal/datasource/config" datasource "github.com/nginx/agent/v3/internal/datasource/proto" "github.com/nginx/agent/v3/internal/model" @@ -30,8 +32,6 @@ import ( "github.com/nginx/agent/v3/internal/config" - "github.com/nginx/agent/v3/internal/datasource/host" - mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" ) diff --git a/internal/resource/resource_service_test.go b/internal/resource/resource_service_test.go index f8fe52244..827aa529f 100644 --- a/internal/resource/resource_service_test.go +++ b/internal/resource/resource_service_test.go @@ -13,6 +13,8 @@ import ( "path/filepath" "testing" + "github.com/nginx/agent/v3/pkg/host/hostfakes" + "github.com/nginx/agent/v3/internal/model" "github.com/nginx/agent/v3/internal/watcher/instance/instancefakes" @@ -23,8 +25,6 @@ import ( "github.com/nginx/agent/v3/internal/resource/resourcefakes" "github.com/nginx/agent/v3/test/types" - "github.com/nginx/agent/v3/internal/datasource/host/hostfakes" - "github.com/nginx/agent/v3/api/grpc/mpi/v1" "github.com/nginx/agent/v3/test/protos" "github.com/stretchr/testify/assert" diff --git a/internal/watcher/health/nginx_health_watcher_operator.go b/internal/watcher/health/nginx_health_watcher_operator.go index 0fdd75618..79d53167b 100644 --- a/internal/watcher/health/nginx_health_watcher_operator.go +++ b/internal/watcher/health/nginx_health_watcher_operator.go @@ -9,8 +9,9 @@ import ( "context" "fmt" + "github.com/nginx/agent/v3/pkg/host/exec" + mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host/exec" processwatcher "github.com/nginx/agent/v3/internal/watcher/process" "github.com/nginx/agent/v3/pkg/nginxprocess" ) diff --git a/internal/watcher/instance/instance_watcher_service.go b/internal/watcher/instance/instance_watcher_service.go index 1bc50c496..a132e6b9e 100644 --- a/internal/watcher/instance/instance_watcher_service.go +++ b/internal/watcher/instance/instance_watcher_service.go @@ -13,6 +13,8 @@ import ( "sync/atomic" "time" + "github.com/nginx/agent/v3/pkg/host/exec" + "github.com/nginx/agent/v3/internal/datasource/proto" parser "github.com/nginx/agent/v3/internal/datasource/config" @@ -23,7 +25,6 @@ import ( "github.com/nginx/agent/v3/internal/watcher/process" "github.com/nginx/agent/v3/internal/config" - "github.com/nginx/agent/v3/internal/datasource/host/exec" "github.com/nginx/agent/v3/internal/logger" "github.com/nginx/agent/v3/internal/model" ) diff --git a/internal/watcher/instance/instance_watcher_service_test.go b/internal/watcher/instance/instance_watcher_service_test.go index 218b64f41..fdf0cf3f8 100644 --- a/internal/watcher/instance/instance_watcher_service_test.go +++ b/internal/watcher/instance/instance_watcher_service_test.go @@ -9,11 +9,12 @@ import ( "context" "testing" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" + "github.com/nginx/agent/v3/internal/watcher/instance/instancefakes" "github.com/nginx/agent/v3/internal/watcher/process/processfakes" mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host/exec/execfakes" "github.com/nginx/agent/v3/internal/model" testModel "github.com/nginx/agent/v3/test/model" "github.com/nginx/agent/v3/test/protos" diff --git a/internal/watcher/instance/nginx_process_parser.go b/internal/watcher/instance/nginx_process_parser.go index 3a54cd914..e647fd9d3 100644 --- a/internal/watcher/instance/nginx_process_parser.go +++ b/internal/watcher/instance/nginx_process_parser.go @@ -17,8 +17,9 @@ import ( "sort" "strings" + "github.com/nginx/agent/v3/pkg/host/exec" + mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host/exec" "github.com/nginx/agent/v3/pkg/id" "github.com/nginx/agent/v3/pkg/nginxprocess" ) diff --git a/internal/watcher/instance/nginx_process_parser_test.go b/internal/watcher/instance/nginx_process_parser_test.go index 889807960..3dceb1dd3 100644 --- a/internal/watcher/instance/nginx_process_parser_test.go +++ b/internal/watcher/instance/nginx_process_parser_test.go @@ -15,10 +15,11 @@ import ( "strings" "testing" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" + "google.golang.org/protobuf/proto" mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host/exec/execfakes" "github.com/nginx/agent/v3/pkg/nginxprocess" "github.com/nginx/agent/v3/test/helpers" "github.com/nginx/agent/v3/test/protos" diff --git a/internal/datasource/host/exec/exec.go b/pkg/host/exec/exec.go similarity index 100% rename from internal/datasource/host/exec/exec.go rename to pkg/host/exec/exec.go diff --git a/internal/datasource/host/exec/exec_test.go b/pkg/host/exec/exec_test.go similarity index 100% rename from internal/datasource/host/exec/exec_test.go rename to pkg/host/exec/exec_test.go diff --git a/internal/datasource/host/exec/execfakes/fake_exec_interface.go b/pkg/host/exec/execfakes/fake_exec_interface.go similarity index 99% rename from internal/datasource/host/exec/execfakes/fake_exec_interface.go rename to pkg/host/exec/execfakes/fake_exec_interface.go index a023d6dc3..e64cdad6b 100644 --- a/internal/datasource/host/exec/execfakes/fake_exec_interface.go +++ b/pkg/host/exec/execfakes/fake_exec_interface.go @@ -4,10 +4,10 @@ package execfakes import ( "bytes" "context" + "github.com/nginx/agent/v3/pkg/host/exec" "sync" v1 "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host/exec" ) type FakeExecInterface struct { diff --git a/internal/datasource/host/hostfakes/fake_info_interface.go b/pkg/host/hostfakes/fake_info_interface.go similarity index 99% rename from internal/datasource/host/hostfakes/fake_info_interface.go rename to pkg/host/hostfakes/fake_info_interface.go index b0b302313..bdc1388bc 100644 --- a/internal/datasource/host/hostfakes/fake_info_interface.go +++ b/pkg/host/hostfakes/fake_info_interface.go @@ -3,10 +3,10 @@ package hostfakes import ( "context" + "github.com/nginx/agent/v3/pkg/host" "sync" v1 "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host" ) type FakeInfoInterface struct { diff --git a/internal/datasource/host/info.go b/pkg/host/info.go similarity index 99% rename from internal/datasource/host/info.go rename to pkg/host/info.go index bb47c6933..208aa478f 100644 --- a/internal/datasource/host/info.go +++ b/pkg/host/info.go @@ -16,9 +16,10 @@ import ( "regexp" "strings" + "github.com/nginx/agent/v3/pkg/host/exec" + "github.com/google/uuid" "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host/exec" "golang.org/x/sync/singleflight" ) diff --git a/internal/datasource/host/info_test.go b/pkg/host/info_test.go similarity index 99% rename from internal/datasource/host/info_test.go rename to pkg/host/info_test.go index fb056a518..ef66345fa 100644 --- a/internal/datasource/host/info_test.go +++ b/pkg/host/info_test.go @@ -11,8 +11,9 @@ import ( "strings" "testing" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" + "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host/exec/execfakes" "github.com/nginx/agent/v3/test/helpers" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" From b54a80e7c334c7c0fca83ac0b7d5508ceb55607b Mon Sep 17 00:00:00 2001 From: Sean Breen Date: Thu, 24 Jul 2025 13:32:12 +0100 Subject: [PATCH 2/7] update fakes --- pkg/host/exec/execfakes/fake_exec_interface.go | 2 +- pkg/host/hostfakes/fake_info_interface.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/host/exec/execfakes/fake_exec_interface.go b/pkg/host/exec/execfakes/fake_exec_interface.go index e64cdad6b..b5cda3f2d 100644 --- a/pkg/host/exec/execfakes/fake_exec_interface.go +++ b/pkg/host/exec/execfakes/fake_exec_interface.go @@ -4,10 +4,10 @@ package execfakes import ( "bytes" "context" - "github.com/nginx/agent/v3/pkg/host/exec" "sync" v1 "github.com/nginx/agent/v3/api/grpc/mpi/v1" + "github.com/nginx/agent/v3/pkg/host/exec" ) type FakeExecInterface struct { diff --git a/pkg/host/hostfakes/fake_info_interface.go b/pkg/host/hostfakes/fake_info_interface.go index bdc1388bc..c791064cd 100644 --- a/pkg/host/hostfakes/fake_info_interface.go +++ b/pkg/host/hostfakes/fake_info_interface.go @@ -3,10 +3,10 @@ package hostfakes import ( "context" - "github.com/nginx/agent/v3/pkg/host" "sync" v1 "github.com/nginx/agent/v3/api/grpc/mpi/v1" + "github.com/nginx/agent/v3/pkg/host" ) type FakeInfoInterface struct { From 587907046a7941a13e33938e8ef0cef64524642d Mon Sep 17 00:00:00 2001 From: Sean Breen Date: Thu, 31 Jul 2025 12:24:48 +0100 Subject: [PATCH 3/7] warn when creating grpc connection --- internal/config/config.go | 6 +- internal/grpc/grpc.go | 7 +- internal/resource/resource_service.go | 17 ++- internal/resource/resource_service_test.go | 6 +- pkg/host/exec/exec.go | 11 +- .../exec/execfakes/fake_exec_interface.go | 23 ++-- pkg/host/hostfakes/fake_info_interface.go | 92 ++++++++----- pkg/host/info.go | 128 ++++++++++-------- pkg/host/info_test.go | 17 ++- 9 files changed, 185 insertions(+), 122 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 25f7b9a24..01fd3d53e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -272,7 +272,11 @@ func addDefaultProcessors(collector *Collector) { } func addDefaultHostMetricsReceiver(collector *Collector) { - if host.NewInfo().IsContainer() { + isContainer, err := host.NewInfo().IsContainer() + if err != nil { + slog.Warn("Failed to get host info", "error", err) + } + if isContainer { addDefaultContainerHostMetricsReceiver(collector) } else { addDefaultVMHostMetricsReceiver(collector) diff --git a/internal/grpc/grpc.go b/internal/grpc/grpc.go index d79df1731..779fcb9a4 100644 --- a/internal/grpc/grpc.go +++ b/internal/grpc/grpc.go @@ -89,10 +89,13 @@ func NewGrpcConnection(ctx context.Context, agentConfig *config.Config, slog.InfoContext(ctx, "Dialing grpc server", "server_addr", serverAddr) + var err error info := host.NewInfo() - resourceID := info.ResourceID(ctx) + resourceID, err := info.ResourceID(ctx) + if err != nil { + slog.WarnContext(ctx, "Failed to get ResourceID from host info", "error", err.Error()) + } - var err error grpcConnection.mutex.Lock() grpcConnection.conn, err = grpc.NewClient(serverAddr, DialOptions(agentConfig, commandConfig, resourceID)...) grpcConnection.mutex.Unlock() diff --git a/internal/resource/resource_service.go b/internal/resource/resource_service.go index a2cfb15f2..5fce707f1 100644 --- a/internal/resource/resource_service.go +++ b/internal/resource/resource_service.go @@ -383,12 +383,23 @@ func (r *ResourceService) updateResourceInfo(ctx context.Context) { r.resourceMutex.Lock() defer r.resourceMutex.Unlock() - if r.info.IsContainer() { - r.resource.Info = r.info.ContainerInfo(ctx) + isContainer, err := r.info.IsContainer() + if err != nil { + slog.WarnContext(ctx, "Failed to check if resource is container", "error", err) + } + + if isContainer { + r.resource.Info, err = r.info.ContainerInfo(ctx) + if err != nil { + slog.ErrorContext(ctx, "Failed to get container info", "error", err) + } r.resource.ResourceId = r.resource.GetContainerInfo().GetContainerId() r.resource.Instances = []*mpi.Instance{} } else { - r.resource.Info = r.info.HostInfo(ctx) + r.resource.Info, err = r.info.HostInfo(ctx) + if err != nil { + slog.ErrorContext(ctx, "Failed to get host info", "error", err) + } r.resource.ResourceId = r.resource.GetHostInfo().GetHostId() r.resource.Instances = []*mpi.Instance{} } diff --git a/internal/resource/resource_service_test.go b/internal/resource/resource_service_test.go index 827aa529f..be9766f00 100644 --- a/internal/resource/resource_service_test.go +++ b/internal/resource/resource_service_test.go @@ -211,17 +211,17 @@ func TestResourceService_GetResource(t *testing.T) { mockInfo.ContainerInfoReturns( &v1.Resource_ContainerInfo{ ContainerInfo: tc.expectedResource.GetContainerInfo(), - }, + }, nil, ) } else { mockInfo.HostInfoReturns( &v1.Resource_HostInfo{ HostInfo: tc.expectedResource.GetHostInfo(), - }, + }, nil, ) } - mockInfo.IsContainerReturns(tc.isContainer) + mockInfo.IsContainerReturns(tc.isContainer, nil) resourceService := NewResourceService(ctx, types.AgentConfig()) resourceService.info = mockInfo diff --git a/pkg/host/exec/exec.go b/pkg/host/exec/exec.go index 8a160e263..c9111d510 100644 --- a/pkg/host/exec/exec.go +++ b/pkg/host/exec/exec.go @@ -8,7 +8,7 @@ package exec import ( "bytes" "context" - "log/slog" + "errors" "os" "os/exec" "syscall" @@ -27,7 +27,7 @@ type ExecInterface interface { KillProcess(pid int32) error Hostname() (string, error) HostID(ctx context.Context) (string, error) - ReleaseInfo(ctx context.Context) (releaseInfo *v1.ReleaseInfo) + ReleaseInfo(ctx context.Context) (releaseInfo *v1.ReleaseInfo, err error) } type Exec struct{} @@ -67,11 +67,10 @@ func (*Exec) HostID(ctx context.Context) (string, error) { return host.HostIDWithContext(ctx) } -func (*Exec) ReleaseInfo(ctx context.Context) (releaseInfo *v1.ReleaseInfo) { +func (*Exec) ReleaseInfo(ctx context.Context) (*v1.ReleaseInfo, error) { hostInfo, err := host.InfoWithContext(ctx) if err != nil { - slog.ErrorContext(ctx, "Could not read release information for host", "error", err) - return &v1.ReleaseInfo{} + return &v1.ReleaseInfo{}, errors.New("Could not read release information for host error=" + err.Error()) } return &v1.ReleaseInfo{ @@ -80,5 +79,5 @@ func (*Exec) ReleaseInfo(ctx context.Context) (releaseInfo *v1.ReleaseInfo) { Codename: hostInfo.OS, Name: hostInfo.PlatformFamily, Id: hostInfo.Platform, - } + }, nil } diff --git a/pkg/host/exec/execfakes/fake_exec_interface.go b/pkg/host/exec/execfakes/fake_exec_interface.go index b5cda3f2d..30f00f7c7 100644 --- a/pkg/host/exec/execfakes/fake_exec_interface.go +++ b/pkg/host/exec/execfakes/fake_exec_interface.go @@ -82,16 +82,18 @@ type FakeExecInterface struct { processIDReturnsOnCall map[int]struct { result1 int32 } - ReleaseInfoStub func(context.Context) *v1.ReleaseInfo + ReleaseInfoStub func(context.Context) (*v1.ReleaseInfo, error) releaseInfoMutex sync.RWMutex releaseInfoArgsForCall []struct { arg1 context.Context } releaseInfoReturns struct { result1 *v1.ReleaseInfo + result2 error } releaseInfoReturnsOnCall map[int]struct { result1 *v1.ReleaseInfo + result2 error } RunCmdStub func(context.Context, string, ...string) (*bytes.Buffer, error) runCmdMutex sync.RWMutex @@ -466,7 +468,7 @@ func (fake *FakeExecInterface) ProcessIDReturnsOnCall(i int, result1 int32) { }{result1} } -func (fake *FakeExecInterface) ReleaseInfo(arg1 context.Context) *v1.ReleaseInfo { +func (fake *FakeExecInterface) ReleaseInfo(arg1 context.Context) (*v1.ReleaseInfo, error) { fake.releaseInfoMutex.Lock() ret, specificReturn := fake.releaseInfoReturnsOnCall[len(fake.releaseInfoArgsForCall)] fake.releaseInfoArgsForCall = append(fake.releaseInfoArgsForCall, struct { @@ -480,9 +482,9 @@ func (fake *FakeExecInterface) ReleaseInfo(arg1 context.Context) *v1.ReleaseInfo return stub(arg1) } if specificReturn { - return ret.result1 + return ret.result1, ret.result2 } - return fakeReturns.result1 + return fakeReturns.result1, fakeReturns.result2 } func (fake *FakeExecInterface) ReleaseInfoCallCount() int { @@ -491,7 +493,7 @@ func (fake *FakeExecInterface) ReleaseInfoCallCount() int { return len(fake.releaseInfoArgsForCall) } -func (fake *FakeExecInterface) ReleaseInfoCalls(stub func(context.Context) *v1.ReleaseInfo) { +func (fake *FakeExecInterface) ReleaseInfoCalls(stub func(context.Context) (*v1.ReleaseInfo, error)) { fake.releaseInfoMutex.Lock() defer fake.releaseInfoMutex.Unlock() fake.ReleaseInfoStub = stub @@ -504,27 +506,30 @@ func (fake *FakeExecInterface) ReleaseInfoArgsForCall(i int) context.Context { return argsForCall.arg1 } -func (fake *FakeExecInterface) ReleaseInfoReturns(result1 *v1.ReleaseInfo) { +func (fake *FakeExecInterface) ReleaseInfoReturns(result1 *v1.ReleaseInfo, result2 error) { fake.releaseInfoMutex.Lock() defer fake.releaseInfoMutex.Unlock() fake.ReleaseInfoStub = nil fake.releaseInfoReturns = struct { result1 *v1.ReleaseInfo - }{result1} + result2 error + }{result1, result2} } -func (fake *FakeExecInterface) ReleaseInfoReturnsOnCall(i int, result1 *v1.ReleaseInfo) { +func (fake *FakeExecInterface) ReleaseInfoReturnsOnCall(i int, result1 *v1.ReleaseInfo, result2 error) { fake.releaseInfoMutex.Lock() defer fake.releaseInfoMutex.Unlock() fake.ReleaseInfoStub = nil if fake.releaseInfoReturnsOnCall == nil { fake.releaseInfoReturnsOnCall = make(map[int]struct { result1 *v1.ReleaseInfo + result2 error }) } fake.releaseInfoReturnsOnCall[i] = struct { result1 *v1.ReleaseInfo - }{result1} + result2 error + }{result1, result2} } func (fake *FakeExecInterface) RunCmd(arg1 context.Context, arg2 string, arg3 ...string) (*bytes.Buffer, error) { diff --git a/pkg/host/hostfakes/fake_info_interface.go b/pkg/host/hostfakes/fake_info_interface.go index c791064cd..bed75fa03 100644 --- a/pkg/host/hostfakes/fake_info_interface.go +++ b/pkg/host/hostfakes/fake_info_interface.go @@ -10,54 +10,62 @@ import ( ) type FakeInfoInterface struct { - ContainerInfoStub func(context.Context) *v1.Resource_ContainerInfo + ContainerInfoStub func(context.Context) (*v1.Resource_ContainerInfo, error) containerInfoMutex sync.RWMutex containerInfoArgsForCall []struct { arg1 context.Context } containerInfoReturns struct { result1 *v1.Resource_ContainerInfo + result2 error } containerInfoReturnsOnCall map[int]struct { result1 *v1.Resource_ContainerInfo + result2 error } - HostInfoStub func(context.Context) *v1.Resource_HostInfo + HostInfoStub func(context.Context) (*v1.Resource_HostInfo, error) hostInfoMutex sync.RWMutex hostInfoArgsForCall []struct { arg1 context.Context } hostInfoReturns struct { result1 *v1.Resource_HostInfo + result2 error } hostInfoReturnsOnCall map[int]struct { result1 *v1.Resource_HostInfo + result2 error } - IsContainerStub func() bool + IsContainerStub func() (bool, error) isContainerMutex sync.RWMutex isContainerArgsForCall []struct { } isContainerReturns struct { result1 bool + result2 error } isContainerReturnsOnCall map[int]struct { result1 bool + result2 error } - ResourceIDStub func(context.Context) string + ResourceIDStub func(context.Context) (string, error) resourceIDMutex sync.RWMutex resourceIDArgsForCall []struct { arg1 context.Context } resourceIDReturns struct { result1 string + result2 error } resourceIDReturnsOnCall map[int]struct { result1 string + result2 error } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } -func (fake *FakeInfoInterface) ContainerInfo(arg1 context.Context) *v1.Resource_ContainerInfo { +func (fake *FakeInfoInterface) ContainerInfo(arg1 context.Context) (*v1.Resource_ContainerInfo, error) { fake.containerInfoMutex.Lock() ret, specificReturn := fake.containerInfoReturnsOnCall[len(fake.containerInfoArgsForCall)] fake.containerInfoArgsForCall = append(fake.containerInfoArgsForCall, struct { @@ -71,9 +79,9 @@ func (fake *FakeInfoInterface) ContainerInfo(arg1 context.Context) *v1.Resource_ return stub(arg1) } if specificReturn { - return ret.result1 + return ret.result1, ret.result2 } - return fakeReturns.result1 + return fakeReturns.result1, fakeReturns.result2 } func (fake *FakeInfoInterface) ContainerInfoCallCount() int { @@ -82,7 +90,7 @@ func (fake *FakeInfoInterface) ContainerInfoCallCount() int { return len(fake.containerInfoArgsForCall) } -func (fake *FakeInfoInterface) ContainerInfoCalls(stub func(context.Context) *v1.Resource_ContainerInfo) { +func (fake *FakeInfoInterface) ContainerInfoCalls(stub func(context.Context) (*v1.Resource_ContainerInfo, error)) { fake.containerInfoMutex.Lock() defer fake.containerInfoMutex.Unlock() fake.ContainerInfoStub = stub @@ -95,30 +103,33 @@ func (fake *FakeInfoInterface) ContainerInfoArgsForCall(i int) context.Context { return argsForCall.arg1 } -func (fake *FakeInfoInterface) ContainerInfoReturns(result1 *v1.Resource_ContainerInfo) { +func (fake *FakeInfoInterface) ContainerInfoReturns(result1 *v1.Resource_ContainerInfo, result2 error) { fake.containerInfoMutex.Lock() defer fake.containerInfoMutex.Unlock() fake.ContainerInfoStub = nil fake.containerInfoReturns = struct { result1 *v1.Resource_ContainerInfo - }{result1} + result2 error + }{result1, result2} } -func (fake *FakeInfoInterface) ContainerInfoReturnsOnCall(i int, result1 *v1.Resource_ContainerInfo) { +func (fake *FakeInfoInterface) ContainerInfoReturnsOnCall(i int, result1 *v1.Resource_ContainerInfo, result2 error) { fake.containerInfoMutex.Lock() defer fake.containerInfoMutex.Unlock() fake.ContainerInfoStub = nil if fake.containerInfoReturnsOnCall == nil { fake.containerInfoReturnsOnCall = make(map[int]struct { result1 *v1.Resource_ContainerInfo + result2 error }) } fake.containerInfoReturnsOnCall[i] = struct { result1 *v1.Resource_ContainerInfo - }{result1} + result2 error + }{result1, result2} } -func (fake *FakeInfoInterface) HostInfo(arg1 context.Context) *v1.Resource_HostInfo { +func (fake *FakeInfoInterface) HostInfo(arg1 context.Context) (*v1.Resource_HostInfo, error) { fake.hostInfoMutex.Lock() ret, specificReturn := fake.hostInfoReturnsOnCall[len(fake.hostInfoArgsForCall)] fake.hostInfoArgsForCall = append(fake.hostInfoArgsForCall, struct { @@ -132,9 +143,9 @@ func (fake *FakeInfoInterface) HostInfo(arg1 context.Context) *v1.Resource_HostI return stub(arg1) } if specificReturn { - return ret.result1 + return ret.result1, ret.result2 } - return fakeReturns.result1 + return fakeReturns.result1, fakeReturns.result2 } func (fake *FakeInfoInterface) HostInfoCallCount() int { @@ -143,7 +154,7 @@ func (fake *FakeInfoInterface) HostInfoCallCount() int { return len(fake.hostInfoArgsForCall) } -func (fake *FakeInfoInterface) HostInfoCalls(stub func(context.Context) *v1.Resource_HostInfo) { +func (fake *FakeInfoInterface) HostInfoCalls(stub func(context.Context) (*v1.Resource_HostInfo, error)) { fake.hostInfoMutex.Lock() defer fake.hostInfoMutex.Unlock() fake.HostInfoStub = stub @@ -156,30 +167,33 @@ func (fake *FakeInfoInterface) HostInfoArgsForCall(i int) context.Context { return argsForCall.arg1 } -func (fake *FakeInfoInterface) HostInfoReturns(result1 *v1.Resource_HostInfo) { +func (fake *FakeInfoInterface) HostInfoReturns(result1 *v1.Resource_HostInfo, result2 error) { fake.hostInfoMutex.Lock() defer fake.hostInfoMutex.Unlock() fake.HostInfoStub = nil fake.hostInfoReturns = struct { result1 *v1.Resource_HostInfo - }{result1} + result2 error + }{result1, result2} } -func (fake *FakeInfoInterface) HostInfoReturnsOnCall(i int, result1 *v1.Resource_HostInfo) { +func (fake *FakeInfoInterface) HostInfoReturnsOnCall(i int, result1 *v1.Resource_HostInfo, result2 error) { fake.hostInfoMutex.Lock() defer fake.hostInfoMutex.Unlock() fake.HostInfoStub = nil if fake.hostInfoReturnsOnCall == nil { fake.hostInfoReturnsOnCall = make(map[int]struct { result1 *v1.Resource_HostInfo + result2 error }) } fake.hostInfoReturnsOnCall[i] = struct { result1 *v1.Resource_HostInfo - }{result1} + result2 error + }{result1, result2} } -func (fake *FakeInfoInterface) IsContainer() bool { +func (fake *FakeInfoInterface) IsContainer() (bool, error) { fake.isContainerMutex.Lock() ret, specificReturn := fake.isContainerReturnsOnCall[len(fake.isContainerArgsForCall)] fake.isContainerArgsForCall = append(fake.isContainerArgsForCall, struct { @@ -192,9 +206,9 @@ func (fake *FakeInfoInterface) IsContainer() bool { return stub() } if specificReturn { - return ret.result1 + return ret.result1, ret.result2 } - return fakeReturns.result1 + return fakeReturns.result1, fakeReturns.result2 } func (fake *FakeInfoInterface) IsContainerCallCount() int { @@ -203,36 +217,39 @@ func (fake *FakeInfoInterface) IsContainerCallCount() int { return len(fake.isContainerArgsForCall) } -func (fake *FakeInfoInterface) IsContainerCalls(stub func() bool) { +func (fake *FakeInfoInterface) IsContainerCalls(stub func() (bool, error)) { fake.isContainerMutex.Lock() defer fake.isContainerMutex.Unlock() fake.IsContainerStub = stub } -func (fake *FakeInfoInterface) IsContainerReturns(result1 bool) { +func (fake *FakeInfoInterface) IsContainerReturns(result1 bool, result2 error) { fake.isContainerMutex.Lock() defer fake.isContainerMutex.Unlock() fake.IsContainerStub = nil fake.isContainerReturns = struct { result1 bool - }{result1} + result2 error + }{result1, result2} } -func (fake *FakeInfoInterface) IsContainerReturnsOnCall(i int, result1 bool) { +func (fake *FakeInfoInterface) IsContainerReturnsOnCall(i int, result1 bool, result2 error) { fake.isContainerMutex.Lock() defer fake.isContainerMutex.Unlock() fake.IsContainerStub = nil if fake.isContainerReturnsOnCall == nil { fake.isContainerReturnsOnCall = make(map[int]struct { result1 bool + result2 error }) } fake.isContainerReturnsOnCall[i] = struct { result1 bool - }{result1} + result2 error + }{result1, result2} } -func (fake *FakeInfoInterface) ResourceID(arg1 context.Context) string { +func (fake *FakeInfoInterface) ResourceID(arg1 context.Context) (string, error) { fake.resourceIDMutex.Lock() ret, specificReturn := fake.resourceIDReturnsOnCall[len(fake.resourceIDArgsForCall)] fake.resourceIDArgsForCall = append(fake.resourceIDArgsForCall, struct { @@ -246,9 +263,9 @@ func (fake *FakeInfoInterface) ResourceID(arg1 context.Context) string { return stub(arg1) } if specificReturn { - return ret.result1 + return ret.result1, ret.result2 } - return fakeReturns.result1 + return fakeReturns.result1, fakeReturns.result2 } func (fake *FakeInfoInterface) ResourceIDCallCount() int { @@ -257,7 +274,7 @@ func (fake *FakeInfoInterface) ResourceIDCallCount() int { return len(fake.resourceIDArgsForCall) } -func (fake *FakeInfoInterface) ResourceIDCalls(stub func(context.Context) string) { +func (fake *FakeInfoInterface) ResourceIDCalls(stub func(context.Context) (string, error)) { fake.resourceIDMutex.Lock() defer fake.resourceIDMutex.Unlock() fake.ResourceIDStub = stub @@ -270,27 +287,30 @@ func (fake *FakeInfoInterface) ResourceIDArgsForCall(i int) context.Context { return argsForCall.arg1 } -func (fake *FakeInfoInterface) ResourceIDReturns(result1 string) { +func (fake *FakeInfoInterface) ResourceIDReturns(result1 string, result2 error) { fake.resourceIDMutex.Lock() defer fake.resourceIDMutex.Unlock() fake.ResourceIDStub = nil fake.resourceIDReturns = struct { result1 string - }{result1} + result2 error + }{result1, result2} } -func (fake *FakeInfoInterface) ResourceIDReturnsOnCall(i int, result1 string) { +func (fake *FakeInfoInterface) ResourceIDReturnsOnCall(i int, result1 string, result2 error) { fake.resourceIDMutex.Lock() defer fake.resourceIDMutex.Unlock() fake.ResourceIDStub = nil if fake.resourceIDReturnsOnCall == nil { fake.resourceIDReturnsOnCall = make(map[int]struct { result1 string + result2 error }) } fake.resourceIDReturnsOnCall[i] = struct { result1 string - }{result1} + result2 error + }{result1, result2} } func (fake *FakeInfoInterface) Invocations() map[string][][]interface{} { diff --git a/pkg/host/info.go b/pkg/host/info.go index 208aa478f..8f220b89d 100644 --- a/pkg/host/info.go +++ b/pkg/host/info.go @@ -9,9 +9,9 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "io" - "log/slog" "os" "regexp" "strings" @@ -72,11 +72,12 @@ var ( //counterfeiter:generate . InfoInterface type ( + // InfoInterface is an interface that defines methods to get information about the host or container. InfoInterface interface { - IsContainer() bool - ResourceID(ctx context.Context) string - ContainerInfo(ctx context.Context) *v1.Resource_ContainerInfo - HostInfo(ctx context.Context) *v1.Resource_HostInfo + IsContainer() (bool, error) + ResourceID(ctx context.Context) (string, error) + ContainerInfo(ctx context.Context) (*v1.Resource_ContainerInfo, error) + HostInfo(ctx context.Context) (*v1.Resource_HostInfo, error) } Info struct { @@ -104,7 +105,7 @@ func NewInfo() *Info { } } -func (i *Info) IsContainer() bool { +func (i *Info) IsContainer() (bool, error) { res, err, _ := singleflightGroup.Do(IsContainerKey, func() (interface{}, error) { for _, filename := range i.containerSpecificFiles { if _, err := os.Stat(filename); err == nil { @@ -112,109 +113,125 @@ func (i *Info) IsContainer() bool { } } - return containsContainerReference(i.selfCgroupLocation), nil + return containsContainerReference(i.selfCgroupLocation) }) - if err != nil { - slog.Warn("Unable to determine if resource is a container or not", "error", err) - return false + return false, err } if result, ok := res.(bool); ok { - return result + return result, nil } - return false + return false, nil } -func (i *Info) ResourceID(ctx context.Context) string { - if i.IsContainer() { +func (i *Info) ResourceID(ctx context.Context) (string, error) { + isContainer, _ := i.IsContainer() + if isContainer { return i.containerID() } return i.hostID(ctx) } -func (i *Info) ContainerInfo(ctx context.Context) *v1.Resource_ContainerInfo { +func (i *Info) ContainerInfo(ctx context.Context) (*v1.Resource_ContainerInfo, error) { + var errs error hostname, err := i.exec.Hostname() if err != nil { - slog.WarnContext(ctx, "Unable to get hostname", "error", err) + errs = errors.Join(errs, errors.New("unable to get hostname -- "+err.Error())) + } + containerId, err := i.containerID() + if err != nil { + errs = errors.Join(errors.New("unable to get container id -- " + err.Error())) + } + releaseInfo, err := i.releaseInfo(ctx, i.osReleaseLocation) + if err != nil { + errs = errors.Join(errors.New("unable to get release info -- " + err.Error())) } return &v1.Resource_ContainerInfo{ ContainerInfo: &v1.ContainerInfo{ - ContainerId: i.containerID(), + ContainerId: containerId, Hostname: hostname, - ReleaseInfo: i.releaseInfo(ctx, i.osReleaseLocation), + ReleaseInfo: releaseInfo, }, - } + }, errs } -func (i *Info) HostInfo(ctx context.Context) *v1.Resource_HostInfo { +func (i *Info) HostInfo(ctx context.Context) (*v1.Resource_HostInfo, error) { hostname, err := i.exec.Hostname() if err != nil { - slog.WarnContext(ctx, "Unable to get hostname", "error", err) + return &v1.Resource_HostInfo{}, errors.New("unable to get hostname -- " + err.Error()) + } + hostID, err := i.hostID(ctx) + if err != nil { + return &v1.Resource_HostInfo{}, errors.New("unable to get host id -- " + err.Error()) + } + releaseInfo, err := i.releaseInfo(ctx, i.osReleaseLocation) + if err != nil { + return &v1.Resource_HostInfo{}, errors.New("unable to get release info -- " + err.Error()) } return &v1.Resource_HostInfo{ HostInfo: &v1.HostInfo{ - HostId: i.hostID(ctx), + HostId: hostID, Hostname: hostname, - ReleaseInfo: i.releaseInfo(ctx, i.osReleaseLocation), + ReleaseInfo: releaseInfo, }, - } + }, nil } -func containsContainerReference(cgroupFile string) bool { +func containsContainerReference(cgroupFile string) (bool, error) { + var err error data, err := os.ReadFile(cgroupFile) if err != nil { - slog.Warn("Unable to check if cgroup file contains a container reference", "error", err) - return false + return false, errors.New("unable to check if cgroup file contains a container reference --" + err.Error()) } scanner := bufio.NewScanner(bytes.NewReader(data)) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if strings.Contains(line, k8sKind) || strings.Contains(line, docker) || strings.Contains(line, containerd) { - return true + return true, nil } } - return false + return false, nil } -func (i *Info) containerID() string { +// containerID returns the container ID of the current running environment. +func (i *Info) containerID() (string, error) { res, err, _ := singleflightGroup.Do(GetContainerIDKey, func() (interface{}, error) { containerID, err := containerIDFromMountInfo(i.mountInfoLocation) return uuid.NewMD5(uuid.NameSpaceDNS, []byte(containerID)).String(), err }) - if err != nil { - slog.Error("Could not get container ID", "error", err) - return "" + return "", errors.New("unable to get container ID -- " + err.Error()) } if result, ok := res.(string); ok { - return result + return result, nil } - return "" + return "", nil } -// containerID returns the container ID of the current running environment. +// containerIDFromMountInfo returns the container ID of the current running environment. // Supports cgroup v1 and v2. Reading "/proc/1/cpuset" would only work for cgroups v1 // mountInfo is the path: "/proc/self/mountinfo" func containerIDFromMountInfo(mountInfo string) (string, error) { + var errs error mInfoFile, err := os.Open(mountInfo) defer func(f *os.File, fileName string) { closeErr := f.Close() if closeErr != nil { - slog.Error("Unable to close file", "file", fileName, "error", closeErr) + errs = errors.Join(err, errors.New("Unable to close file "+fileName+" -- error"+closeErr.Error())) } }(mInfoFile, mountInfo) if err != nil { - return "", fmt.Errorf("could not read %s: %w", mountInfo, err) + return "", errors.Join(errs, fmt.Errorf("could not read %s: %w", mountInfo, err)) } fileScanner := bufio.NewScanner(mInfoFile) @@ -235,7 +252,7 @@ func containerIDFromMountInfo(mountInfo string) (string, error) { } } - return "", fmt.Errorf("container ID not found in %s", mountInfo) + return "", errors.Join(errs, fmt.Errorf("container ID not found in %s", mountInfo)) } func containerIDFromPatterns(word string) string { @@ -271,58 +288,57 @@ func containsContainerID(slices []string) bool { return len(slices) >= 2 && len(slices[1]) == lengthOfContainerID } -func (i *Info) hostID(ctx context.Context) string { +func (i *Info) hostID(ctx context.Context) (string, error) { res, err, _ := singleflightGroup.Do(GetSystemUUIDKey, func() (interface{}, error) { var err error hostID, err := i.exec.HostID(ctx) if err != nil { - slog.WarnContext(ctx, "Unable to get host ID", "error", err) - return "", err + return "", errors.New("unable to get host id -- " + err.Error()) } return uuid.NewMD5(uuid.Nil, []byte(hostID)).String(), err }) - if err != nil { - slog.WarnContext(ctx, "Unable to get host ID", "error", err) - return "" + return "", errors.New("unable to get host id -- " + err.Error()) } if result, ok := res.(string); ok { - return result + return result, nil } - return "" + return "", nil } -func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (releaseInfo *v1.ReleaseInfo) { - hostReleaseInfo := i.exec.ReleaseInfo(ctx) +func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (releaseInfo *v1.ReleaseInfo, err error) { + hostReleaseInfo, err := i.exec.ReleaseInfo(ctx) + if err != nil { + return hostReleaseInfo, errors.New("unable to get host release info -- " + err.Error()) + } osRelease, err := readOsRelease(osReleaseLocation) if err != nil { - slog.WarnContext(ctx, "Unable to read from os release file", "error", err) - - return hostReleaseInfo + return hostReleaseInfo, errors.New("unable to read os release info -- " + err.Error()) } - return mergeHostAndOsReleaseInfo(hostReleaseInfo, osRelease) + return mergeHostAndOsReleaseInfo(hostReleaseInfo, osRelease), nil } func readOsRelease(path string) (map[string]string, error) { + var errs error f, err := os.Open(path) defer func(f *os.File, fileName string) { closeErr := f.Close() if closeErr != nil { - slog.Error("Unable to close file", "file", fileName, "error", closeErr) + errs = errors.Join(err, closeErr) } }(f, path) if err != nil { - return nil, fmt.Errorf("release file %s is unreadable: %w", path, err) + return nil, errors.Join(errs, fmt.Errorf("release file %s is unreadable: %w", path, err)) } info, err := parseOsReleaseFile(f) if err != nil { - return nil, fmt.Errorf("release file %s is unparsable: %w", path, err) + return nil, errors.Join(errs, fmt.Errorf("release file %s is unparsable: %w", path, err)) } return info, nil diff --git a/pkg/host/info_test.go b/pkg/host/info_test.go index ef66345fa..e17317e85 100644 --- a/pkg/host/info_test.go +++ b/pkg/host/info_test.go @@ -419,8 +419,8 @@ func TestInfo_IsContainer(t *testing.T) { info := NewInfo() info.containerSpecificFiles = test.containerSpecificFiles info.selfCgroupLocation = test.selfCgroupLocation - - assert.Equal(tt, test.expected, info.IsContainer()) + isContainer, _ := info.IsContainer() + assert.Equal(tt, test.expected, isContainer) }) } } @@ -516,7 +516,7 @@ func TestInfo_ContainerInfo(t *testing.T) { execMock := &execfakes.FakeExecInterface{} execMock.HostnameReturns(test.expectHostname, nil) - execMock.ReleaseInfoReturns(releaseInfo) + execMock.ReleaseInfoReturns(releaseInfo, nil) _, err = mountInfoFile.WriteString(test.mountInfo) require.NoError(tt, err) @@ -528,7 +528,11 @@ func TestInfo_ContainerInfo(t *testing.T) { info.mountInfoLocation = mountInfoFile.Name() info.exec = execMock info.osReleaseLocation = "/non/existent" - containerInfo := info.ContainerInfo(ctx) + + containerInfo, err := info.ContainerInfo(ctx) + if err != nil { + t.Logf("error %v", err) + } assert.Equal(tt, test.expectContainerID, containerInfo.ContainerInfo.GetContainerId()) assert.Equal(tt, test.expectHostname, containerInfo.ContainerInfo.GetHostname()) @@ -556,12 +560,13 @@ func TestInfo_HostInfo(t *testing.T) { execMock := &execfakes.FakeExecInterface{} execMock.HostnameReturns("server.com", nil) execMock.HostIDReturns("test-host-id", nil) - execMock.ReleaseInfoReturns(releaseInfo) + execMock.ReleaseInfoReturns(releaseInfo, nil) info := NewInfo() info.exec = execMock info.osReleaseLocation = osReleaseFile.Name() - hostInfo := info.HostInfo(ctx) + hostInfo, err := info.HostInfo(ctx) + require.NoError(t, err) expectedReleaseInfo := &v1.ReleaseInfo{ Codename: "focal", From 312fbd6157f262718b447de54138ae2d904faa20 Mon Sep 17 00:00:00 2001 From: Sean Breen Date: Wed, 6 Aug 2025 17:39:02 +0100 Subject: [PATCH 4/7] fix lint errors --- pkg/host/info.go | 4 ++-- pkg/host/info_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/host/info.go b/pkg/host/info.go index 8f220b89d..1ab272e1a 100644 --- a/pkg/host/info.go +++ b/pkg/host/info.go @@ -326,12 +326,12 @@ func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (relea func readOsRelease(path string) (map[string]string, error) { var errs error f, err := os.Open(path) - defer func(f *os.File, fileName string) { + defer func(f *os.File) { closeErr := f.Close() if closeErr != nil { errs = errors.Join(err, closeErr) } - }(f, path) + }(f) if err != nil { return nil, errors.Join(errs, fmt.Errorf("release file %s is unreadable: %w", path, err)) } diff --git a/pkg/host/info_test.go b/pkg/host/info_test.go index e17317e85..3b3a0e331 100644 --- a/pkg/host/info_test.go +++ b/pkg/host/info_test.go @@ -529,8 +529,8 @@ func TestInfo_ContainerInfo(t *testing.T) { info.exec = execMock info.osReleaseLocation = "/non/existent" - containerInfo, err := info.ContainerInfo(ctx) - if err != nil { + containerInfo, containerErr := info.ContainerInfo(ctx) + if containerErr != nil { t.Logf("error %v", err) } From 600289b106f828613830bf1689dc352729d2cdb6 Mon Sep 17 00:00:00 2001 From: Donal Hurley Date: Thu, 7 Aug 2025 16:24:01 +0100 Subject: [PATCH 5/7] Update error handling --- go.mod | 2 +- internal/resource/resource_service.go | 2 + pkg/host/exec/exec.go | 16 +++- pkg/host/info.go | 106 +++++++++----------------- pkg/host/info_test.go | 14 ++-- 5 files changed, 63 insertions(+), 77 deletions(-) diff --git a/go.mod b/go.mod index 1c1886634..91677246f 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,6 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/mod v0.23.0 - golang.org/x/sync v0.13.0 google.golang.org/protobuf v1.36.6 ) @@ -292,6 +291,7 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/sync v0.13.0 // indirect golang.org/x/tools v0.30.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect diff --git a/internal/resource/resource_service.go b/internal/resource/resource_service.go index 5fce707f1..4a4f69121 100644 --- a/internal/resource/resource_service.go +++ b/internal/resource/resource_service.go @@ -392,6 +392,7 @@ func (r *ResourceService) updateResourceInfo(ctx context.Context) { r.resource.Info, err = r.info.ContainerInfo(ctx) if err != nil { slog.ErrorContext(ctx, "Failed to get container info", "error", err) + return } r.resource.ResourceId = r.resource.GetContainerInfo().GetContainerId() r.resource.Instances = []*mpi.Instance{} @@ -399,6 +400,7 @@ func (r *ResourceService) updateResourceInfo(ctx context.Context) { r.resource.Info, err = r.info.HostInfo(ctx) if err != nil { slog.ErrorContext(ctx, "Failed to get host info", "error", err) + return } r.resource.ResourceId = r.resource.GetHostInfo().GetHostId() r.resource.Instances = []*mpi.Instance{} diff --git a/pkg/host/exec/exec.go b/pkg/host/exec/exec.go index c9111d510..e8b1b6a09 100644 --- a/pkg/host/exec/exec.go +++ b/pkg/host/exec/exec.go @@ -8,7 +8,6 @@ package exec import ( "bytes" "context" - "errors" "os" "os/exec" "syscall" @@ -32,6 +31,9 @@ type ExecInterface interface { type Exec struct{} +// RunCmd executes a command with the given arguments and returns its output. +// It combines stdout and stderr into a single buffer. +// If the command fails, the error is returned along with any output that was produced. func (*Exec) RunCmd(ctx context.Context, cmd string, args ...string) (*bytes.Buffer, error) { command := exec.CommandContext(ctx, cmd, args...) @@ -43,34 +45,44 @@ func (*Exec) RunCmd(ctx context.Context, cmd string, args ...string) (*bytes.Buf return bytes.NewBuffer(output), nil } +// Executable returns the path to the current executable. func (*Exec) Executable() (string, error) { return os.Executable() } +// FindExecutable searches for an executable named by the given file name in the +// directories listed in the PATH environment variable. func (*Exec) FindExecutable(name string) (string, error) { return exec.LookPath(name) } +// ProcessID returns the process ID of the current process. func (*Exec) ProcessID() int32 { return int32(os.Getpid()) } +// KillProcess sends a SIGHUP signal to the process with the given pid. func (*Exec) KillProcess(pid int32) error { return syscall.Kill(int(pid), syscall.SIGHUP) } +// Hostname returns the host name reported by the kernel. func (*Exec) Hostname() (string, error) { return os.Hostname() } +// HostID returns a unique ID for the host machine. +// The context can be used to cancel the operation. func (*Exec) HostID(ctx context.Context) (string, error) { return host.HostIDWithContext(ctx) } +// ReleaseInfo returns operating system release information. +// It provides details about the platform, version, and other system information. func (*Exec) ReleaseInfo(ctx context.Context) (*v1.ReleaseInfo, error) { hostInfo, err := host.InfoWithContext(ctx) if err != nil { - return &v1.ReleaseInfo{}, errors.New("Could not read release information for host error=" + err.Error()) + return &v1.ReleaseInfo{}, err } return &v1.ReleaseInfo{ diff --git a/pkg/host/info.go b/pkg/host/info.go index 1ab272e1a..cbd2d8256 100644 --- a/pkg/host/info.go +++ b/pkg/host/info.go @@ -20,7 +20,6 @@ import ( "github.com/google/uuid" "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "golang.org/x/sync/singleflight" ) const ( @@ -44,15 +43,9 @@ const ( codeName = "VERSION_CODENAME" id = "ID" name = "NAME" - - IsContainerKey = "IsContainer" - GetContainerIDKey = "GetContainerID" - GetSystemUUIDKey = "GetSystemUUIDKey" ) var ( - singleflightGroup = &singleflight.Group{} - // example: /docker/f244832c5a58377c3f1c7581b311c5bd8479808741f3e912d8bea8afe6431cb4 basePattern = regexp.MustCompile("/([a-f0-9]{64})$") // nolint: lll @@ -91,6 +84,8 @@ type ( } ) +// NewInfo creates and returns a new Info instance with default settings for container detection +// and operating system information retrieval. func NewInfo() *Info { return &Info{ containerSpecificFiles: []string{ @@ -105,27 +100,22 @@ func NewInfo() *Info { } } +// IsContainer determines if the current environment is running inside a container. +// It checks for container-specific files and container references in cgroup. +// Returns true if running in a container, false otherwise. func (i *Info) IsContainer() (bool, error) { - res, err, _ := singleflightGroup.Do(IsContainerKey, func() (interface{}, error) { - for _, filename := range i.containerSpecificFiles { - if _, err := os.Stat(filename); err == nil { - return true, nil - } + for _, filename := range i.containerSpecificFiles { + if _, err := os.Stat(filename); err == nil { + return true, nil } - - return containsContainerReference(i.selfCgroupLocation) - }) - if err != nil { - return false, err } - if result, ok := res.(bool); ok { - return result, nil - } - - return false, nil + return containsContainerReference(i.selfCgroupLocation) } +// ResourceID returns a unique identifier for the resource. +// If running in a container, it returns the container ID. +// Otherwise, it returns the host ID. func (i *Info) ResourceID(ctx context.Context) (string, error) { isContainer, _ := i.IsContainer() if isContainer { @@ -135,19 +125,20 @@ func (i *Info) ResourceID(ctx context.Context) (string, error) { return i.hostID(ctx) } +// ContainerInfo returns container-specific information including container ID, hostname, +// and operating system release details when running in a containerized environment. func (i *Info) ContainerInfo(ctx context.Context) (*v1.Resource_ContainerInfo, error) { - var errs error hostname, err := i.exec.Hostname() if err != nil { - errs = errors.Join(errs, errors.New("unable to get hostname -- "+err.Error())) + return nil, err } containerId, err := i.containerID() if err != nil { - errs = errors.Join(errors.New("unable to get container id -- " + err.Error())) + return nil, err } releaseInfo, err := i.releaseInfo(ctx, i.osReleaseLocation) if err != nil { - errs = errors.Join(errors.New("unable to get release info -- " + err.Error())) + return nil, err } return &v1.Resource_ContainerInfo{ @@ -156,21 +147,23 @@ func (i *Info) ContainerInfo(ctx context.Context) (*v1.Resource_ContainerInfo, e Hostname: hostname, ReleaseInfo: releaseInfo, }, - }, errs + }, nil } +// HostInfo returns information about the host system including host ID, hostname, +// and operating system release details. func (i *Info) HostInfo(ctx context.Context) (*v1.Resource_HostInfo, error) { hostname, err := i.exec.Hostname() if err != nil { - return &v1.Resource_HostInfo{}, errors.New("unable to get hostname -- " + err.Error()) + return nil, err } hostID, err := i.hostID(ctx) if err != nil { - return &v1.Resource_HostInfo{}, errors.New("unable to get host id -- " + err.Error()) + return nil, err } releaseInfo, err := i.releaseInfo(ctx, i.osReleaseLocation) if err != nil { - return &v1.Resource_HostInfo{}, errors.New("unable to get release info -- " + err.Error()) + return nil, err } return &v1.Resource_HostInfo{ @@ -183,10 +176,9 @@ func (i *Info) HostInfo(ctx context.Context) (*v1.Resource_HostInfo, error) { } func containsContainerReference(cgroupFile string) (bool, error) { - var err error data, err := os.ReadFile(cgroupFile) if err != nil { - return false, errors.New("unable to check if cgroup file contains a container reference --" + err.Error()) + return false, err } scanner := bufio.NewScanner(bytes.NewReader(data)) @@ -202,19 +194,8 @@ func containsContainerReference(cgroupFile string) (bool, error) { // containerID returns the container ID of the current running environment. func (i *Info) containerID() (string, error) { - res, err, _ := singleflightGroup.Do(GetContainerIDKey, func() (interface{}, error) { - containerID, err := containerIDFromMountInfo(i.mountInfoLocation) - return uuid.NewMD5(uuid.NameSpaceDNS, []byte(containerID)).String(), err - }) - if err != nil { - return "", errors.New("unable to get container ID -- " + err.Error()) - } - - if result, ok := res.(string); ok { - return result, nil - } - - return "", nil + containerID, err := containerIDFromMountInfo(i.mountInfoLocation) + return uuid.NewMD5(uuid.NameSpaceDNS, []byte(containerID)).String(), err } // containerIDFromMountInfo returns the container ID of the current running environment. @@ -223,15 +204,15 @@ func (i *Info) containerID() (string, error) { func containerIDFromMountInfo(mountInfo string) (string, error) { var errs error mInfoFile, err := os.Open(mountInfo) - defer func(f *os.File, fileName string) { + defer func(f *os.File) { closeErr := f.Close() if closeErr != nil { - errs = errors.Join(err, errors.New("Unable to close file "+fileName+" -- error"+closeErr.Error())) + errs = errors.Join(err, closeErr) } - }(mInfoFile, mountInfo) + }(mInfoFile) if err != nil { - return "", errors.Join(errs, fmt.Errorf("could not read %s: %w", mountInfo, err)) + return "", errors.Join(errs, err) } fileScanner := bufio.NewScanner(mInfoFile) @@ -289,35 +270,24 @@ func containsContainerID(slices []string) bool { } func (i *Info) hostID(ctx context.Context) (string, error) { - res, err, _ := singleflightGroup.Do(GetSystemUUIDKey, func() (interface{}, error) { - var err error - - hostID, err := i.exec.HostID(ctx) - if err != nil { - return "", errors.New("unable to get host id -- " + err.Error()) - } - - return uuid.NewMD5(uuid.Nil, []byte(hostID)).String(), err - }) + hostID, err := i.exec.HostID(ctx) if err != nil { - return "", errors.New("unable to get host id -- " + err.Error()) - } - - if result, ok := res.(string); ok { - return result, nil + return "", err } - return "", nil + return uuid.NewMD5(uuid.Nil, []byte(hostID)).String(), err } -func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (releaseInfo *v1.ReleaseInfo, err error) { +func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (*v1.ReleaseInfo, error) { hostReleaseInfo, err := i.exec.ReleaseInfo(ctx) if err != nil { - return hostReleaseInfo, errors.New("unable to get host release info -- " + err.Error()) + return hostReleaseInfo, err } osRelease, err := readOsRelease(osReleaseLocation) if err != nil { - return hostReleaseInfo, errors.New("unable to read os release info -- " + err.Error()) + // If there is an error reading the OS release file just return the host release info instead + // nolint: nilerr + return hostReleaseInfo, nil } return mergeHostAndOsReleaseInfo(hostReleaseInfo, osRelease), nil diff --git a/pkg/host/info_test.go b/pkg/host/info_test.go index 3b3a0e331..aa5f8ac26 100644 --- a/pkg/host/info_test.go +++ b/pkg/host/info_test.go @@ -530,13 +530,15 @@ func TestInfo_ContainerInfo(t *testing.T) { info.osReleaseLocation = "/non/existent" containerInfo, containerErr := info.ContainerInfo(ctx) - if containerErr != nil { - t.Logf("error %v", err) + if test.expectContainerID != "" { + require.NoError(tt, containerErr) + assert.Equal(tt, test.expectContainerID, containerInfo.ContainerInfo.GetContainerId()) + assert.Equal(tt, test.expectHostname, containerInfo.ContainerInfo.GetHostname()) + assert.Equal(tt, releaseInfo, containerInfo.ContainerInfo.GetReleaseInfo()) + } else { + require.Error(tt, containerErr) + assert.Nil(tt, containerInfo) } - - assert.Equal(tt, test.expectContainerID, containerInfo.ContainerInfo.GetContainerId()) - assert.Equal(tt, test.expectHostname, containerInfo.ContainerInfo.GetHostname()) - assert.Equal(tt, releaseInfo, containerInfo.ContainerInfo.GetReleaseInfo()) }) } } From b6edd2a078523213aa5629dbf55afe9e93f2569e Mon Sep 17 00:00:00 2001 From: Sean Breen Date: Fri, 8 Aug 2025 10:41:20 +0100 Subject: [PATCH 6/7] update comment, move methods on Info together --- internal/config/config.go | 2 +- pkg/host/info.go | 78 ++++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 9a02ede15..649a2366b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -288,7 +288,7 @@ func addDefaultProcessors(collector *Collector) { func addDefaultHostMetricsReceiver(collector *Collector) { isContainer, err := host.NewInfo().IsContainer() if err != nil { - slog.Warn("Failed to get host info", "error", err) + slog.Debug("No container information found", "error", err) } if isContainer { addDefaultContainerHostMetricsReceiver(collector) diff --git a/pkg/host/info.go b/pkg/host/info.go index cbd2d8256..fa92a2a9d 100644 --- a/pkg/host/info.go +++ b/pkg/host/info.go @@ -48,14 +48,18 @@ const ( var ( // example: /docker/f244832c5a58377c3f1c7581b311c5bd8479808741f3e912d8bea8afe6431cb4 basePattern = regexp.MustCompile("/([a-f0-9]{64})$") + // nolint: lll // example: /system.slice/containerd.service/kubepods-besteffort-pod214f3ba8_4b69_4bdb_a7d5_5ecc73f04ae9.slice:cri-containerd:d4e8e05a546c86b6443f101966c618e47753ed01fa9929cae00d3b692f7a9f80 colonPattern = regexp.MustCompile(":([a-f0-9]{64})$") + // example: /system.slice/crio-9e524432d716aa750574c9b6c01dee49e4b453445006684aad94c3d6df849e5c.scope scopePattern = regexp.MustCompile(`/.+-(.+?).scope$`) + // nolint: lll // example: /containers/storage/overlay-containers/ba0be90007be48bca767be0a462390ad2c9b0e910608158f79c8d6a984302b7e/userdata/hostname containersPattern = regexp.MustCompile("containers/([a-f0-9]{64})") + // nolint: lll // example: /var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/d7cb24ec5dede02990283dec30bd1e6ae1f93e3e19b152b708b7e0e133c6baec/hostname containerdPattern = regexp.MustCompile("sandboxes/([a-f0-9]{64})") @@ -74,12 +78,13 @@ type ( } Info struct { + exec exec.ExecInterface + selfCgroupLocation string + mountInfoLocation string + osReleaseLocation string + // containerSpecificFiles are files that are only created in containers. // We use this to determine if an instance is running in a container or not - exec exec.ExecInterface - selfCgroupLocation string - mountInfoLocation string - osReleaseLocation string containerSpecificFiles []string } ) @@ -175,6 +180,39 @@ func (i *Info) HostInfo(ctx context.Context) (*v1.Resource_HostInfo, error) { }, nil } +// hostID returns a unique identifier for the host system. +func (i *Info) hostID(ctx context.Context) (string, error) { + hostID, err := i.exec.HostID(ctx) + if err != nil { + return "", err + } + + return uuid.NewMD5(uuid.Nil, []byte(hostID)).String(), err +} + +// releaseInfo retrieves the operating system release information. +func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (*v1.ReleaseInfo, error) { + hostReleaseInfo, err := i.exec.ReleaseInfo(ctx) + if err != nil { + return hostReleaseInfo, err + } + osRelease, err := readOsRelease(osReleaseLocation) + if err != nil { + // If there is an error reading the OS release file just return the host release info instead + // nolint: nilerr + return hostReleaseInfo, nil + } + + return mergeHostAndOsReleaseInfo(hostReleaseInfo, osRelease), nil +} + +// containerID returns the container ID of the current running environment. +func (i *Info) containerID() (string, error) { + containerID, err := containerIDFromMountInfo(i.mountInfoLocation) + return uuid.NewMD5(uuid.NameSpaceDNS, []byte(containerID)).String(), err +} + +// containsContainerReference checks if the cgroup file contains references to container runtimes. func containsContainerReference(cgroupFile string) (bool, error) { data, err := os.ReadFile(cgroupFile) if err != nil { @@ -192,12 +230,6 @@ func containsContainerReference(cgroupFile string) (bool, error) { return false, nil } -// containerID returns the container ID of the current running environment. -func (i *Info) containerID() (string, error) { - containerID, err := containerIDFromMountInfo(i.mountInfoLocation) - return uuid.NewMD5(uuid.NameSpaceDNS, []byte(containerID)).String(), err -} - // containerIDFromMountInfo returns the container ID of the current running environment. // Supports cgroup v1 and v2. Reading "/proc/1/cpuset" would only work for cgroups v1 // mountInfo is the path: "/proc/self/mountinfo" @@ -236,6 +268,7 @@ func containerIDFromMountInfo(mountInfo string) (string, error) { return "", errors.Join(errs, fmt.Errorf("container ID not found in %s", mountInfo)) } +// containerIDFromPatterns checks a word against multiple regex patterns to extract the container ID. func containerIDFromPatterns(word string) string { slices := scopePattern.FindStringSubmatch(word) if containsContainerID(slices) { @@ -265,34 +298,11 @@ func containerIDFromPatterns(word string) string { return "" } +// containsContainerID checks if the provided slices contain a valid container ID. func containsContainerID(slices []string) bool { return len(slices) >= 2 && len(slices[1]) == lengthOfContainerID } -func (i *Info) hostID(ctx context.Context) (string, error) { - hostID, err := i.exec.HostID(ctx) - if err != nil { - return "", err - } - - return uuid.NewMD5(uuid.Nil, []byte(hostID)).String(), err -} - -func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (*v1.ReleaseInfo, error) { - hostReleaseInfo, err := i.exec.ReleaseInfo(ctx) - if err != nil { - return hostReleaseInfo, err - } - osRelease, err := readOsRelease(osReleaseLocation) - if err != nil { - // If there is an error reading the OS release file just return the host release info instead - // nolint: nilerr - return hostReleaseInfo, nil - } - - return mergeHostAndOsReleaseInfo(hostReleaseInfo, osRelease), nil -} - func readOsRelease(path string) (map[string]string, error) { var errs error f, err := os.Open(path) From 74ecf471c2c0b5510da9f534264187013b791939 Mon Sep 17 00:00:00 2001 From: Sean Breen Date: Fri, 15 Aug 2025 14:22:22 +0100 Subject: [PATCH 7/7] fix import paths --- internal/datasource/nginx/process.go | 2 +- internal/datasource/nginx/process_test.go | 2 +- internal/resource/nginx_instance_operator_test.go | 5 +++++ internal/resource/nginx_instance_process_operator.go | 2 +- internal/resource/nginx_instance_process_operator_test.go | 2 +- internal/resource/resource_service.go | 3 +++ internal/resource/resourcefakes/fake_process_operator.go | 2 +- internal/watcher/instance/nginx_process_parser.go | 1 + 8 files changed, 14 insertions(+), 5 deletions(-) diff --git a/internal/datasource/nginx/process.go b/internal/datasource/nginx/process.go index 8a9f5b109..1cd039609 100644 --- a/internal/datasource/nginx/process.go +++ b/internal/datasource/nginx/process.go @@ -13,8 +13,8 @@ import ( "regexp" "strings" - "github.com/nginx/agent/v3/internal/datasource/host/exec" "github.com/nginx/agent/v3/internal/model" + "github.com/nginx/agent/v3/pkg/host/exec" "github.com/nginx/agent/v3/pkg/nginxprocess" ) diff --git a/internal/datasource/nginx/process_test.go b/internal/datasource/nginx/process_test.go index 331a370ba..bb873498c 100644 --- a/internal/datasource/nginx/process_test.go +++ b/internal/datasource/nginx/process_test.go @@ -11,7 +11,7 @@ import ( "errors" "testing" - "github.com/nginx/agent/v3/internal/datasource/host/exec/execfakes" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" "github.com/stretchr/testify/assert" ) diff --git a/internal/resource/nginx_instance_operator_test.go b/internal/resource/nginx_instance_operator_test.go index 800ba8ad2..df51b04ab 100644 --- a/internal/resource/nginx_instance_operator_test.go +++ b/internal/resource/nginx_instance_operator_test.go @@ -15,6 +15,11 @@ import ( "testing" "time" + "github.com/nginx/agent/v3/internal/config" + "github.com/nginx/agent/v3/internal/resource/resourcefakes" + "github.com/nginx/agent/v3/pkg/nginxprocess" + "github.com/nginx/agent/v3/test/stub" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" "github.com/nginx/agent/v3/test/helpers" diff --git a/internal/resource/nginx_instance_process_operator.go b/internal/resource/nginx_instance_process_operator.go index 2bcb7816e..4a13d5146 100644 --- a/internal/resource/nginx_instance_process_operator.go +++ b/internal/resource/nginx_instance_process_operator.go @@ -10,8 +10,8 @@ import ( "errors" "log/slog" - "github.com/nginx/agent/v3/internal/datasource/host/exec" "github.com/nginx/agent/v3/internal/datasource/nginx" + "github.com/nginx/agent/v3/pkg/host/exec" "github.com/nginx/agent/v3/pkg/id" "github.com/nginx/agent/v3/pkg/nginxprocess" diff --git a/internal/resource/nginx_instance_process_operator_test.go b/internal/resource/nginx_instance_process_operator_test.go index 65fe664ae..940a4e858 100644 --- a/internal/resource/nginx_instance_process_operator_test.go +++ b/internal/resource/nginx_instance_process_operator_test.go @@ -13,7 +13,7 @@ import ( "testing" "time" - "github.com/nginx/agent/v3/internal/datasource/host/exec/execfakes" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" "github.com/nginx/agent/v3/pkg/nginxprocess" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/resource/resource_service.go b/internal/resource/resource_service.go index 1c79e03d9..1e848f122 100644 --- a/internal/resource/resource_service.go +++ b/internal/resource/resource_service.go @@ -19,6 +19,9 @@ import ( "strings" "sync" + "github.com/nginx/agent/v3/pkg/host/exec" + "github.com/nginx/agent/v3/pkg/nginxprocess" + "github.com/nginx/agent/v3/pkg/host" parser "github.com/nginx/agent/v3/internal/datasource/config" diff --git a/internal/resource/resourcefakes/fake_process_operator.go b/internal/resource/resourcefakes/fake_process_operator.go index 9774d58f7..0b8ac14f9 100644 --- a/internal/resource/resourcefakes/fake_process_operator.go +++ b/internal/resource/resourcefakes/fake_process_operator.go @@ -5,7 +5,7 @@ import ( "context" "sync" - "github.com/nginx/agent/v3/internal/datasource/host/exec" + "github.com/nginx/agent/v3/pkg/host/exec" "github.com/nginx/agent/v3/pkg/nginxprocess" ) diff --git a/internal/watcher/instance/nginx_process_parser.go b/internal/watcher/instance/nginx_process_parser.go index 6b35f87a0..47884eeb6 100644 --- a/internal/watcher/instance/nginx_process_parser.go +++ b/internal/watcher/instance/nginx_process_parser.go @@ -17,6 +17,7 @@ import ( "github.com/nginx/agent/v3/internal/model" mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" + "github.com/nginx/agent/v3/pkg/host/exec" "github.com/nginx/agent/v3/pkg/id" "github.com/nginx/agent/v3/pkg/nginxprocess" )