diff --git a/internal/config/config.go b/internal/config/config.go index 867865d0c..01fd3d53e 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" @@ -271,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 fe0feaf62..779fcb9a4 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" @@ -88,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/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..5fce707f1 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" ) @@ -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 f8fe52244..be9766f00 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/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 86% rename from internal/datasource/host/exec/exec.go rename to pkg/host/exec/exec.go index 8a160e263..c9111d510 100644 --- a/internal/datasource/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/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 70% rename from internal/datasource/host/info.go rename to pkg/host/info.go index bb47c6933..8f220b89d 100644 --- a/internal/datasource/host/info.go +++ b/pkg/host/info.go @@ -9,16 +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" ) @@ -71,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 { @@ -103,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 { @@ -111,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) @@ -234,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 { @@ -270,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/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..e17317e85 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,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()) @@ -555,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",