feat: implement sudo fallback for pkg-helper

This commit is contained in:
Keith Patrick committed 2026-06-11 13:48:36 +01:00
1 parent 37e2d37cf4
commit d04108fff9
2 files changed
+61 -10

No files matched your search

+17 -10
View File
@@ -222,14 +222,25 @@ func handleRequest(req request) response {
}
}
var runApkFunc = runApk
func runApk(args ...string) ([]byte, error) {
if os.Getuid() != 0 {
if sudo, err := exec.LookPath("sudo"); err == nil {
sudoArgs := append([]string{"apk"}, args...)
return exec.Command(sudo, sudoArgs...).CombinedOutput()
}
}
return exec.Command("apk", args...).CombinedOutput()
}
func doInstall(pkg string) response {
apkMutex.Lock()
defer apkMutex.Unlock()
slog.Info("pkg-helper: installing", "package", pkg)
cmd := exec.Command("apk", "add", "--no-cache", pkg)
out, err := cmd.CombinedOutput()
out, err := runApkFunc("add", "--no-cache", pkg)
if err != nil {
msg, code := classifyApkOutput(string(out), err)
slog.Error("pkg-helper: install failed", "package", pkg, "error", msg, "code", code)
@@ -247,8 +258,7 @@ func doUninstall(pkg string) response {
slog.Info("pkg-helper: uninstalling", "package", pkg)
cmd := exec.Command("apk", "del", pkg)
out, err := cmd.CombinedOutput()
out, err := runApkFunc("del", pkg)
if err != nil {
msg, code := classifyApkOutput(string(out), err)
slog.Error("pkg-helper: uninstall failed", "package", pkg, "error", msg, "code", code)
@@ -269,8 +279,7 @@ func doUpgrade(pkg string) response {
slog.Info("pkg-helper: upgrading", "package", pkg)
cmd := exec.Command("apk", "add", "-u", pkg)
out, err := cmd.CombinedOutput()
out, err := runApkFunc("add", "-u", pkg)
if err != nil {
msg, code := classifyApkOutput(string(out), err)
slog.Error("pkg-helper: upgrade failed", "package", pkg, "error", msg, "code", code)
@@ -288,8 +297,7 @@ func doUpdateIndex() response {
slog.Info("pkg-helper: updating index")
cmd := exec.Command("apk", "update")
out, err := cmd.CombinedOutput()
out, err := runApkFunc("update")
if err != nil {
msg, code := classifyApkOutput(string(out), err)
slog.Warn("pkg-helper: update-index failed", "error", msg, "code", code)
@@ -306,8 +314,7 @@ func doListOutdated() response {
apkMutex.Lock()
defer apkMutex.Unlock()
cmd := exec.Command("apk", "version", "-l", "<")
out, err := cmd.CombinedOutput()
out, err := runApkFunc("version", "-l", "<")
if err != nil {
msg, code := classifyApkOutput(string(out), err)
return response{Error: msg, Code: code}
+44
View File
@@ -12,6 +12,13 @@ import (
// Note: Command execution tests are not included here since apk is not available
// in unit test environments. Integration tests would handle actual execution.
func TestHandleRequest(t *testing.T) {
// Mock runApkFunc to avoid running actual commands or triggering sudo
origRunApkFunc := runApkFunc
runApkFunc = func(args ...string) ([]byte, error) {
return []byte("mock output"), nil
}
defer func() { runApkFunc = origRunApkFunc }()
tests := []struct {
name string
req request
@@ -154,6 +161,13 @@ func TestValidPkgName(t *testing.T) {
// TestHandleRequest_AllActionsValidated tests both install and uninstall actions.
func TestHandleRequest_AllActionsValidated(t *testing.T) {
// Mock runApkFunc to avoid running actual commands or triggering sudo
origRunApkFunc := runApkFunc
runApkFunc = func(args ...string) ([]byte, error) {
return []byte("mock output"), nil
}
defer func() { runApkFunc = origRunApkFunc }()
tests := []struct {
action string
}{
@@ -365,6 +379,12 @@ func TestValidPkgNameRegex_Compliance(t *testing.T) {
// TestHandleRequest_ErrorMessages tests that error messages are clear.
func TestHandleRequest_ErrorMessages(t *testing.T) {
origRunApkFunc := runApkFunc
runApkFunc = func(args ...string) ([]byte, error) {
return []byte("mock output"), nil
}
defer func() { runApkFunc = origRunApkFunc }()
tests := []struct {
name string
req request
@@ -404,6 +424,12 @@ func TestHandleRequest_ErrorMessages(t *testing.T) {
// Note: Actual apk command execution will fail in test environment (no apk available),
// but validation should pass.
func TestHandleRequest_SuccessPath(t *testing.T) {
origRunApkFunc := runApkFunc
runApkFunc = func(args ...string) ([]byte, error) {
return []byte("mock output"), nil
}
defer func() { runApkFunc = origRunApkFunc }()
tests := []struct {
action string
pkg string
@@ -438,6 +464,12 @@ func TestHandleRequest_SuccessPath(t *testing.T) {
// TestHandleRequest_UpgradeValidation verifies that the upgrade action uses
// the stricter validApkName regex (lowercase only, no @, no /).
func TestHandleRequest_UpgradeValidation(t *testing.T) {
origRunApkFunc := runApkFunc
runApkFunc = func(args ...string) ([]byte, error) {
return []byte("mock output"), nil
}
defer func() { runApkFunc = origRunApkFunc }()
// Valid names for upgrade (lowercase apk grammar)
valid := []string{
"curl",
@@ -462,6 +494,12 @@ func TestHandleRequest_UpgradeValidation(t *testing.T) {
// TestHandleRequest_UpgradeInjectionPatterns verifies 5 injection patterns are rejected.
func TestHandleRequest_UpgradeInjectionPatterns(t *testing.T) {
origRunApkFunc := runApkFunc
runApkFunc = func(args ...string) ([]byte, error) {
return []byte("mock output"), nil
}
defer func() { runApkFunc = origRunApkFunc }()
injections := []string{
"-malicious", // leading hyphen
"pkg;evil", // semicolon
@@ -486,6 +524,12 @@ func TestHandleRequest_UpgradeInjectionPatterns(t *testing.T) {
// by legacy validPkgName for install/uninstall) is REJECTED by upgrade action
// via the stricter validApkName.
func TestHandleRequest_UpgradeRejectsLegacySymbols(t *testing.T) {
origRunApkFunc := runApkFunc
runApkFunc = func(args ...string) ([]byte, error) {
return []byte("mock output"), nil
}
defer func() { runApkFunc = origRunApkFunc }()
legacySymbols := []string{
"pkg@edge", // @ accepted by validPkgName, rejected by validApkName
"@scope/pkg", // npm scoped — rejected by validApkName