Skip to content

Commit ee44658

Browse files
authored
🐛 Fix npm package ID to be uniq across directories (#5523)
* 🐛 Fix npm package ID to be uniq across directories The same npm package can be installed in different directories. Ensure we get all the packages listed by setting a unique ID. Fixes: #5479 Signed-off-by: Christian Zunker <christian@mondoo.com> * Add tests Signed-off-by: Christian Zunker <christian@mondoo.com> * Fix license headers Signed-off-by: Christian Zunker <christian@mondoo.com> * Fix linter Signed-off-by: Christian Zunker <christian@mondoo.com> --------- Signed-off-by: Christian Zunker <christian@mondoo.com>
1 parent 661a17c commit ee44658

File tree

5 files changed

+244
-22
lines changed

5 files changed

+244
-22
lines changed

providers/os/connection/fs/filesystem.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@ func NewFileSystemConnectionWithClose(id uint32, conf *inventory.Config, asset *
3333

3434
log.Debug().Str("path", path).Msg("load filesystem")
3535

36+
return NewFileSystemConnectionWithFs(id, conf, asset, path, closeFN, fs.NewMountedFs(path))
37+
}
38+
39+
func NewFileSystemConnectionWithFs(id uint32, conf *inventory.Config, asset *inventory.Asset, path string, closeFN func(), fs afero.Fs) (*FileSystemConnection, error) {
3640
return &FileSystemConnection{
3741
Connection: plugin.NewConnection(id, asset),
3842
Conf: conf,
3943
asset: asset,
4044
MountedDir: path,
4145
closeFN: closeFN,
4246
tcPlatformId: conf.PlatformId,
43-
fs: fs.NewMountedFs(path),
47+
fs: fs,
4448
}, nil
4549
}
4650

providers/os/resources/npm.go

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,23 @@ import (
2323
"go.mondoo.com/cnquery/v11/types"
2424
)
2525

26-
var (
27-
defaultNpmPaths = []string{
28-
// Linux
29-
"/usr/local/lib",
30-
"/opt/homebrew/lib",
31-
"/usr/lib",
32-
"/home/*/.npm-global/lib",
33-
// Windows
34-
"C:\\Users\\*\\AppData\\Roaming\\npm",
35-
"C:\\Program Files\\nodejs\\node_modules\\npm",
36-
"C:\\Users\\*\\node_modules",
37-
// macOS
38-
"/Users/*/.npm-global/lib",
39-
// Container app paths
40-
"/app",
41-
"/home/node/app",
42-
"/usr/src/app",
43-
}
44-
)
26+
var defaultNpmPaths = []string{
27+
// Linux
28+
"/usr/local/lib",
29+
"/opt/homebrew/lib",
30+
"/usr/lib",
31+
"/home/*/.npm-global/lib",
32+
// Windows
33+
"C:\\Users\\*\\AppData\\Roaming\\npm",
34+
"C:\\Program Files\\nodejs\\node_modules\\npm",
35+
"C:\\Users\\*\\node_modules",
36+
// macOS
37+
"/Users/*/.npm-global/lib",
38+
// Container app paths
39+
"/app",
40+
"/home/node/app",
41+
"/usr/src/app",
42+
}
4543

4644
func initNpmPackages(_ *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) {
4745
if x, ok := args["path"]; ok {
@@ -82,7 +80,7 @@ func collectNpmPackagesInPaths(runtime *plugin.Runtime, fs afero.Fs, paths []str
8280
// we walk through the directories and check if there is a node_modules directory
8381
log.Debug().Str("path", walkPath).Msg("found npm package")
8482
nodeModulesPath := filepath.Join(walkPath, "node_modules")
85-
var files, err = afs.ReadDir(nodeModulesPath)
83+
files, err := afs.ReadDir(nodeModulesPath)
8684
if err != nil {
8785
// we ignore the error, it is expected that there is no node_modules directory
8886
return nil
@@ -322,8 +320,14 @@ func newNpmPackage(runtime *plugin.Runtime, pkg *languages.Package) (*mqlNpmPack
322320
mqlFiles = append(mqlFiles, lf)
323321
}
324322

323+
path := ""
324+
if len(mqlFiles) > 0 {
325+
if fi, ok := mqlFiles[0].(*mqlPkgFileInfo); ok {
326+
path = fi.Path.Data
327+
}
328+
}
325329
mqlPkg, err := CreateResource(runtime, "npm.package", map[string]*llx.RawData{
326-
"id": llx.StringData(pkg.Name),
330+
"id": llx.StringData(pkg.Name + path),
327331
"name": llx.StringData(pkg.Name),
328332
"version": llx.StringData(pkg.Version),
329333
"purl": llx.StringData(pkg.Purl),

providers/os/resources/npm_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package resources
5+
6+
import (
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
11+
"github.com/spf13/afero"
12+
"github.com/stretchr/testify/require"
13+
"go.mondoo.com/cnquery/v11/llx"
14+
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
15+
"go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin"
16+
"go.mondoo.com/cnquery/v11/providers/os/connection/fs"
17+
"go.mondoo.com/cnquery/v11/types"
18+
"go.mondoo.com/cnquery/v11/utils/syncx"
19+
)
20+
21+
func TestNpmPackage_unique(t *testing.T) {
22+
mockFS := afero.NewMemMapFs()
23+
// create test files and directories
24+
err := mockFS.MkdirAll("/usr/local/lib/node_modules/generator-code", 0o755)
25+
require.NoError(t, err)
26+
err = mockFS.MkdirAll("/usr/local/lib/node_modules/yo", 0o755)
27+
require.NoError(t, err)
28+
29+
// Read package.json files from testdata
30+
yoPkg, err := os.ReadFile(filepath.Join("packages", "testdata", "yo_package.json"))
31+
require.NoError(t, err)
32+
require.NotNil(t, yoPkg)
33+
34+
gcPkg, err := os.ReadFile(filepath.Join("packages", "testdata", "gc_package.json"))
35+
require.NoError(t, err)
36+
require.NotNil(t, gcPkg)
37+
38+
err = afero.WriteFile(mockFS, "/usr/local/lib/node_modules/generator-code/package.json", gcPkg, 0o644)
39+
require.NoError(t, err)
40+
err = afero.WriteFile(mockFS, "/usr/local/lib/node_modules/yo/package.json", yoPkg, 0o644)
41+
require.NoError(t, err)
42+
43+
conn, err := fs.NewFileSystemConnectionWithFs(0, &inventory.Config{}, &inventory.Asset{}, "", nil, mockFS)
44+
require.NoError(t, err)
45+
46+
r := &plugin.Runtime{
47+
Resources: &syncx.Map[plugin.Resource]{},
48+
Connection: conn,
49+
Callback: &providerCallbacks{},
50+
}
51+
mqlNpm := &mqlNpmPackages{
52+
MqlRuntime: r,
53+
}
54+
55+
// Create resources from filesystem
56+
err = mqlNpm.gatherData()
57+
require.NoError(t, err)
58+
59+
// Check that we have 4 packages
60+
require.Equal(t, 4, len(mqlNpm.List.Data))
61+
62+
// Check that the first package is yosay
63+
pkg2 := mqlNpm.List.Data[2].(*mqlNpmPackage)
64+
require.Equal(t, "yosay", pkg2.Name.Data)
65+
require.Equal(t, "^2.0.2", pkg2.Version.Data)
66+
67+
// Check that the third package is also yosay, but with a different version
68+
pkg3 := mqlNpm.List.Data[3].(*mqlNpmPackage)
69+
require.Equal(t, "yosay", pkg3.Name.Data)
70+
require.Equal(t, "^3.0.0", pkg3.Version.Data)
71+
72+
// To get the correct data, we need distinct IDs
73+
require.NotEqual(t, pkg2.MqlID(), pkg3.MqlID())
74+
require.Equal(t, "yosay/usr/local/lib/node_modules/yo/package.json", pkg2.MqlID())
75+
require.Equal(t, "yosay/usr/local/lib/node_modules/generator-code/package.json", pkg3.MqlID())
76+
}
77+
78+
// Mock callbacks for testing
79+
// These are needed during calls to CreateSharedResource
80+
type providerCallbacks struct{}
81+
82+
func (p *providerCallbacks) GetData(req *plugin.DataReq) (*plugin.DataRes, error) {
83+
return &plugin.DataRes{
84+
Data: &llx.Primitive{
85+
Type: string(types.Resource(req.Resource)),
86+
Value: []byte("not of interest"),
87+
},
88+
}, nil
89+
}
90+
91+
func (p *providerCallbacks) GetRecording(req *plugin.DataReq) (*plugin.ResourceData, error) {
92+
res := plugin.ResourceData{}
93+
return &res, nil
94+
}
95+
96+
func (p *providerCallbacks) Collect(req *plugin.DataRes) error {
97+
return nil
98+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "generator-code",
3+
"version": "1.11.9",
4+
"description": "Yeoman generator for Visual Studio Code extensions.",
5+
"keywords": [
6+
"yeoman-generator",
7+
"vscode",
8+
"visual studio",
9+
"visual studio code",
10+
"vs code",
11+
"extensions"
12+
],
13+
"type": "module",
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/Microsoft/vscode-generator-code.git"
17+
},
18+
"bugs": {
19+
"url": "https://github.com/Microsoft/vscode-generator-code/issues"
20+
},
21+
"main": "./generators/app/index.js",
22+
"homepage": "http://code.visualstudio.com",
23+
"license": "MIT",
24+
"author": {
25+
"name": "VS Code Team",
26+
"url": "https://github.com/Microsoft"
27+
},
28+
"engines": {
29+
"node": "^18.17.0 || >=20.5.0"
30+
},
31+
"scripts": {
32+
"test": "mocha",
33+
"prepublishOnly": "npm test",
34+
"preversion": "npm test",
35+
"postversion": "git push && git push --tags"
36+
},
37+
"dependencies": {
38+
"yosay": "^3.0.0"
39+
}
40+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"name": "yo",
3+
"version": "5.1.0",
4+
"description": "CLI tool for running Yeoman generators",
5+
"license": "BSD-2-Clause",
6+
"repository": "yeoman/yo",
7+
"homepage": "http://yeoman.io",
8+
"author": "Yeoman",
9+
"main": "lib",
10+
"bin": {
11+
"yo": "lib/cli.js",
12+
"yo-complete": "lib/completion/index.js"
13+
},
14+
"engines": {
15+
"node": "^18.17.0 || >=20.5.0"
16+
},
17+
"scripts": {
18+
"postinstall": "yodoctor",
19+
"postupdate": "yodoctor",
20+
"pretest": "xo",
21+
"test": "nyc mocha --timeout=30000",
22+
"coverage": "nyc report --reporter=text-lcov | coveralls"
23+
},
24+
"files": [
25+
"lib"
26+
],
27+
"keywords": [
28+
"cli-app",
29+
"cli",
30+
"front-end",
31+
"development",
32+
"dev",
33+
"build",
34+
"web",
35+
"tool",
36+
"scaffold",
37+
"stack",
38+
"yeoman",
39+
"generator",
40+
"generate",
41+
"app",
42+
"boilerplate"
43+
],
44+
"dependencies": {
45+
"yosay": "^2.0.2"
46+
},
47+
"resolutions": {
48+
"natives": "1.1.3"
49+
},
50+
"tabtab": {
51+
"yo": [
52+
"-f",
53+
"--force",
54+
"--version",
55+
"--no-color",
56+
"--generators",
57+
"--local-only"
58+
]
59+
},
60+
"xo": {
61+
"space": true,
62+
"overrides": [
63+
{
64+
"files": "test/**",
65+
"envs": [
66+
"node",
67+
"mocha"
68+
]
69+
}
70+
],
71+
"rules": {
72+
"promise/prefer-await-to-then": 0,
73+
"unicorn/no-array-reduce": "off"
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)