Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 183 additions & 28 deletions providers/os/resources/packages/snap_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
package packages

import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"path"
"sort"
"strconv"
"strings"

"github.com/rs/zerolog/log"
"github.com/spf13/afero"
Expand All @@ -26,6 +30,12 @@ type SnapPkgManager struct {
platform *inventory.Platform
}

type snapListEntry struct {
name string
version string
rev string
}

func (spm *SnapPkgManager) Name() string {
return "Snap Package Manager"
}
Expand All @@ -35,50 +45,195 @@ func (spm *SnapPkgManager) Format() string {
}

func (spm *SnapPkgManager) List() ([]Package, error) {
fs := spm.conn.FileSystem()
snapDir := "/snap"
afs := &afero.Afero{Fs: fs}
_, dErr := afs.Stat(snapDir)
if dErr != nil {
log.Debug().Str("path", snapDir).Msg("cannot find snap dir")
return []Package{}, nil
if spm.conn.Capabilities().Has(shared.Capability_RunCommand) {
packages, err := spm.listFromCLI()
if err == nil {
return packages, nil
}

log.Debug().Err(err).Msg("mql[snap]> could not enumerate snaps via cli, falling back to filesystem")
}

// e.g. /snap/firefox/6103/meta/snap.yaml
// https://snapcraft.io/docs/the-snap-format#p-3326-setup-files
snapRegEx := regexp.MustCompile(`/snap/[^/]+/\d+/meta/snap\.yaml`)
files := []string{}
err := afs.Walk(snapDir, func(path string, info os.FileInfo, err error) error {
if info == nil || info.IsDir() {
return nil
}
if !snapRegEx.MatchString(path) {
return nil
return spm.listFromFS()
}

func (spm *SnapPkgManager) listFromCLI() ([]Package, error) {
cmdResult, err := spm.conn.RunCommand("snap list")
if err != nil {
return nil, err
}

if cmdResult.ExitStatus != 0 {
stderr := "unknown error"
if cmdResult.Stderr != nil {
stderrBytes, readErr := io.ReadAll(cmdResult.Stderr)
if readErr == nil {
stderr = strings.TrimSpace(string(stderrBytes))
if stderr == "" {
stderr = "unknown error"
}
}
}

files = append(files, path)
return nil
})
return nil, fmt.Errorf("snap list failed: %s", stderr)
}

if cmdResult.Stdout == nil {
return []Package{}, nil
}

entries, err := parseSnapListOutput(cmdResult.Stdout)
if err != nil {
return nil, err
}

pkgList := []Package{}
for _, file := range files {
manifest, err := afs.Open(file)
afs := &afero.Afero{Fs: spm.conn.FileSystem()}
pkgList := make([]Package, 0, len(entries))

for _, entry := range entries {
manifestPath := path.Join("/snap", entry.name, entry.rev, "meta", "snap.yaml")
manifest, err := afs.Open(manifestPath)
if err != nil {
log.Error().Err(err).Str("file", file).Msg("could not open manifest file")
log.Debug().Err(err).Str("path", manifestPath).Msg("mql[snap]> could not open snap manifest from cli revision")
continue
}

pkg, err := spm.parseSnapManifest(manifest)
manifest.Close()
if err != nil {
log.Error().Err(err).Str("file", file).Msg("could not parse manifest file")
manifest.Close()
log.Debug().Err(err).Str("path", manifestPath).Msg("mql[snap]> could not parse snap manifest from cli revision")
continue
}

pkgList = append(pkgList, pkg)
manifest.Close()
}

return pkgList, nil
}

func parseSnapListOutput(input io.Reader) ([]snapListEntry, error) {
scanner := bufio.NewScanner(input)
entries := []snapListEntry{}
firstNonEmptyLine := true

for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}

fields := strings.Fields(line)
if len(fields) == 0 {
continue
}

if firstNonEmptyLine {
Comment thread
syrull marked this conversation as resolved.
firstNonEmptyLine = false
if len(fields) >= 3 && fields[0] == "Name" && fields[2] == "Rev" {
continue
}
}

if len(fields) < 3 {
continue
}

entries = append(entries, snapListEntry{
name: fields[0],
version: fields[1],
rev: fields[2],
})
}

if err := scanner.Err(); err != nil {
return nil, err
}

return entries, nil
}

func (spm *SnapPkgManager) listFromFS() ([]Package, error) {
Comment thread
syrull marked this conversation as resolved.
afs := &afero.Afero{Fs: spm.conn.FileSystem()}
const snapDir = "/snap"

dirEntries, err := afs.ReadDir(snapDir)
if err != nil {
if os.IsNotExist(err) {
log.Debug().Str("path", snapDir).Msg("cannot find snap dir")
return []Package{}, nil
}

return nil, err
}

pkgList := []Package{}
for _, entry := range dirEntries {
name := entry.Name()
currentManifestPath := path.Join(snapDir, name, "current", "meta", "snap.yaml")
manifest, err := afs.Open(currentManifestPath)
if err == nil {
pkg, err := spm.parseSnapManifest(manifest)
manifest.Close()
if err != nil {
log.Debug().Err(err).Str("path", currentManifestPath).Msg("mql[snap]> could not parse current snap manifest")
continue
}

pkgList = append(pkgList, pkg)
continue
}

if !os.IsNotExist(err) {
log.Debug().Err(err).Str("path", currentManifestPath).Msg("mql[snap]> could not open current snap manifest")
continue
}

revisionDir := path.Join(snapDir, name)
revisionEntries, err := afs.ReadDir(revisionDir)
if err != nil {
if !os.IsNotExist(err) {
log.Debug().Err(err).Str("path", revisionDir).Msg("mql[snap]> could not inspect snap revisions")
}
continue
}

revisions := make([]int, 0, len(revisionEntries))
for _, revisionEntry := range revisionEntries {
if !revisionEntry.IsDir() {
continue
}

revision, err := strconv.Atoi(revisionEntry.Name())
if err != nil {
continue
}

revisions = append(revisions, revision)
}

sort.Sort(sort.Reverse(sort.IntSlice(revisions)))
for _, revision := range revisions {
manifestPath := path.Join(snapDir, name, strconv.Itoa(revision), "meta", "snap.yaml")
manifest, err := afs.Open(manifestPath)
if err != nil {
if !os.IsNotExist(err) {
log.Debug().Err(err).Str("path", manifestPath).Msg("mql[snap]> could not open snap manifest from revision fallback")
}
continue
}

pkg, err := spm.parseSnapManifest(manifest)
manifest.Close()
if err != nil {
log.Debug().Err(err).Str("path", manifestPath).Msg("mql[snap]> could not parse snap manifest from revision fallback")
continue
}

pkgList = append(pkgList, pkg)
break
}
}

return pkgList, nil
}

Expand Down
Loading
Loading