mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 18:15:47 +00:00
feat: implement sudo fallback for pkg-helper
This commit is contained in:
1 parent
37e2d37cf4
commit
d04108fff9
2 files changed
+61
-10
No files matched your search
+17
-10
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user