From 698f9fa15232d888b647cd626700617cf98d3ab9 Mon Sep 17 00:00:00 2001 From: Federico Bozzini Date: Fri, 8 May 2026 16:31:33 +0100 Subject: [PATCH 1/4] fix: topo install command needs to include target Signed-off-by: Federico Bozzini --- internal/health/dependencies.go | 86 +++++++++++++++------------- internal/health/dependencies_test.go | 27 ++++++++- internal/health/health.go | 2 +- internal/health/status.go | 5 +- 4 files changed, 73 insertions(+), 47 deletions(-) diff --git a/internal/health/dependencies.go b/internal/health/dependencies.go index 883b8137..bc9319d8 100644 --- a/internal/health/dependencies.go +++ b/internal/health/dependencies.go @@ -2,8 +2,10 @@ package health import ( "context" + "fmt" "github.com/arm/topo/internal/runner" + "github.com/arm/topo/internal/ssh" "github.com/arm/topo/internal/version" ) @@ -83,55 +85,57 @@ var HostRequiredDependencies = []Dependency{ }, } -var TargetRequiredDependencies = []Dependency{ - { - Binary: "docker", - Label: "Container Engine", - SoftwareEnumID: Docker, - Checks: []Check{ - BinaryExists{}, - CommandSuccessful{ - Cmd: "docker info", - Fix: &Fix{Description: "Ensure current user can run docker commands"}, +func TargetRequiredDependencies(target ssh.Destination) []Dependency { + return []Dependency{ + { + Binary: "docker", + Label: "Container Engine", + SoftwareEnumID: Docker, + Checks: []Check{ + BinaryExists{}, + CommandSuccessful{ + Cmd: "docker info", + Fix: &Fix{Description: "Ensure current user can run docker commands"}, + }, }, }, - }, - { - Binary: "remoteproc-runtime", - Label: "Remoteproc Runtime", - SoftwarePrerequisites: []SoftwareDependency{Docker}, - HardwarePrerequisite: []HardwareCapability{Remoteproc}, - Checks: []Check{ - BinaryExists{ - Severity: SeverityWarning, - Fix: &Fix{ - Description: "Install the remoteproc runtime", - Command: "topo install remoteproc-runtime", + { + Binary: "remoteproc-runtime", + Label: "Remoteproc Runtime", + SoftwarePrerequisites: []SoftwareDependency{Docker}, + HardwarePrerequisite: []HardwareCapability{Remoteproc}, + Checks: []Check{ + BinaryExists{ + Severity: SeverityWarning, + Fix: &Fix{ + Description: "Install the remoteproc runtime", + Command: fmt.Sprintf("topo install remoteproc-runtime --target %s", target), + }, }, }, }, - }, - { - Binary: "containerd-shim-remoteproc-v1", - Label: "Remoteproc Shim", - SoftwarePrerequisites: []SoftwareDependency{Docker}, - HardwarePrerequisite: []HardwareCapability{Remoteproc}, - Checks: []Check{ - BinaryExists{ - Severity: SeverityWarning, - Fix: &Fix{ - Description: "Install the remoteproc runtime", - Command: "topo install remoteproc-runtime", + { + Binary: "containerd-shim-remoteproc-v1", + Label: "Remoteproc Shim", + SoftwarePrerequisites: []SoftwareDependency{Docker}, + HardwarePrerequisite: []HardwareCapability{Remoteproc}, + Checks: []Check{ + BinaryExists{ + Severity: SeverityWarning, + Fix: &Fix{ + Description: "Install the remoteproc runtime", + Command: fmt.Sprintf("topo install remoteproc-runtime --target %s", target), + }, }, }, }, - }, - { - Binary: "lscpu", - Label: "Hardware Info", - SoftwareEnumID: Lscpu, - Checks: []Check{BinaryExists{}}, - }, + { + Binary: "lscpu", + Label: "Hardware Info", + SoftwareEnumID: Lscpu, + Checks: []Check{BinaryExists{}}, + }, + } } type DependencyStatus struct { diff --git a/internal/health/dependencies_test.go b/internal/health/dependencies_test.go index 8b8f6255..aa76cfe0 100644 --- a/internal/health/dependencies_test.go +++ b/internal/health/dependencies_test.go @@ -8,6 +8,7 @@ import ( "github.com/arm/topo/internal/command" "github.com/arm/topo/internal/health" "github.com/arm/topo/internal/runner" + "github.com/arm/topo/internal/ssh" "github.com/stretchr/testify/assert" ) @@ -19,7 +20,7 @@ func TestDependencyFormat(t *testing.T) { }) t.Run("target dependencies are of the correct format", func(t *testing.T) { - for _, dep := range health.TargetRequiredDependencies { + for _, dep := range health.TargetRequiredDependencies(ssh.NewDestination("user@my-target")) { assert.NoError(t, command.ValidateBinaryName(dep.Binary)) } }) @@ -29,7 +30,7 @@ func TestDependencyFormat(t *testing.T) { seenEnums := make(map[health.SoftwareDependency]string) t.Run("There are no duplicate SoftwareEnumID assignments", func(t *testing.T) { - for _, dep := range health.TargetRequiredDependencies { + for _, dep := range health.TargetRequiredDependencies(ssh.NewDestination("user@my-target")) { if dep.SoftwareEnumID != health.UnsetSoftwareDependency { if existingDep, exists := seenEnums[dep.SoftwareEnumID]; exists { t.Errorf("Duplicate SoftwareEnumID %d assigned to both %q and %q", dep.SoftwareEnumID, existingDep, dep.Binary) @@ -41,7 +42,7 @@ func TestDependencyFormat(t *testing.T) { }) t.Run("all SoftwarePrerequisites reference valid SoftwareEnumID", func(t *testing.T) { - for _, dep := range health.TargetRequiredDependencies { + for _, dep := range health.TargetRequiredDependencies(ssh.NewDestination("user@my-target")) { for _, prereq := range dep.SoftwarePrerequisites { assert.True(t, availableEnums[prereq], "%q has SoftwarePrerequisites %v which is not provided by any dependency's SoftwareEnumID", dep.Binary, prereq) } @@ -50,6 +51,26 @@ func TestDependencyFormat(t *testing.T) { }) } +func TestTargetRequiredDependencies(t *testing.T) { + t.Run("remoteproc install fix command includes the target", func(t *testing.T) { + deps := health.TargetRequiredDependencies(ssh.NewDestination("user@my-target")) + + var got []string + for _, dep := range deps { + if dep.Binary == "remoteproc-runtime" || dep.Binary == "containerd-shim-remoteproc-v1" { + binaryExists, ok := dep.Checks[0].(health.BinaryExists) + assert.True(t, ok) + got = append(got, binaryExists.Fix.Command) + } + } + + assert.Equal(t, []string{ + "topo install remoteproc-runtime --target ssh://user@my-target", + "topo install remoteproc-runtime --target ssh://user@my-target", + }, got) + }) +} + func TestPerformChecks(t *testing.T) { t.Run("when no dependencies are found, statuses show not installed", func(t *testing.T) { fooDependency := health.Dependency{Binary: "foo", Label: "bar", Checks: []health.Check{health.BinaryExists{}}} diff --git a/internal/health/health.go b/internal/health/health.go index fce79a0a..e9cf759c 100644 --- a/internal/health/health.go +++ b/internal/health/health.go @@ -96,7 +96,7 @@ func CheckTarget(ctx context.Context, dest ssh.Destination, acceptNewHostKeys bo r, connErr := prepareRunner(ctx, dest, acceptNewHostKeys) status := Status{Connection: ConnectionStatus{Destination: dest, Error: connErr}} if connErr == nil { - hs := ProbeHealthStatus(ctx, r) + hs := ProbeHealthStatus(ctx, r, dest) status.Dependencies = hs.Dependencies status.Hardware = hs.Hardware } diff --git a/internal/health/status.go b/internal/health/status.go index 5e25f9b4..0ebc07e5 100644 --- a/internal/health/status.go +++ b/internal/health/status.go @@ -5,6 +5,7 @@ import ( "github.com/arm/topo/internal/probe" "github.com/arm/topo/internal/runner" + "github.com/arm/topo/internal/ssh" ) type HardwareProfile struct { @@ -25,14 +26,14 @@ type HealthStatus struct { Hardware HardwareProfile } -func ProbeHealthStatus(ctx context.Context, r runner.Runner) HealthStatus { +func ProbeHealthStatus(ctx context.Context, r runner.Runner, target ssh.Destination) HealthStatus { var hs HealthStatus remoteProcessors, err := probe.Remoteproc(ctx, r) hs.Hardware.RemoteProcessors = remoteProcessors hs.Hardware.Err = err - dependenciesToCheck := FilterByHardware(TargetRequiredDependencies, hs.Hardware.Capabilities()) + dependenciesToCheck := FilterByHardware(TargetRequiredDependencies(target), hs.Hardware.Capabilities()) hs.Dependencies = PerformChecks(ctx, dependenciesToCheck, r) return hs From 75dabca6f00f00fb82cd168611d48b9cd4286098 Mon Sep 17 00:00:00 2001 From: Federico Bozzini Date: Fri, 8 May 2026 16:39:15 +0100 Subject: [PATCH 2/4] Test refactor Signed-off-by: Federico Bozzini --- internal/health/dependencies_test.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/health/dependencies_test.go b/internal/health/dependencies_test.go index aa76cfe0..3f381ecb 100644 --- a/internal/health/dependencies_test.go +++ b/internal/health/dependencies_test.go @@ -55,19 +55,16 @@ func TestTargetRequiredDependencies(t *testing.T) { t.Run("remoteproc install fix command includes the target", func(t *testing.T) { deps := health.TargetRequiredDependencies(ssh.NewDestination("user@my-target")) - var got []string + var got string for _, dep := range deps { - if dep.Binary == "remoteproc-runtime" || dep.Binary == "containerd-shim-remoteproc-v1" { + if dep.Binary == "remoteproc-runtime" { binaryExists, ok := dep.Checks[0].(health.BinaryExists) assert.True(t, ok) - got = append(got, binaryExists.Fix.Command) + got = binaryExists.Fix.Command } } - assert.Equal(t, []string{ - "topo install remoteproc-runtime --target ssh://user@my-target", - "topo install remoteproc-runtime --target ssh://user@my-target", - }, got) + assert.Equal(t, "topo install remoteproc-runtime --target ssh://user@my-target", got) }) } From dd9318a97848f0358ecc456e12aac6fade5f8d5a Mon Sep 17 00:00:00 2001 From: Federico Bozzini Date: Mon, 18 May 2026 11:10:44 +0100 Subject: [PATCH 3/4] Bartek's feedback Signed-off-by: Federico Bozzini --- internal/health/dependencies_test.go | 32 +++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/internal/health/dependencies_test.go b/internal/health/dependencies_test.go index 3f381ecb..21ca69c4 100644 --- a/internal/health/dependencies_test.go +++ b/internal/health/dependencies_test.go @@ -12,6 +12,18 @@ import ( "github.com/stretchr/testify/assert" ) +func findDependencyByBinary(t *testing.T, deps []health.Dependency, binary string) (health.Dependency, error) { + t.Helper() + + for _, dep := range deps { + if dep.Binary == binary { + return dep, nil + } + } + + return health.Dependency{}, errors.New("dependency not found") +} + func TestDependencyFormat(t *testing.T) { t.Run("host dependencies are of the correct format", func(t *testing.T) { for _, dep := range health.HostRequiredDependencies { @@ -20,7 +32,7 @@ func TestDependencyFormat(t *testing.T) { }) t.Run("target dependencies are of the correct format", func(t *testing.T) { - for _, dep := range health.TargetRequiredDependencies(ssh.NewDestination("user@my-target")) { + for _, dep := range health.TargetRequiredDependencies(ssh.NewDestination("does-not-matter-for-this-test")) { assert.NoError(t, command.ValidateBinaryName(dep.Binary)) } }) @@ -55,16 +67,16 @@ func TestTargetRequiredDependencies(t *testing.T) { t.Run("remoteproc install fix command includes the target", func(t *testing.T) { deps := health.TargetRequiredDependencies(ssh.NewDestination("user@my-target")) - var got string - for _, dep := range deps { - if dep.Binary == "remoteproc-runtime" { - binaryExists, ok := dep.Checks[0].(health.BinaryExists) - assert.True(t, ok) - got = binaryExists.Fix.Command - } + dep, err := findDependencyByBinary(t, deps, "remoteproc-runtime") + assert.NoError(t, err) + wantBinaryExistsCheck := health.BinaryExists{ + Severity: health.SeverityWarning, + Fix: &health.Fix{ + Description: "Install the remoteproc runtime", + Command: "topo install remoteproc-runtime --target ssh://user@my-target", + }, } - - assert.Equal(t, "topo install remoteproc-runtime --target ssh://user@my-target", got) + assert.Contains(t, dep.Checks, wantBinaryExistsCheck) }) } From b842e1012e9caf6d6b8a7ac75c717d0d35d0833f Mon Sep 17 00:00:00 2001 From: Federico Bozzini Date: Mon, 18 May 2026 12:05:59 +0100 Subject: [PATCH 4/4] Test helper moved to the bottom of the file Signed-off-by: Federico Bozzini --- internal/health/dependencies_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/health/dependencies_test.go b/internal/health/dependencies_test.go index 21ca69c4..75dd1677 100644 --- a/internal/health/dependencies_test.go +++ b/internal/health/dependencies_test.go @@ -12,18 +12,6 @@ import ( "github.com/stretchr/testify/assert" ) -func findDependencyByBinary(t *testing.T, deps []health.Dependency, binary string) (health.Dependency, error) { - t.Helper() - - for _, dep := range deps { - if dep.Binary == binary { - return dep, nil - } - } - - return health.Dependency{}, errors.New("dependency not found") -} - func TestDependencyFormat(t *testing.T) { t.Run("host dependencies are of the correct format", func(t *testing.T) { for _, dep := range health.HostRequiredDependencies { @@ -341,3 +329,15 @@ func TestFilterByHardware(t *testing.T) { assert.Equal(t, want, got) }) } + +func findDependencyByBinary(t *testing.T, deps []health.Dependency, binary string) (health.Dependency, error) { + t.Helper() + + for _, dep := range deps { + if dep.Binary == binary { + return dep, nil + } + } + + return health.Dependency{}, errors.New("dependency not found") +}