mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
feat: Add Julia language analyzer support (#5635)
This commit is contained in:
1
.github/workflows/semantic-pr.yaml
vendored
1
.github/workflows/semantic-pr.yaml
vendored
@@ -77,6 +77,7 @@ jobs:
|
||||
swift
|
||||
bitnami
|
||||
conda
|
||||
julia
|
||||
|
||||
os
|
||||
lang
|
||||
|
||||
@@ -143,6 +143,7 @@ language:
|
||||
- go
|
||||
- elixir
|
||||
- dart
|
||||
- julia
|
||||
|
||||
vuln:
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ On the other hand, when the target is a post-build artifact, like a container im
|
||||
| [Dart](dart.md) | pubspec.lock | - | - | ✅ | ✅ |
|
||||
| [Swift](swift.md) | Podfile.lock | - | - | ✅ | ✅ |
|
||||
| | Package.resolved | - | - | ✅ | ✅ |
|
||||
| [Julia](julia.md) | Manifest.toml | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
The path of these files does not matter.
|
||||
|
||||
|
||||
24
docs/docs/coverage/language/julia.md
Normal file
24
docs/docs/coverage/language/julia.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Julia
|
||||
|
||||
## Features
|
||||
|
||||
Trivy supports [Pkg.jl](https://pkgdocs.julialang.org/v1/), which is the Julia package manager.
|
||||
The following table provides an outline of the features Trivy offers.
|
||||
|
||||
| Package manager | File | Transitive dependencies | Dev dependencies | License | Dependency graph | Position |
|
||||
| --------------- | ------------- | :---------------------: | :--------------- | :-----: | :--------------: | :------: |
|
||||
| Pkg.jl | Manifest.toml | ✅ | Excluded[^1] | - | ✅ | ✅ |
|
||||
|
||||
### Pkg.jl
|
||||
|
||||
Trivy searches for `Manifest.toml` to detect dependencies.
|
||||
|
||||
Trivy also supports dependency trees; however, to display an accurate tree, it needs to know whether each package is a direct dependency of the project.
|
||||
Since this information is not included in `Manifest.toml`, Trivy parses `Project.toml`, which should be located next to `Project.toml`.
|
||||
If you want to see the dependency tree, please ensure that `Project.toml` is present.
|
||||
|
||||
Scanning `Manifest.toml` and `Project.toml` together also removes developer dependencies.
|
||||
|
||||
Dependency extensions are currently ignored.
|
||||
|
||||
[^1]: When you scan `Manifest.toml` and `Project.toml` together.
|
||||
@@ -397,6 +397,15 @@ func TestRepository(t *testing.T) {
|
||||
want.ArtifactType = artifact.TypeFilesystem
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "julia generating SPDX SBOM",
|
||||
args: args{
|
||||
command: "rootfs",
|
||||
format: "spdx-json",
|
||||
input: "testdata/fixtures/repo/julia",
|
||||
},
|
||||
golden: "testdata/julia-spdx.json.golden",
|
||||
},
|
||||
}
|
||||
|
||||
// Set up testing DB
|
||||
|
||||
16
integration/testdata/fixtures/repo/julia/Manifest.toml
vendored
Normal file
16
integration/testdata/fixtures/repo/julia/Manifest.toml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# This file is machine-generated - editing it directly is not advised
|
||||
|
||||
julia_version = "1.9.0"
|
||||
manifest_format = "2.0"
|
||||
project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc"
|
||||
|
||||
[[deps.A]]
|
||||
uuid = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60"
|
||||
|
||||
[deps.A.deps]
|
||||
B = "f41f7b98-334e-11e9-1257-49272045fb24"
|
||||
|
||||
[[deps.B]]
|
||||
uuid = "f41f7b98-334e-11e9-1257-49272045fb24"
|
||||
[[deps.B]]
|
||||
uuid = "edca9bc6-334e-11e9-3554-9595dbb4349c"
|
||||
7
integration/testdata/fixtures/repo/julia/Project.toml
vendored
Normal file
7
integration/testdata/fixtures/repo/julia/Project.toml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
name = "packageName"
|
||||
uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773"
|
||||
version = "0.1.0"
|
||||
|
||||
[deps]
|
||||
A = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60"
|
||||
B = "edca9bc6-334e-11e9-3554-9595dbb4349c"
|
||||
138
integration/testdata/julia-spdx.json.golden
vendored
Normal file
138
integration/testdata/julia-spdx.json.golden
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"spdxVersion": "SPDX-2.3",
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "testdata/fixtures/repo/julia",
|
||||
"documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/testdata/fixtures/repo/julia-3ff14136-e09f-4df9-80ea-000000000006",
|
||||
"creationInfo": {
|
||||
"creators": [
|
||||
"Organization: aquasecurity",
|
||||
"Tool: trivy-dev"
|
||||
],
|
||||
"created": "2021-08-25T12:20:30Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
"name": "Manifest.toml",
|
||||
"SPDXID": "SPDXRef-Application-18fc3597717a3e56",
|
||||
"downloadLocation": "NONE",
|
||||
"filesAnalyzed": false,
|
||||
"attributionTexts": [
|
||||
"Class: lang-pkgs",
|
||||
"Type: julia"
|
||||
],
|
||||
"primaryPackagePurpose": "APPLICATION"
|
||||
},
|
||||
{
|
||||
"name": "A",
|
||||
"SPDXID": "SPDXRef-Package-2a46714189f3b9de",
|
||||
"versionInfo": "1.9.0",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NONE",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "package found in: Manifest.toml",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:julia/A@1.9.0?uuid=ead4f63c-334e-11e9-00e6-e7f0a5f21b60"
|
||||
}
|
||||
],
|
||||
"attributionTexts": [
|
||||
"PkgID: ead4f63c-334e-11e9-00e6-e7f0a5f21b60",
|
||||
"PkgType: julia"
|
||||
],
|
||||
"primaryPackagePurpose": "LIBRARY"
|
||||
},
|
||||
{
|
||||
"name": "B",
|
||||
"SPDXID": "SPDXRef-Package-4a8e351c4c9b7318",
|
||||
"versionInfo": "1.9.0",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NONE",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "package found in: Manifest.toml",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:julia/B@1.9.0?uuid=edca9bc6-334e-11e9-3554-9595dbb4349c"
|
||||
}
|
||||
],
|
||||
"attributionTexts": [
|
||||
"PkgID: edca9bc6-334e-11e9-3554-9595dbb4349c",
|
||||
"PkgType: julia"
|
||||
],
|
||||
"primaryPackagePurpose": "LIBRARY"
|
||||
},
|
||||
{
|
||||
"name": "B",
|
||||
"SPDXID": "SPDXRef-Package-d10d5e4a30a43fff",
|
||||
"versionInfo": "1.9.0",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NONE",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "package found in: Manifest.toml",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:julia/B@1.9.0?uuid=f41f7b98-334e-11e9-1257-49272045fb24"
|
||||
}
|
||||
],
|
||||
"attributionTexts": [
|
||||
"PkgID: f41f7b98-334e-11e9-1257-49272045fb24",
|
||||
"PkgType: julia"
|
||||
],
|
||||
"primaryPackagePurpose": "LIBRARY"
|
||||
},
|
||||
{
|
||||
"name": "testdata/fixtures/repo/julia",
|
||||
"SPDXID": "SPDXRef-Filesystem-1be792dd0077c431",
|
||||
"downloadLocation": "NONE",
|
||||
"filesAnalyzed": false,
|
||||
"attributionTexts": [
|
||||
"SchemaVersion: 2"
|
||||
],
|
||||
"primaryPackagePurpose": "SOURCE"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Application-18fc3597717a3e56",
|
||||
"relatedSpdxElement": "SPDXRef-Package-2a46714189f3b9de",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Application-18fc3597717a3e56",
|
||||
"relatedSpdxElement": "SPDXRef-Package-4a8e351c4c9b7318",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Application-18fc3597717a3e56",
|
||||
"relatedSpdxElement": "SPDXRef-Package-d10d5e4a30a43fff",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||
"relatedSpdxElement": "SPDXRef-Filesystem-1be792dd0077c431",
|
||||
"relationshipType": "DESCRIBES"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Filesystem-1be792dd0077c431",
|
||||
"relatedSpdxElement": "SPDXRef-Application-18fc3597717a3e56",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-2a46714189f3b9de",
|
||||
"relatedSpdxElement": "SPDXRef-Package-d10d5e4a30a43fff",
|
||||
"relationshipType": "DEPENDS_ON"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -102,6 +102,7 @@ nav:
|
||||
- Ruby: docs/coverage/language/ruby.md
|
||||
- Rust: docs/coverage/language/rust.md
|
||||
- Swift: docs/coverage/language/swift.md
|
||||
- Julia: docs/coverage/language/julia.md
|
||||
- IaC:
|
||||
- Overview: docs/coverage/iac/index.md
|
||||
- Azure ARM Template: docs/coverage/iac/azure-arm.md
|
||||
|
||||
@@ -36,8 +36,9 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
|
||||
var primMan primitiveManifest
|
||||
var manMetadata toml.MetaData
|
||||
decoder := toml.NewDecoder(r)
|
||||
// Try to read the old Manifest format. If that fails, try the new format.
|
||||
if _, err := decoder.Decode(&oldDeps); err != nil {
|
||||
// Try to read the old Manifest format. This can also read the v1.0 Manifest format, which we parse out later.
|
||||
var err error
|
||||
if manMetadata, err = decoder.Decode(&oldDeps); err != nil {
|
||||
if _, err = r.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, nil, xerrors.Errorf("seek error: %w", err)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,12 @@ func TestParse(t *testing.T) {
|
||||
want: juliaV1_9ShadowedDepPkgs,
|
||||
wantDeps: juliaV1_9ShadowedDepDeps,
|
||||
},
|
||||
{
|
||||
name: "julia v1.0 format",
|
||||
file: "testdata/julia_v1.0_format/Manifest.toml",
|
||||
want: juliaV10FormatPkgs,
|
||||
wantDeps: juliaV10FormatDeps,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -74,4 +74,19 @@ var (
|
||||
juliaV1_9ShadowedDepDeps = []ftypes.Dependency{
|
||||
{ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", DependsOn: []string{"f41f7b98-334e-11e9-1257-49272045fb24"}},
|
||||
}
|
||||
|
||||
juliaV10FormatPkgs = []ftypes.Package{
|
||||
{ID: "767738be-2f1f-45a9-b806-0234f3164144", Name: "Foo", Version: "unknown", Locations: []ftypes.Location{{StartLine: 1, EndLine: 5}}},
|
||||
{ID: "6f418443-bd2e-4783-b551-cdbac608adf2", Name: "Foo", Version: "unknown", Locations: []ftypes.Location{{StartLine: 7, EndLine: 10}}},
|
||||
{ID: "2a550a13-6bab-4a91-a4ee-dff34d6b99d0", Name: "Bar", Version: "unknown", Locations: []ftypes.Location{{StartLine: 12, EndLine: 14}}},
|
||||
{ID: "6801f525-dc68-44e8-a4e8-cabd286279e7", Name: "Baz", Version: "unknown", Locations: []ftypes.Location{{StartLine: 19, EndLine: 21}}},
|
||||
{ID: "b5ec9b9c-e354-47fd-b367-a348bdc8f909", Name: "Qux", Version: "unknown", Locations: []ftypes.Location{{StartLine: 26, EndLine: 28}}},
|
||||
}
|
||||
|
||||
juliaV10FormatDeps = []ftypes.Dependency{
|
||||
{ID: "767738be-2f1f-45a9-b806-0234f3164144", DependsOn: []string{"2a550a13-6bab-4a91-a4ee-dff34d6b99d0", "6801f525-dc68-44e8-a4e8-cabd286279e7", "b5ec9b9c-e354-47fd-b367-a348bdc8f909"}},
|
||||
{ID: "6f418443-bd2e-4783-b551-cdbac608adf2", DependsOn: []string{"b5ec9b9c-e354-47fd-b367-a348bdc8f909"}},
|
||||
{ID: "2a550a13-6bab-4a91-a4ee-dff34d6b99d0", DependsOn: []string{"6801f525-dc68-44e8-a4e8-cabd286279e7", "6f418443-bd2e-4783-b551-cdbac608adf2"}},
|
||||
{ID: "6801f525-dc68-44e8-a4e8-cabd286279e7", DependsOn: []string{"6f418443-bd2e-4783-b551-cdbac608adf2", "b5ec9b9c-e354-47fd-b367-a348bdc8f909"}},
|
||||
}
|
||||
)
|
||||
|
||||
28
pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Manifest.toml
vendored
Normal file
28
pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Manifest.toml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
[[Foo]]
|
||||
deps = ["Bar", "Baz", "Qux"]
|
||||
uuid = "767738be-2f1f-45a9-b806-0234f3164144"
|
||||
git-tree-sha1 = "7c626031568a5e432112a74009c3763f9b851e3e"
|
||||
path = "deps/Foo1"
|
||||
|
||||
[[Foo]]
|
||||
deps = ["Qux"]
|
||||
uuid = "6f418443-bd2e-4783-b551-cdbac608adf2"
|
||||
path = "deps/Foo2.jl"
|
||||
|
||||
[[Bar]]
|
||||
uuid = "2a550a13-6bab-4a91-a4ee-dff34d6b99d0"
|
||||
path = "deps/Bar"
|
||||
[Bar.deps]
|
||||
Baz = "6801f525-dc68-44e8-a4e8-cabd286279e7"
|
||||
Foo = "6f418443-bd2e-4783-b551-cdbac608adf2"
|
||||
|
||||
[[Baz]]
|
||||
uuid = "6801f525-dc68-44e8-a4e8-cabd286279e7"
|
||||
git-tree-sha1 = "efc7e24c53d6a328011975294a2c75fed2f9800a"
|
||||
[Baz.deps]
|
||||
Foo = "6f418443-bd2e-4783-b551-cdbac608adf2"
|
||||
Qux = "b5ec9b9c-e354-47fd-b367-a348bdc8f909"
|
||||
|
||||
[[Qux]]
|
||||
uuid = "b5ec9b9c-e354-47fd-b367-a348bdc8f909"
|
||||
path = "deps/Qux.jl"
|
||||
6
pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Project.toml
vendored
Normal file
6
pkg/dependency/parser/julia/manifest/testdata/julia_v1.0_format/Project.toml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
name = "TestProject"
|
||||
uuid = "84c38c17-0c6f-4d12-a694-d20b69c16777"
|
||||
|
||||
[deps]
|
||||
Foo = "767738be-2f1f-45a9-b806-0234f3164144"
|
||||
Bar = "2a550a13-6bab-4a91-a4ee-dff34d6b99d0"
|
||||
@@ -81,6 +81,9 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) {
|
||||
case ftypes.K8sUpstream:
|
||||
ecosystem = vulnerability.Kubernetes
|
||||
comparer = compare.GenericComparer{}
|
||||
case ftypes.Julia:
|
||||
log.Warn("Julia is supported for SBOM, not for vulnerability scanning")
|
||||
return Driver{}, false
|
||||
default:
|
||||
log.Warn("The library type is not supported for vulnerability scanning",
|
||||
log.String("type", string(libType)))
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/gradle"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/pom"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/julia/pkg"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/npm"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pkg"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pnpm"
|
||||
|
||||
@@ -94,6 +94,9 @@ const (
|
||||
// Dart
|
||||
TypePubSpecLock Type = "pubspec-lock"
|
||||
|
||||
// Julia
|
||||
TypeJulia Type = "julia"
|
||||
|
||||
// ============
|
||||
// Non-packaged
|
||||
// ============
|
||||
@@ -191,6 +194,7 @@ var (
|
||||
TypeSwift,
|
||||
TypePubSpecLock,
|
||||
TypeMixLock,
|
||||
TypeJulia,
|
||||
}
|
||||
|
||||
// TypeLockfiles has all lock file analyzers
|
||||
|
||||
190
pkg/fanal/analyzer/language/julia/pkg/pkg.go
Normal file
190
pkg/fanal/analyzer/language/julia/pkg/pkg.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package pkgjl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
julia "github.com/aquasecurity/trivy/pkg/dependency/parser/julia/manifest"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/utils/fsutils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
analyzer.RegisterPostAnalyzer(analyzer.TypeJulia, newJuliaAnalyzer)
|
||||
}
|
||||
|
||||
const version = 1
|
||||
|
||||
var requiredFiles = []string{
|
||||
types.JuliaManifest,
|
||||
types.JuliaProject,
|
||||
}
|
||||
|
||||
type juliaAnalyzer struct {
|
||||
lockParser language.Parser
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
Dependencies map[string]string `toml:"deps"`
|
||||
Extras map[string]string `toml:"extras"`
|
||||
}
|
||||
|
||||
func newJuliaAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) {
|
||||
return &juliaAnalyzer{
|
||||
lockParser: julia.NewParser(),
|
||||
logger: log.WithPrefix("julia"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a juliaAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) {
|
||||
var apps []types.Application
|
||||
|
||||
required := func(path string, d fs.DirEntry) bool {
|
||||
return filepath.Base(path) == types.JuliaManifest
|
||||
}
|
||||
|
||||
err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error {
|
||||
// Parse Manifest.toml
|
||||
app, err := a.parseJuliaManifest(path, r)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse error: %w", err)
|
||||
} else if app == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse Project.toml alongside Manifest.toml to identify the direct dependencies. This mutates `app`.
|
||||
if err = a.analyzeDependencies(input.FS, filepath.Dir(path), app); err != nil {
|
||||
a.logger.Warn("Unable to parse file to analyze dependencies",
|
||||
log.String("FILEPATH", filepath.Join(filepath.Dir(path), types.JuliaProject)), log.Err(err))
|
||||
}
|
||||
|
||||
sort.Sort(app.Packages)
|
||||
apps = append(apps, *app)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("julia walk error: %w", err)
|
||||
}
|
||||
|
||||
return &analyzer.AnalysisResult{
|
||||
Applications: apps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a juliaAnalyzer) Required(filePath string, _ os.FileInfo) bool {
|
||||
fileName := filepath.Base(filePath)
|
||||
return slices.Contains(requiredFiles, fileName)
|
||||
}
|
||||
|
||||
func (a juliaAnalyzer) Type() analyzer.Type {
|
||||
return analyzer.TypeJulia
|
||||
}
|
||||
|
||||
func (a juliaAnalyzer) Version() int {
|
||||
return version
|
||||
}
|
||||
|
||||
func (a juliaAnalyzer) parseJuliaManifest(path string, r io.Reader) (*types.Application, error) {
|
||||
return language.Parse(types.Julia, path, r, a.lockParser)
|
||||
}
|
||||
|
||||
func (a juliaAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.Application) error {
|
||||
deps, devDeps, err := a.getProjectDeps(fsys, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkgs := walkDependencies(deps, app.Packages, false)
|
||||
devPkgs := walkDependencies(devDeps, app.Packages, true)
|
||||
app.Packages = append(pkgs, devPkgs...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getProjectDeps parses project.toml and returns root and dev dependencies.
|
||||
func (a juliaAnalyzer) getProjectDeps(fsys fs.FS, dir string) (map[string]string, map[string]string, error) {
|
||||
projectPath := filepath.Join(dir, types.JuliaProject)
|
||||
project, err := parseJuliaProject(fsys, projectPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
a.logger.Debug("Julia project not found", log.String("PROJECT_PATH", projectPath))
|
||||
return nil, nil, nil
|
||||
} else if err != nil {
|
||||
return nil, nil, xerrors.Errorf("unable to parse %s: %w", projectPath, err)
|
||||
}
|
||||
return project.Dependencies, project.Extras, nil
|
||||
}
|
||||
|
||||
// Parses Project.toml
|
||||
func parseJuliaProject(fsys fs.FS, path string) (Project, error) {
|
||||
proj := Project{}
|
||||
f, err := fsys.Open(path)
|
||||
if err != nil {
|
||||
return proj, xerrors.Errorf("file open error: %w", err)
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
if _, err = toml.NewDecoder(f).Decode(&proj); err != nil {
|
||||
return proj, xerrors.Errorf("decode error: %w", err)
|
||||
}
|
||||
return proj, nil
|
||||
}
|
||||
|
||||
// Marks the given direct dependencies as direct, then marks those packages' dependencies as indirect.
|
||||
// Marks all encountered packages' Dev flag according to `dev`.
|
||||
// Modifies the packages in `allPackages`.
|
||||
func walkDependencies(directDeps map[string]string, allPackages types.Packages, dev bool) []types.Package {
|
||||
pkgsByID := lo.SliceToMap(allPackages, func(pkg types.Package) (string, types.Package) {
|
||||
return pkg.ID, pkg
|
||||
})
|
||||
|
||||
// Identify direct dependencies
|
||||
// Everything in `directDeps` is assumed to be direct
|
||||
visited := make(map[string]types.Package)
|
||||
for _, uuid := range directDeps {
|
||||
if pkg, ok := pkgsByID[uuid]; ok {
|
||||
pkg.Indirect = false
|
||||
pkg.Dev = dev
|
||||
visited[pkg.ID] = pkg
|
||||
}
|
||||
}
|
||||
|
||||
// Identify indirect dependencies
|
||||
for _, pkg := range visited {
|
||||
walkIndirectDependencies(pkg, pkgsByID, visited)
|
||||
}
|
||||
|
||||
return maps.Values(visited)
|
||||
}
|
||||
|
||||
// Marks all indirect dependencies as indirect. Starts from `rootPkg`. Visited deps are added to `visited`.
|
||||
func walkIndirectDependencies(rootPkg types.Package, allPkgIDs, visited map[string]types.Package) {
|
||||
for _, pkgID := range rootPkg.DependsOn {
|
||||
if _, ok := visited[pkgID]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
dep, ok := allPkgIDs[pkgID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
dep.Indirect = true
|
||||
dep.Dev = rootPkg.Dev
|
||||
visited[dep.ID] = dep
|
||||
walkIndirectDependencies(dep, allPkgIDs, visited)
|
||||
}
|
||||
}
|
||||
209
pkg/fanal/analyzer/language/julia/pkg/pkg_test.go
Normal file
209
pkg/fanal/analyzer/language/julia/pkg/pkg_test.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package pkgjl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_juliaAnalyzer_Analyze(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
want *analyzer.AnalysisResult
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
dir: "testdata/happy",
|
||||
want: &analyzer.AnalysisResult{
|
||||
Applications: []types.Application{
|
||||
{
|
||||
Type: types.Julia,
|
||||
FilePath: "Manifest.toml",
|
||||
Packages: types.Packages{
|
||||
{
|
||||
ID: "ade2ca70-3891-5945-98fb-dc099432e06a",
|
||||
Name: "Dates",
|
||||
Version: "1.9.0",
|
||||
Indirect: false,
|
||||
Locations: []types.Location{{StartLine: 7, EndLine: 9}},
|
||||
DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"},
|
||||
},
|
||||
{
|
||||
ID: "d9a60922-03b4-4a1b-81be-b8d05b827236",
|
||||
Name: "DevDep",
|
||||
Version: "1.0.0",
|
||||
Indirect: false,
|
||||
Dev: true,
|
||||
Locations: []types.Location{{StartLine: 65, EndLine: 68}},
|
||||
DependsOn: []string{"b637660b-5035-4894-8335-b3805a4b50d8"},
|
||||
},
|
||||
{
|
||||
ID: "b637660b-5035-4894-8335-b3805a4b50d8",
|
||||
Name: "IndirectDevDep",
|
||||
Version: "2.0.0",
|
||||
Indirect: true,
|
||||
Dev: true,
|
||||
Locations: []types.Location{{StartLine: 70, EndLine: 72}},
|
||||
},
|
||||
{
|
||||
ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6",
|
||||
Name: "JSON",
|
||||
Version: "0.21.4",
|
||||
Indirect: false,
|
||||
Locations: []types.Location{{StartLine: 11, EndLine: 15}},
|
||||
DependsOn: []string{
|
||||
"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5",
|
||||
"69de0a69-1ddd-5017-9359-2bf0b02dc9f0",
|
||||
"a63ad114-7e13-5084-954f-fe012c677804",
|
||||
"ade2ca70-3891-5945-98fb-dc099432e06a",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "a63ad114-7e13-5084-954f-fe012c677804",
|
||||
Name: "Mmap",
|
||||
Version: "1.9.0",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 17, EndLine: 18}},
|
||||
},
|
||||
{
|
||||
ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0",
|
||||
Name: "Parsers",
|
||||
Version: "2.5.10",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 20, EndLine: 24}},
|
||||
DependsOn: []string{
|
||||
"ade2ca70-3891-5945-98fb-dc099432e06a",
|
||||
"aea7be01-6a6a-4083-8856-8a6e6704d82a",
|
||||
"cf7118a7-6976-5b1a-9a39-7adc72f591a4",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a",
|
||||
Name: "PrecompileTools",
|
||||
Version: "1.1.1",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 26, EndLine: 30}},
|
||||
DependsOn: []string{"21216c6a-2e73-6563-6e65-726566657250"},
|
||||
},
|
||||
{
|
||||
ID: "21216c6a-2e73-6563-6e65-726566657250",
|
||||
Name: "Preferences",
|
||||
Version: "1.4.0",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 32, EndLine: 36}},
|
||||
DependsOn: []string{"fa267f1f-6049-4f14-aa54-33bafae1ed76"},
|
||||
},
|
||||
{
|
||||
ID: "de0858da-6303-5e67-8744-51eddeeeb8d7",
|
||||
Name: "Printf",
|
||||
Version: "1.9.0",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 38, EndLine: 40}},
|
||||
DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"},
|
||||
},
|
||||
{
|
||||
ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c",
|
||||
Name: "Random",
|
||||
Version: "1.9.0",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 42, EndLine: 44}},
|
||||
DependsOn: []string{"9e88b42a-f829-5b0c-bbe9-9e923198166b", "ea8e919c-243c-51af-8825-aaa63cd721ce"},
|
||||
},
|
||||
{
|
||||
ID: "ea8e919c-243c-51af-8825-aaa63cd721ce",
|
||||
Name: "SHA",
|
||||
Version: "0.7.0",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 46, EndLine: 48}},
|
||||
},
|
||||
{
|
||||
ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b",
|
||||
Name: "Serialization",
|
||||
Version: "1.9.0",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 50, EndLine: 51}},
|
||||
},
|
||||
{
|
||||
ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76",
|
||||
Name: "TOML",
|
||||
Version: "1.0.3",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 53, EndLine: 56}},
|
||||
DependsOn: []string{"ade2ca70-3891-5945-98fb-dc099432e06a"},
|
||||
},
|
||||
{
|
||||
ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4",
|
||||
Name: "UUIDs",
|
||||
Version: "1.9.0",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 58, EndLine: 60}},
|
||||
DependsOn: []string{"9a3f8284-a2c9-5f02-9a11-845980a1fd5c", "ea8e919c-243c-51af-8825-aaa63cd721ce"},
|
||||
},
|
||||
{
|
||||
ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5",
|
||||
Name: "Unicode",
|
||||
Version: "1.9.0",
|
||||
Indirect: true,
|
||||
Locations: []types.Location{{StartLine: 62, EndLine: 63}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no_deps_v1.6",
|
||||
dir: "testdata/no_deps_v1.6",
|
||||
want: &analyzer.AnalysisResult{},
|
||||
},
|
||||
{
|
||||
name: "dep_ext_v1.9",
|
||||
dir: "testdata/dep_ext_v1.9",
|
||||
want: &analyzer.AnalysisResult{
|
||||
Applications: []types.Application{
|
||||
{
|
||||
Type: types.Julia,
|
||||
FilePath: "Manifest.toml",
|
||||
Packages: types.Packages{
|
||||
{
|
||||
ID: "621f4979-c628-5d54-868e-fcf4e3e8185c",
|
||||
Name: "AbstractFFTs",
|
||||
Version: "1.3.1",
|
||||
Indirect: false,
|
||||
Locations: []types.Location{{StartLine: 7, EndLine: 10}},
|
||||
DependsOn: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no_manifest",
|
||||
dir: "testdata/no_manifest",
|
||||
want: &analyzer.AnalysisResult{
|
||||
Applications: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a, err := newJuliaAnalyzer(analyzer.AnalyzerOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{
|
||||
FS: os.DirFS(tt.dir),
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
16
pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml
vendored
Normal file
16
pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Manifest.toml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# This file is machine-generated - editing it directly is not advised
|
||||
|
||||
julia_version = "1.9.0"
|
||||
manifest_format = "2.0"
|
||||
project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc"
|
||||
|
||||
[[deps.AbstractFFTs]]
|
||||
git-tree-sha1 = "16b6dbc4cf7caee4e1e75c49485ec67b667098a0"
|
||||
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
|
||||
version = "1.3.1"
|
||||
|
||||
[deps.AbstractFFTs.extensions]
|
||||
AbstractFFTsChainRulesCoreExt = "ChainRulesCore"
|
||||
|
||||
[deps.AbstractFFTs.weakdeps]
|
||||
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
|
||||
9
pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml
vendored
Normal file
9
pkg/fanal/analyzer/language/julia/pkg/testdata/dep_ext_v1.9/Project.toml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
name = "packageName"
|
||||
uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773"
|
||||
version = "0.1.0"
|
||||
|
||||
[deps]
|
||||
AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c"
|
||||
|
||||
[compat]
|
||||
AbstractFFTs = "1.3"
|
||||
72
pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml
vendored
Normal file
72
pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Manifest.toml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# This file is machine-generated - editing it directly is not advised
|
||||
|
||||
julia_version = "1.9.0"
|
||||
manifest_format = "2.0"
|
||||
project_hash = "f65b9de676a27ce78ee011db6d477b3d44d1a7c5"
|
||||
|
||||
[[deps.Dates]]
|
||||
deps = ["Printf"]
|
||||
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
||||
|
||||
[[deps.JSON]]
|
||||
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
|
||||
git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a"
|
||||
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||
version = "0.21.4"
|
||||
|
||||
[[deps.Mmap]]
|
||||
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
|
||||
|
||||
[[deps.Parsers]]
|
||||
deps = ["Dates", "PrecompileTools", "UUIDs"]
|
||||
git-tree-sha1 = "a5aef8d4a6e8d81f171b2bd4be5265b01384c74c"
|
||||
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
|
||||
version = "2.5.10"
|
||||
|
||||
[[deps.PrecompileTools]]
|
||||
deps = ["Preferences"]
|
||||
git-tree-sha1 = "259e206946c293698122f63e2b513a7c99a244e8"
|
||||
uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
|
||||
version = "1.1.1"
|
||||
|
||||
[[deps.Preferences]]
|
||||
deps = ["TOML"]
|
||||
git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1"
|
||||
uuid = "21216c6a-2e73-6563-6e65-726566657250"
|
||||
version = "1.4.0"
|
||||
|
||||
[[deps.Printf]]
|
||||
deps = ["Unicode"]
|
||||
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||
|
||||
[[deps.Random]]
|
||||
deps = ["SHA", "Serialization"]
|
||||
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
|
||||
[[deps.SHA]]
|
||||
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
|
||||
version = "0.7.0"
|
||||
|
||||
[[deps.Serialization]]
|
||||
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
|
||||
|
||||
[[deps.TOML]]
|
||||
deps = ["Dates"]
|
||||
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
|
||||
version = "1.0.3"
|
||||
|
||||
[[deps.UUIDs]]
|
||||
deps = ["Random", "SHA"]
|
||||
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
|
||||
|
||||
[[deps.Unicode]]
|
||||
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
|
||||
|
||||
[[deps.DevDep]]
|
||||
deps = ["IndirectDevDep"]
|
||||
uuid = "d9a60922-03b4-4a1b-81be-b8d05b827236"
|
||||
version = "1.0.0"
|
||||
|
||||
[[deps.IndirectDevDep]]
|
||||
uuid = "b637660b-5035-4894-8335-b3805a4b50d8"
|
||||
version = "2.0.0"
|
||||
17
pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml
vendored
Normal file
17
pkg/fanal/analyzer/language/julia/pkg/testdata/happy/Project.toml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name = "packageName"
|
||||
uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773"
|
||||
version = "0.1.0"
|
||||
|
||||
[deps]
|
||||
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
||||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||
|
||||
[compat]
|
||||
JSON = "0.21"
|
||||
julia = "1.8"
|
||||
|
||||
[extras]
|
||||
DevDep = "d9a60922-03b4-4a1b-81be-b8d05b827236"
|
||||
|
||||
[targets]
|
||||
test = ["DevDep"]
|
||||
2
pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml
vendored
Normal file
2
pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Manifest.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is machine-generated - editing it directly is not advised
|
||||
|
||||
3
pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml
vendored
Normal file
3
pkg/fanal/analyzer/language/julia/pkg/testdata/no_deps_v1.6/Project.toml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
name = "packageName"
|
||||
uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773"
|
||||
version = "0.1.0"
|
||||
2
pkg/fanal/analyzer/language/julia/pkg/testdata/no_manifest/Project.toml
vendored
Normal file
2
pkg/fanal/analyzer/language/julia/pkg/testdata/no_manifest/Project.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[deps]
|
||||
A = "ead4f63c-334e-11e9-00e6-e7f0a5f21b62"
|
||||
16
pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml
vendored
Normal file
16
pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Manifest.toml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# This file is machine-generated - editing it directly is not advised
|
||||
|
||||
julia_version = "1.9.0"
|
||||
manifest_format = "2.0"
|
||||
project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc"
|
||||
|
||||
[[deps.A]]
|
||||
uuid = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60"
|
||||
|
||||
[deps.A.deps]
|
||||
B = "f41f7b98-334e-11e9-1257-49272045fb24"
|
||||
|
||||
[[deps.B]]
|
||||
uuid = "f41f7b98-334e-11e9-1257-49272045fb24"
|
||||
[[deps.B]]
|
||||
uuid = "edca9bc6-334e-11e9-3554-9595dbb4349c"
|
||||
7
pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml
vendored
Normal file
7
pkg/fanal/analyzer/language/julia/pkg/testdata/shadowed_dep_v1.9/Project.toml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
name = "packageName"
|
||||
uuid = "1c653b0a-0b5a-4cff-b25a-92f0db012773"
|
||||
version = "0.1.0"
|
||||
|
||||
[deps]
|
||||
A = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60"
|
||||
B = "edca9bc6-334e-11e9-3554-9595dbb4349c"
|
||||
@@ -73,6 +73,7 @@ const (
|
||||
Pub LangType = "pub"
|
||||
Hex LangType = "hex"
|
||||
Bitnami LangType = "bitnami"
|
||||
Julia LangType = "julia"
|
||||
|
||||
K8sUpstream LangType = "kubernetes"
|
||||
EKS LangType = "eks" // Amazon Elastic Kubernetes Service
|
||||
@@ -143,4 +144,7 @@ const (
|
||||
|
||||
CondaEnvYaml = "environment.yaml"
|
||||
CondaEnvYml = "environment.yml"
|
||||
|
||||
JuliaProject = "Project.toml"
|
||||
JuliaManifest = "Manifest.toml"
|
||||
)
|
||||
|
||||
@@ -109,6 +109,10 @@ func New(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (*Pac
|
||||
return nil, nil
|
||||
}
|
||||
return (*PackageURL)(purl), nil
|
||||
case packageurl.TypeJulia:
|
||||
var qs packageurl.Qualifiers
|
||||
namespace, name, qs = parseJulia(name, pkg.ID) // for Julia, the ID is set to the package UUID
|
||||
qualifiers = append(qualifiers, qs...)
|
||||
}
|
||||
|
||||
return (*PackageURL)(packageurl.NewPackageURL(ptype, namespace, name, ver, qualifiers, subpath)), nil
|
||||
@@ -424,6 +428,18 @@ func parseNpm(pkgName string) (string, string) {
|
||||
return parsePkgName(name)
|
||||
}
|
||||
|
||||
// ref. https://github.com/package-url/purl-spec/blob/7759d1cf81629267742eeeb0cdfccf5ebd624cc5/PURL-TYPES.rst#julia
|
||||
func parseJulia(pkgName, pkgUUID string) (string, string, packageurl.Qualifiers) {
|
||||
namespace, name := parsePkgName(pkgName)
|
||||
qualifiers := packageurl.Qualifiers{
|
||||
{
|
||||
Key: "uuid",
|
||||
Value: pkgUUID,
|
||||
},
|
||||
}
|
||||
return namespace, name, qualifiers
|
||||
}
|
||||
|
||||
func purlType(t ftypes.TargetType) string {
|
||||
switch t {
|
||||
case ftypes.Jar, ftypes.Pom, ftypes.Gradle:
|
||||
@@ -462,6 +478,8 @@ func purlType(t ftypes.TargetType) string {
|
||||
return packageurl.TypeRPM
|
||||
case TypeOCI:
|
||||
return packageurl.TypeOCI
|
||||
case ftypes.Julia:
|
||||
return packageurl.TypeJulia
|
||||
}
|
||||
return string(t)
|
||||
}
|
||||
|
||||
@@ -406,6 +406,26 @@ func TestNewPackageURL(t *testing.T) {
|
||||
},
|
||||
wantErr: "failed to parse digest",
|
||||
},
|
||||
{
|
||||
name: "julia project",
|
||||
typ: ftypes.Julia,
|
||||
pkg: ftypes.Package{
|
||||
ID: "ade2ca70-3891-5945-98fb-dc099432e06a",
|
||||
Name: "Dates",
|
||||
Version: "1.9.0",
|
||||
},
|
||||
want: &purl.PackageURL{
|
||||
Type: packageurl.TypeJulia,
|
||||
Name: "Dates",
|
||||
Version: "1.9.0",
|
||||
Qualifiers: packageurl.Qualifiers{
|
||||
{
|
||||
Key: "uuid",
|
||||
Value: "ade2ca70-3891-5945-98fb-dc099432e06a",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
Reference in New Issue
Block a user