chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const pkgMaxResults = 50
|
||||
|
||||
// detectDistro lee /etc/os-release y devuelve ID (ubuntu, debian, fedora, arch, ...).
|
||||
func detectDistro() string {
|
||||
f, err := os.Open("/etc/os-release")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
if strings.HasPrefix(line, "ID=") {
|
||||
return strings.Trim(strings.TrimPrefix(line, "ID="), `"`)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// runPkgSearch busca paquetes. Detect distro y delega a apt/dnf/pacman.
|
||||
// Permite override via env PKG_FAKE_OUTPUT (for tests).
|
||||
func runPkgSearch(cap *Capability, args map[string]any) (any, int, error) {
|
||||
_ = cap
|
||||
query := mapStringField(args, "query")
|
||||
if query == "" {
|
||||
return nil, -1, fmt.Errorf("query required")
|
||||
}
|
||||
// Validar query: solo alfanumerico + . + _ + -
|
||||
for _, r := range query {
|
||||
if !(r >= 'a' && r <= 'z') && !(r >= 'A' && r <= 'Z') &&
|
||||
!(r >= '0' && r <= '9') && r != '.' && r != '_' && r != '-' && r != '+' {
|
||||
return nil, -1, fmt.Errorf("invalid char in query: %q", query)
|
||||
}
|
||||
}
|
||||
|
||||
// Test fake output via env
|
||||
if fake := os.Getenv("PKG_FAKE_OUTPUT"); fake != "" {
|
||||
return parsePkgOutput(fake, "apt", query), 0, nil
|
||||
}
|
||||
|
||||
distro := detectDistro()
|
||||
var bin string
|
||||
var argv []string
|
||||
switch distro {
|
||||
case "ubuntu", "debian":
|
||||
bin = "apt-cache"
|
||||
argv = []string{"search", query}
|
||||
case "fedora", "rhel", "centos":
|
||||
bin = "dnf"
|
||||
argv = []string{"search", "--quiet", query}
|
||||
case "arch", "manjaro":
|
||||
bin = "pacman"
|
||||
argv = []string{"-Ss", query}
|
||||
default:
|
||||
// fallback try apt-cache
|
||||
if _, err := exec.LookPath("apt-cache"); err == nil {
|
||||
bin = "apt-cache"
|
||||
argv = []string{"search", query}
|
||||
distro = "ubuntu"
|
||||
} else {
|
||||
return nil, -1, fmt.Errorf("unsupported distro %q (no pkg manager found)", distro)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
c := exec.CommandContext(ctx, bin, argv...) // #nosec G204 — query validated
|
||||
out, err := c.Output()
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("%s search: %w", bin, err)
|
||||
}
|
||||
return parsePkgOutput(string(out), distro, query), 0, nil
|
||||
}
|
||||
|
||||
// parsePkgOutput parsea salida apt-cache search / dnf search / pacman -Ss.
|
||||
func parsePkgOutput(out, distro, query string) map[string]any {
|
||||
packages := []map[string]any{}
|
||||
lines := strings.Split(out, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
var name, desc string
|
||||
switch distro {
|
||||
case "ubuntu", "debian", "apt":
|
||||
// "name - description"
|
||||
idx := strings.Index(line, " - ")
|
||||
if idx > 0 {
|
||||
name = line[:idx]
|
||||
desc = line[idx+3:]
|
||||
} else {
|
||||
name = line
|
||||
}
|
||||
case "arch", "manjaro":
|
||||
// "repo/name version" then next line " desc"
|
||||
if strings.HasPrefix(line, " ") {
|
||||
if len(packages) > 0 {
|
||||
packages[len(packages)-1]["description"] = strings.TrimSpace(line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) > 0 {
|
||||
name = parts[0]
|
||||
}
|
||||
default:
|
||||
// dnf: "name.arch : description"
|
||||
if idx := strings.Index(line, " : "); idx > 0 {
|
||||
name = strings.TrimSpace(line[:idx])
|
||||
desc = strings.TrimSpace(line[idx+3:])
|
||||
} else {
|
||||
name = line
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
packages = append(packages, map[string]any{
|
||||
"name": name,
|
||||
"description": desc,
|
||||
})
|
||||
if len(packages) >= pkgMaxResults {
|
||||
break
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"query": query,
|
||||
"distro": distro,
|
||||
"packages": packages,
|
||||
"count": len(packages),
|
||||
"truncated": len(packages) >= pkgMaxResults,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user