From e9c3e3ca4b51be7ce6fd1e83d39e2f4d4f24223d Mon Sep 17 00:00:00 2001 From: ryanzhu Date: Thu, 21 May 2026 20:30:22 +0000 Subject: [PATCH 1/3] [gnoi] SetPackage: auto-resolve version from image when not provided When the caller does not provide the version (to_version) field in the SetPackage request, instead of failing with an error, the API now automatically resolves the version by running 'sonic-installer binary_version ' on the installed image. This allows hwproxy and other callers to omit the version field while still getting proper activation (set-default) behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: ryanzhu --- pkg/gnoi/system/system.go | 47 ++++++-- pkg/gnoi/system/system_monkey_test.go | 155 ++++++++++++++++++++++++++ pkg/gnoi/system/system_test.go | 5 +- 3 files changed, 197 insertions(+), 10 deletions(-) diff --git a/pkg/gnoi/system/system.go b/pkg/gnoi/system/system.go index b14764971..80316b6a5 100644 --- a/pkg/gnoi/system/system.go +++ b/pkg/gnoi/system/system.go @@ -86,10 +86,6 @@ func HandleSetPackage(ctx context.Context, req *syspb.SetPackageRequest) (*syspb log.Errorf("Filename is missing in package request") return nil, status.Errorf(codes.InvalidArgument, "filename is missing in package request") } - if pkg.Package.Version == "" { - log.Errorf("Version is missing in package request") - return nil, status.Errorf(codes.InvalidArgument, "version is missing in package request") - } // Reject RemoteDownload - require local image if pkg.Package.RemoteDownload != nil { @@ -117,11 +113,22 @@ func HandleSetPackage(ctx context.Context, req *syspb.SetPackageRequest) (*syspb // If activate is requested, set as next boot image if pkg.Package.Activate { - if err := activatePackage(ctx, pkg.Package.Version); err != nil { - log.Errorf("Failed to activate package %s: %v", pkg.Package.Version, err) + version := pkg.Package.Version + if version == "" { + // Auto-resolve version from the image binary + resolved, err := getBinaryVersion(ctx, pkg.Package.Filename) + if err != nil { + log.Errorf("Failed to resolve version from image %s: %v", pkg.Package.Filename, err) + return nil, status.Errorf(codes.Internal, "failed to resolve version from image: %v", err) + } + version = resolved + log.V(1).Infof("Auto-resolved version from image: %s", version) + } + if err := activatePackage(ctx, version); err != nil { + log.Errorf("Failed to activate package %s: %v", version, err) return nil, status.Errorf(codes.Internal, "failed to activate package: %v", err) } - log.V(1).Infof("Successfully activated package %s", pkg.Package.Version) + log.V(1).Infof("Successfully activated package %s", version) } return &syspb.SetPackageResponse{}, nil @@ -150,6 +157,32 @@ func installPackage(ctx context.Context, filename string) error { return nil } +// getBinaryVersion extracts the version string from a SONiC image using sonic-installer binary_version. +func getBinaryVersion(ctx context.Context, filename string) (string, error) { + log.V(1).Infof("Resolving binary version for: %s", filename) + + opts := &exec.RunHostCommandOptions{ + Timeout: 1 * time.Minute, + } + result, err := exec.RunHostCommand(ctx, "sonic-installer", []string{"binary_version", filename}, opts) + if err != nil { + return "", fmt.Errorf("failed to run sonic-installer binary_version: %v", err) + } + + if result.Error != nil { + return "", fmt.Errorf("sonic-installer binary_version failed with exit code %d: %s", + result.ExitCode, result.Stderr) + } + + version := strings.TrimSpace(result.Stdout) + if version == "" { + return "", fmt.Errorf("sonic-installer binary_version returned empty output for %s", filename) + } + + log.V(1).Infof("Resolved binary version: %s", version) + return version, nil +} + // activatePackage sets a SONiC image as the next boot image using sonic-installer set-default. func activatePackage(ctx context.Context, version string) error { log.V(1).Infof("Activating package version: %s", version) diff --git a/pkg/gnoi/system/system_monkey_test.go b/pkg/gnoi/system/system_monkey_test.go index aa65f32b7..93271901d 100644 --- a/pkg/gnoi/system/system_monkey_test.go +++ b/pkg/gnoi/system/system_monkey_test.go @@ -48,6 +48,161 @@ func TestHandleSetPackage_SuccessWithoutActivation(t *testing.T) { } } +func TestHandleSetPackage_ActivateWithAutoResolvedVersion(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + // Mock exec.RunHostCommand to handle install, binary_version, and set-default + patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { + if cmd == "sonic-installer" { + if len(args) >= 1 && args[0] == "install" { + return &exec.CommandResult{ + Stdout: "Installation completed successfully", + ExitCode: 0, + }, nil + } + if len(args) >= 1 && args[0] == "binary_version" { + return &exec.CommandResult{ + Stdout: "SONiC-OS-4.2.0-Enterprise\n", + ExitCode: 0, + }, nil + } + if len(args) >= 1 && args[0] == "set-default" { + if args[1] != "SONiC-OS-4.2.0-Enterprise" { + return nil, fmt.Errorf("unexpected version: %s", args[1]) + } + return &exec.CommandResult{ + Stdout: "Default image set successfully", + ExitCode: 0, + }, nil + } + } + return nil, fmt.Errorf("unexpected command: %s %v", cmd, args) + }) + + ctx := context.Background() + req := &syspb.SetPackageRequest{ + Request: &syspb.SetPackageRequest_Package{ + Package: &syspb.Package{ + Filename: "/tmp/test-image.bin", + Version: "", // Empty! Should auto-resolve + Activate: true, + }, + }, + } + + resp, err := HandleSetPackage(ctx, req) + if err != nil { + t.Fatalf("HandleSetPackage() returned error: %v", err) + } + if resp == nil { + t.Fatal("HandleSetPackage() returned nil response") + } +} + +func TestHandleSetPackage_AutoResolveVersionFails(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + // Mock: install succeeds, binary_version fails + patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { + if cmd == "sonic-installer" { + if len(args) >= 1 && args[0] == "install" { + return &exec.CommandResult{ + Stdout: "Installation completed successfully", + ExitCode: 0, + }, nil + } + if len(args) >= 1 && args[0] == "binary_version" { + return nil, fmt.Errorf("binary_version command failed") + } + } + return nil, fmt.Errorf("unexpected command: %s %v", cmd, args) + }) + + ctx := context.Background() + req := &syspb.SetPackageRequest{ + Request: &syspb.SetPackageRequest_Package{ + Package: &syspb.Package{ + Filename: "/tmp/test-image.bin", + Version: "", + Activate: true, + }, + }, + } + + _, err := HandleSetPackage(ctx, req) + if err == nil { + t.Fatal("HandleSetPackage() should return error when binary_version fails") + } + if !containsSubstring(err.Error(), "failed to resolve version from image") { + t.Errorf("HandleSetPackage() error = %v, should contain 'failed to resolve version from image'", err) + } +} + +func TestGetBinaryVersion_Success(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { + if cmd == "sonic-installer" && len(args) >= 1 && args[0] == "binary_version" { + return &exec.CommandResult{ + Stdout: "SONiC-OS-4.2.0-Enterprise\n", + ExitCode: 0, + }, nil + } + return nil, fmt.Errorf("unexpected command: %s %v", cmd, args) + }) + + ctx := context.Background() + version, err := getBinaryVersion(ctx, "/tmp/test-image.bin") + if err != nil { + t.Fatalf("getBinaryVersion() returned error: %v", err) + } + if version != "SONiC-OS-4.2.0-Enterprise" { + t.Errorf("getBinaryVersion() = %q, want %q", version, "SONiC-OS-4.2.0-Enterprise") + } +} + +func TestGetBinaryVersion_EmptyOutput(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { + return &exec.CommandResult{ + Stdout: "", + ExitCode: 0, + }, nil + }) + + ctx := context.Background() + _, err := getBinaryVersion(ctx, "/tmp/test-image.bin") + if err == nil { + t.Fatal("getBinaryVersion() should return error for empty output") + } + if !containsSubstring(err.Error(), "returned empty output") { + t.Errorf("getBinaryVersion() error = %v, should contain 'returned empty output'", err) + } +} + +func TestGetBinaryVersion_CommandError(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { + return nil, fmt.Errorf("command not found") + }) + + ctx := context.Background() + _, err := getBinaryVersion(ctx, "/tmp/test-image.bin") + if err == nil { + t.Fatal("getBinaryVersion() should return error when command fails") + } + if !containsSubstring(err.Error(), "failed to run sonic-installer binary_version") { + t.Errorf("getBinaryVersion() error = %v, should contain 'failed to run sonic-installer binary_version'", err) + } +} + func TestHandleSetPackage_SuccessWithActivation(t *testing.T) { patches := gomonkey.NewPatches() defer patches.Reset() diff --git a/pkg/gnoi/system/system_test.go b/pkg/gnoi/system/system_test.go index 2006e3d13..e53644d80 100644 --- a/pkg/gnoi/system/system_test.go +++ b/pkg/gnoi/system/system_test.go @@ -41,7 +41,7 @@ func TestHandleSetPackageValidation(t *testing.T) { errMsg: "filename is missing", }, { - name: "missing version", + name: "missing version - no longer fails validation, auto-resolved from image", req: &syspb.SetPackageRequest{ Request: &syspb.SetPackageRequest_Package{ Package: &syspb.Package{ @@ -50,8 +50,7 @@ func TestHandleSetPackageValidation(t *testing.T) { }, }, }, - wantErr: true, - errMsg: "version is missing", + wantErr: false, // Version is now auto-resolved; validation no longer rejects this }, { name: "remote download not supported", From d2e9d066acc334f21a440c7f1c011e752a63ef91 Mon Sep 17 00:00:00 2001 From: ryanzhu Date: Thu, 21 May 2026 21:16:13 +0000 Subject: [PATCH 2/3] [gnoi] SetPackage: resolve version before install, fix test coverage Restructure HandleSetPackage to validate and resolve the version from the image binary BEFORE performing install. This ensures we fail early without side effects if version resolution fails. Order is now: validate inputs -> resolve version -> install -> activate Also addresses PR review comments: - Fix args bounds check (len >= 2) before accessing args[1] - Add test for Version='' + Activate=false (install-only) - Update test names to accurately reflect scenarios Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: ryanzhu --- pkg/gnoi/system/system.go | 31 ++++++------ pkg/gnoi/system/system_monkey_test.go | 70 +++++++++++++++++++++------ pkg/gnoi/system/system_test.go | 4 +- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/pkg/gnoi/system/system.go b/pkg/gnoi/system/system.go index 80316b6a5..088beb252 100644 --- a/pkg/gnoi/system/system.go +++ b/pkg/gnoi/system/system.go @@ -93,16 +93,28 @@ func HandleSetPackage(ctx context.Context, req *syspb.SetPackageRequest) (*syspb return nil, status.Errorf(codes.InvalidArgument, "remote download is not supported, image must be local") } - // Log the package details - log.V(1).Infof("Installing package: filename=%s, version=%s, activate=%v", - pkg.Package.Filename, pkg.Package.Version, pkg.Package.Activate) - // Validate filename is absolute path if !filepath.IsAbs(pkg.Package.Filename) { log.Errorf("Filename must be an absolute path: %s", pkg.Package.Filename) return nil, status.Errorf(codes.InvalidArgument, "filename must be an absolute path") } + // Resolve version: use provided version, or auto-resolve from image binary + version := pkg.Package.Version + if version == "" { + resolved, err := getBinaryVersion(ctx, pkg.Package.Filename) + if err != nil { + log.Errorf("Failed to resolve version from image %s: %v", pkg.Package.Filename, err) + return nil, status.Errorf(codes.Internal, "failed to resolve version from image: %v", err) + } + version = resolved + log.V(1).Infof("Auto-resolved version from image: %s", version) + } + + // Log the package details + log.V(1).Infof("Installing package: filename=%s, version=%s, activate=%v", + pkg.Package.Filename, version, pkg.Package.Activate) + // Install the package using sonic-installer if err := installPackage(ctx, pkg.Package.Filename); err != nil { log.Errorf("Failed to install package %s: %v", pkg.Package.Filename, err) @@ -113,17 +125,6 @@ func HandleSetPackage(ctx context.Context, req *syspb.SetPackageRequest) (*syspb // If activate is requested, set as next boot image if pkg.Package.Activate { - version := pkg.Package.Version - if version == "" { - // Auto-resolve version from the image binary - resolved, err := getBinaryVersion(ctx, pkg.Package.Filename) - if err != nil { - log.Errorf("Failed to resolve version from image %s: %v", pkg.Package.Filename, err) - return nil, status.Errorf(codes.Internal, "failed to resolve version from image: %v", err) - } - version = resolved - log.V(1).Infof("Auto-resolved version from image: %s", version) - } if err := activatePackage(ctx, version); err != nil { log.Errorf("Failed to activate package %s: %v", version, err) return nil, status.Errorf(codes.Internal, "failed to activate package: %v", err) diff --git a/pkg/gnoi/system/system_monkey_test.go b/pkg/gnoi/system/system_monkey_test.go index 93271901d..5abb4b86f 100644 --- a/pkg/gnoi/system/system_monkey_test.go +++ b/pkg/gnoi/system/system_monkey_test.go @@ -52,22 +52,22 @@ func TestHandleSetPackage_ActivateWithAutoResolvedVersion(t *testing.T) { patches := gomonkey.NewPatches() defer patches.Reset() - // Mock exec.RunHostCommand to handle install, binary_version, and set-default + // Mock exec.RunHostCommand to handle binary_version, install, and set-default patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { if cmd == "sonic-installer" { - if len(args) >= 1 && args[0] == "install" { + if len(args) >= 1 && args[0] == "binary_version" { return &exec.CommandResult{ - Stdout: "Installation completed successfully", + Stdout: "SONiC-OS-4.2.0-Enterprise\n", ExitCode: 0, }, nil } - if len(args) >= 1 && args[0] == "binary_version" { + if len(args) >= 1 && args[0] == "install" { return &exec.CommandResult{ - Stdout: "SONiC-OS-4.2.0-Enterprise\n", + Stdout: "Installation completed successfully", ExitCode: 0, }, nil } - if len(args) >= 1 && args[0] == "set-default" { + if len(args) >= 2 && args[0] == "set-default" { if args[1] != "SONiC-OS-4.2.0-Enterprise" { return nil, fmt.Errorf("unexpected version: %s", args[1]) } @@ -85,7 +85,7 @@ func TestHandleSetPackage_ActivateWithAutoResolvedVersion(t *testing.T) { Request: &syspb.SetPackageRequest_Package{ Package: &syspb.Package{ Filename: "/tmp/test-image.bin", - Version: "", // Empty! Should auto-resolve + Version: "", // Empty! Should auto-resolve before install Activate: true, }, }, @@ -104,17 +104,55 @@ func TestHandleSetPackage_AutoResolveVersionFails(t *testing.T) { patches := gomonkey.NewPatches() defer patches.Reset() - // Mock: install succeeds, binary_version fails + // Mock: binary_version fails — should reject before install + patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { + if cmd == "sonic-installer" && len(args) >= 1 && args[0] == "binary_version" { + return nil, fmt.Errorf("binary_version command failed") + } + return nil, fmt.Errorf("unexpected command (install should not be called): %s %v", cmd, args) + }) + + ctx := context.Background() + req := &syspb.SetPackageRequest{ + Request: &syspb.SetPackageRequest_Package{ + Package: &syspb.Package{ + Filename: "/tmp/test-image.bin", + Version: "", + Activate: true, + }, + }, + } + + _, err := HandleSetPackage(ctx, req) + if err == nil { + t.Fatal("HandleSetPackage() should return error when binary_version fails") + } + if !containsSubstring(err.Error(), "failed to resolve version from image") { + t.Errorf("HandleSetPackage() error = %v, should contain 'failed to resolve version from image'", err) + } +} + +func TestHandleSetPackage_EmptyVersionNoActivate(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + // Mock: binary_version and install succeed, set-default should NOT be called patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { if cmd == "sonic-installer" { + if len(args) >= 1 && args[0] == "binary_version" { + return &exec.CommandResult{ + Stdout: "SONiC-OS-4.2.0-Enterprise\n", + ExitCode: 0, + }, nil + } if len(args) >= 1 && args[0] == "install" { return &exec.CommandResult{ Stdout: "Installation completed successfully", ExitCode: 0, }, nil } - if len(args) >= 1 && args[0] == "binary_version" { - return nil, fmt.Errorf("binary_version command failed") + if len(args) >= 1 && args[0] == "set-default" { + return nil, fmt.Errorf("set-default should not be called when activate=false") } } return nil, fmt.Errorf("unexpected command: %s %v", cmd, args) @@ -126,17 +164,17 @@ func TestHandleSetPackage_AutoResolveVersionFails(t *testing.T) { Package: &syspb.Package{ Filename: "/tmp/test-image.bin", Version: "", - Activate: true, + Activate: false, }, }, } - _, err := HandleSetPackage(ctx, req) - if err == nil { - t.Fatal("HandleSetPackage() should return error when binary_version fails") + resp, err := HandleSetPackage(ctx, req) + if err != nil { + t.Fatalf("HandleSetPackage() returned error: %v", err) } - if !containsSubstring(err.Error(), "failed to resolve version from image") { - t.Errorf("HandleSetPackage() error = %v, should contain 'failed to resolve version from image'", err) + if resp == nil { + t.Fatal("HandleSetPackage() returned nil response") } } diff --git a/pkg/gnoi/system/system_test.go b/pkg/gnoi/system/system_test.go index e53644d80..22723dca0 100644 --- a/pkg/gnoi/system/system_test.go +++ b/pkg/gnoi/system/system_test.go @@ -41,7 +41,7 @@ func TestHandleSetPackageValidation(t *testing.T) { errMsg: "filename is missing", }, { - name: "missing version - no longer fails validation, auto-resolved from image", + name: "empty version - resolved from image before install", req: &syspb.SetPackageRequest{ Request: &syspb.SetPackageRequest_Package{ Package: &syspb.Package{ @@ -50,7 +50,7 @@ func TestHandleSetPackageValidation(t *testing.T) { }, }, }, - wantErr: false, // Version is now auto-resolved; validation no longer rejects this + wantErr: false, // Version is auto-resolved from image; no longer rejected }, { name: "remote download not supported", From 0842312bf7e6d3ca2c12d9f03f94bb37af780ecf Mon Sep 17 00:00:00 2001 From: "Chandra Shekar (WIPRO LIMITED)" Date: Thu, 28 May 2026 13:07:34 -0700 Subject: [PATCH 3/3] [gnoi] SetPackage: use absolute path for sonic-installer nsenter does not inherit the host PATH when executing commands from within the container. Use /usr/local/bin/sonic-installer as the full path to ensure the command is found. Also use 'binary-version' subcommand instead of deprecated 'binary_version' which fails on newer sonic-installer versions. Signed-off-by: Chandra Shekar (WIPRO LIMITED) --- pkg/gnoi/system/system.go | 14 ++++++------ pkg/gnoi/system/system_monkey_test.go | 32 +++++++++++++-------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pkg/gnoi/system/system.go b/pkg/gnoi/system/system.go index 088beb252..7e7d6cc16 100644 --- a/pkg/gnoi/system/system.go +++ b/pkg/gnoi/system/system.go @@ -144,7 +144,7 @@ func installPackage(ctx context.Context, filename string) error { opts := &exec.RunHostCommandOptions{ Timeout: 10 * time.Minute, // Allow up to 10 minutes for installation } - result, err := exec.RunHostCommand(ctx, "sonic-installer", []string{"install", "-y", filename}, opts) + result, err := exec.RunHostCommand(ctx, "/usr/local/bin/sonic-installer", []string{"install", "-y", filename}, opts) if err != nil { return fmt.Errorf("failed to run sonic-installer install: %v", err) } @@ -158,26 +158,26 @@ func installPackage(ctx context.Context, filename string) error { return nil } -// getBinaryVersion extracts the version string from a SONiC image using sonic-installer binary_version. +// getBinaryVersion extracts the version string from a SONiC image using sonic-installer binary-version. func getBinaryVersion(ctx context.Context, filename string) (string, error) { log.V(1).Infof("Resolving binary version for: %s", filename) opts := &exec.RunHostCommandOptions{ Timeout: 1 * time.Minute, } - result, err := exec.RunHostCommand(ctx, "sonic-installer", []string{"binary_version", filename}, opts) + result, err := exec.RunHostCommand(ctx, "/usr/local/bin/sonic-installer", []string{"binary-version", filename}, opts) if err != nil { - return "", fmt.Errorf("failed to run sonic-installer binary_version: %v", err) + return "", fmt.Errorf("failed to run sonic-installer binary-version: %v", err) } if result.Error != nil { - return "", fmt.Errorf("sonic-installer binary_version failed with exit code %d: %s", + return "", fmt.Errorf("sonic-installer binary-version failed with exit code %d: %s", result.ExitCode, result.Stderr) } version := strings.TrimSpace(result.Stdout) if version == "" { - return "", fmt.Errorf("sonic-installer binary_version returned empty output for %s", filename) + return "", fmt.Errorf("sonic-installer binary-version returned empty output for %s", filename) } log.V(1).Infof("Resolved binary version: %s", version) @@ -193,7 +193,7 @@ func activatePackage(ctx context.Context, version string) error { opts := &exec.RunHostCommandOptions{ Timeout: 2 * time.Minute, // Allow up to 2 minutes for setting default } - result, err := exec.RunHostCommand(ctx, "sonic-installer", []string{"set-default", version}, opts) + result, err := exec.RunHostCommand(ctx, "/usr/local/bin/sonic-installer", []string{"set-default", version}, opts) if err != nil { return fmt.Errorf("failed to run sonic-installer set-default: %v", err) } diff --git a/pkg/gnoi/system/system_monkey_test.go b/pkg/gnoi/system/system_monkey_test.go index 5abb4b86f..033dddf1a 100644 --- a/pkg/gnoi/system/system_monkey_test.go +++ b/pkg/gnoi/system/system_monkey_test.go @@ -16,7 +16,7 @@ func TestHandleSetPackage_SuccessWithoutActivation(t *testing.T) { // Mock exec.RunHostCommand to return successful installation patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { - if cmd == "sonic-installer" && len(args) >= 1 && args[0] == "install" { + if cmd == "/usr/local/bin/sonic-installer" && len(args) >= 1 && args[0] == "install" { return &exec.CommandResult{ Stdout: "Installation completed successfully", Stderr: "", @@ -52,10 +52,10 @@ func TestHandleSetPackage_ActivateWithAutoResolvedVersion(t *testing.T) { patches := gomonkey.NewPatches() defer patches.Reset() - // Mock exec.RunHostCommand to handle binary_version, install, and set-default + // Mock exec.RunHostCommand to handle binary-version, install, and set-default patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { - if cmd == "sonic-installer" { - if len(args) >= 1 && args[0] == "binary_version" { + if cmd == "/usr/local/bin/sonic-installer" { + if len(args) >= 1 && args[0] == "binary-version" { return &exec.CommandResult{ Stdout: "SONiC-OS-4.2.0-Enterprise\n", ExitCode: 0, @@ -104,10 +104,10 @@ func TestHandleSetPackage_AutoResolveVersionFails(t *testing.T) { patches := gomonkey.NewPatches() defer patches.Reset() - // Mock: binary_version fails — should reject before install + // Mock: binary-version fails — should reject before install patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { - if cmd == "sonic-installer" && len(args) >= 1 && args[0] == "binary_version" { - return nil, fmt.Errorf("binary_version command failed") + if cmd == "/usr/local/bin/sonic-installer" && len(args) >= 1 && args[0] == "binary-version" { + return nil, fmt.Errorf("binary-version command failed") } return nil, fmt.Errorf("unexpected command (install should not be called): %s %v", cmd, args) }) @@ -125,7 +125,7 @@ func TestHandleSetPackage_AutoResolveVersionFails(t *testing.T) { _, err := HandleSetPackage(ctx, req) if err == nil { - t.Fatal("HandleSetPackage() should return error when binary_version fails") + t.Fatal("HandleSetPackage() should return error when binary-version fails") } if !containsSubstring(err.Error(), "failed to resolve version from image") { t.Errorf("HandleSetPackage() error = %v, should contain 'failed to resolve version from image'", err) @@ -136,10 +136,10 @@ func TestHandleSetPackage_EmptyVersionNoActivate(t *testing.T) { patches := gomonkey.NewPatches() defer patches.Reset() - // Mock: binary_version and install succeed, set-default should NOT be called + // Mock: binary-version and install succeed, set-default should NOT be called patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { - if cmd == "sonic-installer" { - if len(args) >= 1 && args[0] == "binary_version" { + if cmd == "/usr/local/bin/sonic-installer" { + if len(args) >= 1 && args[0] == "binary-version" { return &exec.CommandResult{ Stdout: "SONiC-OS-4.2.0-Enterprise\n", ExitCode: 0, @@ -183,7 +183,7 @@ func TestGetBinaryVersion_Success(t *testing.T) { defer patches.Reset() patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { - if cmd == "sonic-installer" && len(args) >= 1 && args[0] == "binary_version" { + if cmd == "/usr/local/bin/sonic-installer" && len(args) >= 1 && args[0] == "binary-version" { return &exec.CommandResult{ Stdout: "SONiC-OS-4.2.0-Enterprise\n", ExitCode: 0, @@ -236,8 +236,8 @@ func TestGetBinaryVersion_CommandError(t *testing.T) { if err == nil { t.Fatal("getBinaryVersion() should return error when command fails") } - if !containsSubstring(err.Error(), "failed to run sonic-installer binary_version") { - t.Errorf("getBinaryVersion() error = %v, should contain 'failed to run sonic-installer binary_version'", err) + if !containsSubstring(err.Error(), "failed to run sonic-installer binary-version") { + t.Errorf("getBinaryVersion() error = %v, should contain 'failed to run sonic-installer binary-version'", err) } } @@ -247,7 +247,7 @@ func TestHandleSetPackage_SuccessWithActivation(t *testing.T) { // Mock exec.RunHostCommand to handle both install and set-default commands patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { - if cmd == "sonic-installer" { + if cmd == "/usr/local/bin/sonic-installer" { if len(args) >= 1 && args[0] == "install" { return &exec.CommandResult{ Stdout: "Installation completed successfully", @@ -325,7 +325,7 @@ func TestHandleSetPackage_ActivateCommandError(t *testing.T) { // Mock exec.RunHostCommand: install succeeds, set-default fails patches.ApplyFunc(exec.RunHostCommand, func(ctx context.Context, cmd string, args []string, opts *exec.RunHostCommandOptions) (*exec.CommandResult, error) { - if cmd == "sonic-installer" { + if cmd == "/usr/local/bin/sonic-installer" { if len(args) >= 1 && args[0] == "install" { return &exec.CommandResult{ Stdout: "Installation completed successfully",