Skip to content

Commit e72d61c

Browse files
committed
🐛 fix: bound snap package enumeration over ssh
1 parent eb62072 commit e72d61c

File tree

2 files changed

+496
-44
lines changed

2 files changed

+496
-44
lines changed

providers/os/resources/packages/snap_packages.go

Lines changed: 183 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
package packages
55

66
import (
7+
"bufio"
78
"fmt"
89
"io"
910
"os"
10-
"regexp"
11+
"path"
12+
"sort"
13+
"strconv"
14+
"strings"
1115

1216
"github.com/rs/zerolog/log"
1317
"github.com/spf13/afero"
@@ -26,6 +30,12 @@ type SnapPkgManager struct {
2630
platform *inventory.Platform
2731
}
2832

33+
type snapListEntry struct {
34+
name string
35+
version string
36+
rev string
37+
}
38+
2939
func (spm *SnapPkgManager) Name() string {
3040
return "Snap Package Manager"
3141
}
@@ -35,50 +45,195 @@ func (spm *SnapPkgManager) Format() string {
3545
}
3646

3747
func (spm *SnapPkgManager) List() ([]Package, error) {
38-
fs := spm.conn.FileSystem()
39-
snapDir := "/snap"
40-
afs := &afero.Afero{Fs: fs}
41-
_, dErr := afs.Stat(snapDir)
42-
if dErr != nil {
43-
log.Debug().Str("path", snapDir).Msg("cannot find snap dir")
44-
return []Package{}, nil
48+
if spm.conn.Capabilities().Has(shared.Capability_RunCommand) {
49+
packages, err := spm.listFromCLI()
50+
if err == nil {
51+
return packages, nil
52+
}
53+
54+
log.Debug().Err(err).Msg("mql[snap]> could not enumerate snaps via cli, falling back to filesystem")
4555
}
4656

47-
// e.g. /snap/firefox/6103/meta/snap.yaml
48-
// https://snapcraft.io/docs/the-snap-format#p-3326-setup-files
49-
snapRegEx := regexp.MustCompile(`/snap/[^/]+/\d+/meta/snap\.yaml`)
50-
files := []string{}
51-
err := afs.Walk(snapDir, func(path string, info os.FileInfo, err error) error {
52-
if info == nil || info.IsDir() {
53-
return nil
54-
}
55-
if !snapRegEx.MatchString(path) {
56-
return nil
57+
return spm.listFromFS()
58+
}
59+
60+
func (spm *SnapPkgManager) listFromCLI() ([]Package, error) {
61+
cmdResult, err := spm.conn.RunCommand("snap list")
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
if cmdResult.ExitStatus != 0 {
67+
stderr := "unknown error"
68+
if cmdResult.Stderr != nil {
69+
stderrBytes, readErr := io.ReadAll(cmdResult.Stderr)
70+
if readErr == nil {
71+
stderr = strings.TrimSpace(string(stderrBytes))
72+
if stderr == "" {
73+
stderr = "unknown error"
74+
}
75+
}
5776
}
5877

59-
files = append(files, path)
60-
return nil
61-
})
78+
return nil, fmt.Errorf("snap list failed: %s", stderr)
79+
}
80+
81+
if cmdResult.Stdout == nil {
82+
return []Package{}, nil
83+
}
84+
85+
entries, err := parseSnapListOutput(cmdResult.Stdout)
6286
if err != nil {
6387
return nil, err
6488
}
6589

66-
pkgList := []Package{}
67-
for _, file := range files {
68-
manifest, err := afs.Open(file)
90+
afs := &afero.Afero{Fs: spm.conn.FileSystem()}
91+
pkgList := make([]Package, 0, len(entries))
92+
93+
for _, entry := range entries {
94+
manifestPath := path.Join("/snap", entry.name, entry.rev, "meta", "snap.yaml")
95+
manifest, err := afs.Open(manifestPath)
6996
if err != nil {
70-
log.Error().Err(err).Str("file", file).Msg("could not open manifest file")
97+
log.Debug().Err(err).Str("path", manifestPath).Msg("mql[snap]> could not open snap manifest from cli revision")
7198
continue
7299
}
100+
73101
pkg, err := spm.parseSnapManifest(manifest)
102+
manifest.Close()
74103
if err != nil {
75-
log.Error().Err(err).Str("file", file).Msg("could not parse manifest file")
76-
manifest.Close()
104+
log.Debug().Err(err).Str("path", manifestPath).Msg("mql[snap]> could not parse snap manifest from cli revision")
77105
continue
78106
}
107+
79108
pkgList = append(pkgList, pkg)
80-
manifest.Close()
81109
}
110+
111+
return pkgList, nil
112+
}
113+
114+
func parseSnapListOutput(input io.Reader) ([]snapListEntry, error) {
115+
scanner := bufio.NewScanner(input)
116+
entries := []snapListEntry{}
117+
firstNonEmptyLine := true
118+
119+
for scanner.Scan() {
120+
line := strings.TrimSpace(scanner.Text())
121+
if line == "" {
122+
continue
123+
}
124+
125+
fields := strings.Fields(line)
126+
if len(fields) == 0 {
127+
continue
128+
}
129+
130+
if firstNonEmptyLine {
131+
firstNonEmptyLine = false
132+
if len(fields) >= 3 && fields[0] == "Name" && fields[2] == "Rev" {
133+
continue
134+
}
135+
}
136+
137+
if len(fields) < 3 {
138+
continue
139+
}
140+
141+
entries = append(entries, snapListEntry{
142+
name: fields[0],
143+
version: fields[1],
144+
rev: fields[2],
145+
})
146+
}
147+
148+
if err := scanner.Err(); err != nil {
149+
return nil, err
150+
}
151+
152+
return entries, nil
153+
}
154+
155+
func (spm *SnapPkgManager) listFromFS() ([]Package, error) {
156+
afs := &afero.Afero{Fs: spm.conn.FileSystem()}
157+
const snapDir = "/snap"
158+
159+
dirEntries, err := afs.ReadDir(snapDir)
160+
if err != nil {
161+
if os.IsNotExist(err) {
162+
log.Debug().Str("path", snapDir).Msg("cannot find snap dir")
163+
return []Package{}, nil
164+
}
165+
166+
return nil, err
167+
}
168+
169+
pkgList := []Package{}
170+
for _, entry := range dirEntries {
171+
name := entry.Name()
172+
currentManifestPath := path.Join(snapDir, name, "current", "meta", "snap.yaml")
173+
manifest, err := afs.Open(currentManifestPath)
174+
if err == nil {
175+
pkg, err := spm.parseSnapManifest(manifest)
176+
manifest.Close()
177+
if err != nil {
178+
log.Debug().Err(err).Str("path", currentManifestPath).Msg("mql[snap]> could not parse current snap manifest")
179+
continue
180+
}
181+
182+
pkgList = append(pkgList, pkg)
183+
continue
184+
}
185+
186+
if !os.IsNotExist(err) {
187+
log.Debug().Err(err).Str("path", currentManifestPath).Msg("mql[snap]> could not open current snap manifest")
188+
continue
189+
}
190+
191+
revisionDir := path.Join(snapDir, name)
192+
revisionEntries, err := afs.ReadDir(revisionDir)
193+
if err != nil {
194+
if !os.IsNotExist(err) {
195+
log.Debug().Err(err).Str("path", revisionDir).Msg("mql[snap]> could not inspect snap revisions")
196+
}
197+
continue
198+
}
199+
200+
revisions := make([]int, 0, len(revisionEntries))
201+
for _, revisionEntry := range revisionEntries {
202+
if !revisionEntry.IsDir() {
203+
continue
204+
}
205+
206+
revision, err := strconv.Atoi(revisionEntry.Name())
207+
if err != nil {
208+
continue
209+
}
210+
211+
revisions = append(revisions, revision)
212+
}
213+
214+
sort.Sort(sort.Reverse(sort.IntSlice(revisions)))
215+
for _, revision := range revisions {
216+
manifestPath := path.Join(snapDir, name, strconv.Itoa(revision), "meta", "snap.yaml")
217+
manifest, err := afs.Open(manifestPath)
218+
if err != nil {
219+
if !os.IsNotExist(err) {
220+
log.Debug().Err(err).Str("path", manifestPath).Msg("mql[snap]> could not open snap manifest from revision fallback")
221+
}
222+
continue
223+
}
224+
225+
pkg, err := spm.parseSnapManifest(manifest)
226+
manifest.Close()
227+
if err != nil {
228+
log.Debug().Err(err).Str("path", manifestPath).Msg("mql[snap]> could not parse snap manifest from revision fallback")
229+
continue
230+
}
231+
232+
pkgList = append(pkgList, pkg)
233+
break
234+
}
235+
}
236+
82237
return pkgList, nil
83238
}
84239

0 commit comments

Comments
 (0)