diff --git a/imgbom/distro/identify.go b/imgbom/distro/identify.go index d5468b4ca..b456762d4 100644 --- a/imgbom/distro/identify.go +++ b/imgbom/distro/identify.go @@ -1,11 +1,104 @@ package distro import ( + "regexp" + "strings" + + "github.com/anchore/imgbom/internal/log" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/image" ) -func Identify(img *image.Image) (Distro, error) { +// returns a distro or nil +type parseFunc func(string) *Distro + +// Identify parses distro-specific files to determine distro metadata like version and release +func Identify(img *image.Image) *Distro { // TODO: implement me based off of https://github.com/anchore/anchore-engine/blob/78b23d7e8f007005c070673405b5e23730a660e0/anchore_engine/analyzers/utils.py#L131 - return NewDistro(UnknownDistro, "0.0.0") + identityFiles := map[file.Path]parseFunc{ + "/etc/os-release": parseOsRelease, + // Debian and Debian-based distros have the same contents linked from this path + "/usr/lib/os-release": parseOsRelease, + // TODO: change this to /bin/busybox when stereoscope deals with hardlinks + "/bin/[": parseBusyBox, + } + + for path, fn := range identityFiles { + + contents, err := img.FileContentsFromSquash(path) + + if err != nil { + log.Errorf("unable to get contents from %s: %s", path, err) + continue + } + + if contents == "" { + log.Debugf("no contents in file, skipping: %s", path) + continue + } + distro := fn(contents) + + if distro == nil { + continue + } + + return distro + + } + // TODO: is it useful to know partially detected distros? where the ID is known but not the version (and viceversa?) + return nil +} + +func assembleDistro(name, version string) *Distro { + distroType, ok := Mappings[name] + + // Both distro and version must be present + if len(name) == 0 || len(version) == 0 { + return nil + } + + if ok { + distro, err := NewDistro(distroType, version) + if err != nil { + return nil + } + return &distro + } + + return nil +} + +func parseOsRelease(contents string) *Distro { + id, vers := "", "" + for _, line := range strings.Split(contents, "\n") { + + parts := strings.Split(line, "=") + prefix := parts[0] + value := strings.ReplaceAll(parts[len(parts)-1], `"`, "") + + switch prefix { + case "ID": + id = value + case "VERSION_ID": + vers = value + } + } + + return assembleDistro(id, vers) +} + +var busyboxVersionMatcher = regexp.MustCompile(`BusyBox v[\d\.]+`) + +func parseBusyBox(contents string) *Distro { + matches := busyboxVersionMatcher.FindAllString(contents, -1) + for _, match := range matches { + parts := strings.Split(match, " ") + version := strings.ReplaceAll(parts[1], "v", "") + distro := assembleDistro("busybox", version) + if distro != nil { + return distro + } + } + return nil }