diff --git a/go.mod b/go.mod index 704346f00..e20681b86 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/config/config.go b/internal/config/config.go index 54cd65529..707d3f396 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,8 +20,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" @@ -285,7 +286,11 @@ func addDefaultProcessors(collector *Collector) { } func addDefaultHostMetricsReceiver(collector *Collector) { - if host.NewInfo().IsContainer() { + isContainer, err := host.NewInfo().IsContainer() + if err != nil { + slog.Debug("No container information found", "error", err) + } + if isContainer { addDefaultContainerHostMetricsReceiver(collector) } else { addDefaultVMHostMetricsReceiver(collector) 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/grpc/grpc.go b/internal/grpc/grpc.go index f9099b334..654794912 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" @@ -87,10 +88,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/nginx_instance_operator.go b/internal/resource/nginx_instance_operator.go index 6a4a68229..ee5063742 100644 --- a/internal/resource/nginx_instance_operator.go +++ b/internal/resource/nginx_instance_operator.go @@ -16,9 +16,10 @@ import ( "github.com/nginx/agent/v3/internal/backoff" "github.com/nginx/agent/v3/pkg/nginxprocess" + "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 1f5a39f58..df51b04ab 100644 --- a/internal/resource/nginx_instance_operator_test.go +++ b/internal/resource/nginx_instance_operator_test.go @@ -15,15 +15,13 @@ import ( "testing" "time" + "github.com/nginx/agent/v3/internal/config" "github.com/nginx/agent/v3/internal/resource/resourcefakes" - - "github.com/nginx/agent/v3/test/stub" - "github.com/nginx/agent/v3/pkg/nginxprocess" + "github.com/nginx/agent/v3/test/stub" - "github.com/nginx/agent/v3/internal/config" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" - "github.com/nginx/agent/v3/internal/datasource/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/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 9cfc1b88d..7b6ecaa2c 100644 --- a/internal/resource/resource_service.go +++ b/internal/resource/resource_service.go @@ -19,10 +19,11 @@ import ( "strings" "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" + "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" @@ -34,8 +35,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" ) @@ -398,12 +397,25 @@ 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) + return + } 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) + return + } 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 e4b4beb05..842dacdb2 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" @@ -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/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/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 f2ffc1bba..4e9d02b6e 100644 --- a/internal/watcher/instance/nginx_process_parser.go +++ b/internal/watcher/instance/nginx_process_parser.go @@ -17,7 +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/internal/datasource/host/exec" + "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/watcher/instance/nginx_process_parser_test.go b/internal/watcher/instance/nginx_process_parser_test.go index 09fcf9d85..409e7e976 100644 --- a/internal/watcher/instance/nginx_process_parser_test.go +++ b/internal/watcher/instance/nginx_process_parser_test.go @@ -14,12 +14,13 @@ import ( "strings" "testing" + "github.com/nginx/agent/v3/pkg/host/exec/execfakes" + "github.com/nginx/agent/v3/internal/model" "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 63% rename from internal/datasource/host/exec/exec.go rename to pkg/host/exec/exec.go index 8a160e263..e8b1b6a09 100644 --- a/internal/datasource/host/exec/exec.go +++ b/pkg/host/exec/exec.go @@ -8,7 +8,6 @@ package exec import ( "bytes" "context" - "log/slog" "os" "os/exec" "syscall" @@ -27,11 +26,14 @@ 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{} +// 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,35 +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) } -func (*Exec) ReleaseInfo(ctx context.Context) (releaseInfo *v1.ReleaseInfo) { +// 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 { - slog.ErrorContext(ctx, "Could not read release information for host", "error", err) - return &v1.ReleaseInfo{} + return &v1.ReleaseInfo{}, err } return &v1.ReleaseInfo{ @@ -80,5 +91,5 @@ func (*Exec) ReleaseInfo(ctx context.Context) (releaseInfo *v1.ReleaseInfo) { Codename: hostInfo.OS, Name: hostInfo.PlatformFamily, Id: hostInfo.Platform, - } + }, nil } 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 97% rename from internal/datasource/host/exec/execfakes/fake_exec_interface.go rename to pkg/host/exec/execfakes/fake_exec_interface.go index a023d6dc3..30f00f7c7 100644 --- a/internal/datasource/host/exec/execfakes/fake_exec_interface.go +++ b/pkg/host/exec/execfakes/fake_exec_interface.go @@ -7,7 +7,7 @@ import ( "sync" v1 "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host/exec" + "github.com/nginx/agent/v3/pkg/host/exec" ) type FakeExecInterface struct { @@ -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/internal/datasource/host/hostfakes/fake_info_interface.go b/pkg/host/hostfakes/fake_info_interface.go similarity index 80% rename from internal/datasource/host/hostfakes/fake_info_interface.go rename to pkg/host/hostfakes/fake_info_interface.go index b0b302313..bed75fa03 100644 --- a/internal/datasource/host/hostfakes/fake_info_interface.go +++ b/pkg/host/hostfakes/fake_info_interface.go @@ -6,58 +6,66 @@ import ( "sync" v1 "github.com/nginx/agent/v3/api/grpc/mpi/v1" - "github.com/nginx/agent/v3/internal/datasource/host" + "github.com/nginx/agent/v3/pkg/host" ) 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/internal/datasource/host/info.go b/pkg/host/info.go similarity index 61% rename from internal/datasource/host/info.go rename to pkg/host/info.go index 600a49b25..6baf46973 100644 --- a/internal/datasource/host/info.go +++ b/pkg/host/info.go @@ -9,17 +9,17 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "io" - "log/slog" "os" "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" ) const ( @@ -43,20 +43,15 @@ 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 // needs to be in one line // 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 // needs to be in one line @@ -71,24 +66,28 @@ 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 { + 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 } ) +// 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{ @@ -103,117 +102,145 @@ func NewInfo() *Info { } } -func (i *Info) IsContainer() bool { - res, err, _ := singleflightGroup.Do(IsContainerKey, func() (interface{}, error) { - for _, filename := range i.containerSpecificFiles { - if _, err := os.Stat(filename); err == nil { - return true, nil - } +// 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) { + for _, filename := range i.containerSpecificFiles { + if _, err := os.Stat(filename); err == nil { + return true, nil } - - return containsContainerReference(i.selfCgroupLocation), nil - }) - - if err != nil { - slog.Warn("Unable to determine if resource is a container or not", "error", err) - return false - } - - if result, ok := res.(bool); ok { - return result } - return false + return containsContainerReference(i.selfCgroupLocation) } -func (i *Info) ResourceID(ctx context.Context) string { - if i.IsContainer() { +// 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 { return i.containerID() } return i.hostID(ctx) } -func (i *Info) ContainerInfo(ctx context.Context) *v1.Resource_ContainerInfo { +// 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) { hostname, err := i.exec.Hostname() if err != nil { - slog.WarnContext(ctx, "Unable to get hostname", "error", err) + return nil, err + } + containerId, err := i.containerID() + if err != nil { + return nil, err + } + releaseInfo, err := i.releaseInfo(ctx, i.osReleaseLocation) + if err != nil { + return nil, err } return &v1.Resource_ContainerInfo{ ContainerInfo: &v1.ContainerInfo{ - ContainerId: i.containerID(), + ContainerId: containerId, Hostname: hostname, - ReleaseInfo: i.releaseInfo(ctx, i.osReleaseLocation), + ReleaseInfo: releaseInfo, }, - } + }, nil } -func (i *Info) HostInfo(ctx context.Context) *v1.Resource_HostInfo { +// 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 { - slog.WarnContext(ctx, "Unable to get hostname", "error", err) + return nil, err + } + hostID, err := i.hostID(ctx) + if err != nil { + return nil, err + } + releaseInfo, err := i.releaseInfo(ctx, i.osReleaseLocation) + if err != nil { + return nil, err } 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 { - data, err := os.ReadFile(cgroupFile) +// 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 { - slog.Warn("Unable to check if cgroup file contains a container reference", "error", err) - return false + return "", err } - 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 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 { + //nolint:nilerr // If there is an error reading the OS release file just return the host release info instead + return hostReleaseInfo, nil } - return false + return mergeHostAndOsReleaseInfo(hostReleaseInfo, osRelease), nil } -func (i *Info) containerID() string { - res, err, _ := singleflightGroup.Do(GetContainerIDKey, func() (interface{}, error) { - containerID, err := containerIDFromMountInfo(i.mountInfoLocation) - return uuid.NewMD5(uuid.NameSpaceDNS, []byte(containerID)).String(), err - }) +// 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 { - slog.Error("Could not get container ID", "error", err) - return "" + return false, err } - if result, ok := res.(string); ok { - return result + 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, nil + } } - return "" + return false, 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) { + defer func(f *os.File) { closeErr := f.Close() if closeErr != nil { - slog.Error("Unable to close file", "file", fileName, "error", closeErr) + errs = errors.Join(err, closeErr) } - }(mInfoFile, mountInfo) + }(mInfoFile) if err != nil { - return "", fmt.Errorf("could not read %s: %w", mountInfo, err) + return "", errors.Join(errs, err) } fileScanner := bufio.NewScanner(mInfoFile) @@ -234,9 +261,10 @@ 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)) } +// 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) { @@ -266,62 +294,27 @@ 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 { - 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 uuid.NewMD5(uuid.Nil, []byte(hostID)).String(), err - }) - - if err != nil { - slog.WarnContext(ctx, "Unable to get host ID", "error", err) - return "" - } - - if result, ok := res.(string); ok { - return result - } - - return "" -} - -func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (releaseInfo *v1.ReleaseInfo) { - hostReleaseInfo := i.exec.ReleaseInfo(ctx) - osRelease, err := readOsRelease(osReleaseLocation) - if err != nil { - slog.WarnContext(ctx, "Unable to read from os release file", "error", err) - - return hostReleaseInfo - } - - return mergeHostAndOsReleaseInfo(hostReleaseInfo, osRelease) -} - 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 { - slog.Error("Unable to close file", "file", fileName, "error", closeErr) + errs = errors.Join(err, closeErr) } - }(f, path) + }(f) 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/internal/datasource/host/info_test.go b/pkg/host/info_test.go similarity index 98% rename from internal/datasource/host/info_test.go rename to pkg/host/info_test.go index e4e7b050a..a7b7be491 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" @@ -418,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) }) } } @@ -515,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) @@ -527,11 +528,17 @@ func TestInfo_ContainerInfo(t *testing.T) { info.mountInfoLocation = mountInfoFile.Name() info.exec = execMock info.osReleaseLocation = "/non/existent" - containerInfo := info.ContainerInfo(ctx) - assert.Equal(tt, test.expectContainerID, containerInfo.ContainerInfo.GetContainerId()) - assert.Equal(tt, test.expectHostname, containerInfo.ContainerInfo.GetHostname()) - assert.Equal(tt, releaseInfo, containerInfo.ContainerInfo.GetReleaseInfo()) + containerInfo, containerErr := info.ContainerInfo(ctx) + 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) + } }) } } @@ -555,12 +562,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",