74 lines
1.9 KiB
Go
74 lines
1.9 KiB
Go
package packages
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
const collectTimeout = 2 * time.Minute
|
|
|
|
// Collect enumerates installed packages. Linux only: Windows agents are
|
|
// second-class by design, and vulnerability scanning there needs a different
|
|
// source, a different collector and a different matcher, all out of scope.
|
|
//
|
|
// The format strings below are raw string literals on purpose. The "\t" and
|
|
// "\n" reach dpkg-query and rpm as two characters each, and those tools do the
|
|
// interpreting themselves — Go must not consume the escapes first.
|
|
func Collect() (OSRelease, []Package, error) {
|
|
if runtime.GOOS != "linux" {
|
|
return OSRelease{}, nil, fmt.Errorf("package collection is linux-only, got %s", runtime.GOOS)
|
|
}
|
|
|
|
osrel, err := DetectOS()
|
|
if err != nil {
|
|
return OSRelease{}, nil, fmt.Errorf("detect os: %w", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), collectTimeout)
|
|
defer cancel()
|
|
|
|
switch {
|
|
case have("dpkg-query"):
|
|
out, err := run(ctx, "dpkg-query", "-W", "-f",
|
|
`${Package}\t${Version}\t${Architecture}\t${source:Package}\t${db:Status-Status}\n`)
|
|
if err != nil {
|
|
return osrel, nil, err
|
|
}
|
|
return osrel, ParseDpkg(out), nil
|
|
|
|
case have("rpm"):
|
|
out, err := run(ctx, "rpm", "-qa", "--qf",
|
|
`%{NAME}\t%{EPOCH}\t%{VERSION}-%{RELEASE}\t%{ARCH}\t%{SOURCERPM}\n`)
|
|
if err != nil {
|
|
return osrel, nil, err
|
|
}
|
|
return osrel, ParseRPM(out), nil
|
|
|
|
case have("apk"):
|
|
out, err := run(ctx, "apk", "info", "-v")
|
|
if err != nil {
|
|
return osrel, nil, err
|
|
}
|
|
return osrel, ParseAPK(out), nil
|
|
|
|
default:
|
|
return osrel, nil, fmt.Errorf("no supported package manager found")
|
|
}
|
|
}
|
|
|
|
func have(bin string) bool {
|
|
_, err := exec.LookPath(bin)
|
|
return err == nil
|
|
}
|
|
|
|
func run(ctx context.Context, name string, args ...string) (string, error) {
|
|
out, err := exec.CommandContext(ctx, name, args...).Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
return string(out), nil
|
|
}
|