From 03e89c83ccb3e2219463706111055281a913aa1c Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 18 Jun 2024 14:22:29 +0600 Subject: [PATCH 01/16] feat(parser): add `prefix` as FilePath --- pkg/dependency/parser/conda/environment/parse.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/dependency/parser/conda/environment/parse.go b/pkg/dependency/parser/conda/environment/parse.go index be04d828b40a..25be26b6a3bb 100644 --- a/pkg/dependency/parser/conda/environment/parse.go +++ b/pkg/dependency/parser/conda/environment/parse.go @@ -16,6 +16,7 @@ import ( type environment struct { Entries []Entry `yaml:"dependencies"` + Prefix string `yaml:"prefix"` } type Entry struct { @@ -48,7 +49,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc var pkgs ftypes.Packages for _, entry := range env.Entries { for _, dep := range entry.Dependencies { - pkg := p.toPackage(dep) + pkg := p.toPackage(dep, env.Prefix) // Skip empty pkgs if pkg.Name == "" { continue @@ -61,7 +62,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc return pkgs, nil, nil } -func (p *Parser) toPackage(dep Dependency) ftypes.Package { +func (p *Parser) toPackage(dep Dependency, prefix string) ftypes.Package { name, ver := p.parseDependency(dep.Value) if ver == "" { p.once.Do(func() { @@ -77,6 +78,7 @@ func (p *Parser) toPackage(dep Dependency) ftypes.Package { EndLine: dep.Line, }, }, + FilePath: prefix, } } From 9d08999a1ba2fce09d60b301900e911f1c9d1426 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 18 Jun 2024 14:26:37 +0600 Subject: [PATCH 02/16] feat(fanal): parse packageJSON files to get licenses --- .../language/conda/environment/environment.go | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index ee4dfbd7de88..76e4757d93e3 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -2,22 +2,26 @@ package environment import ( "context" + "fmt" "os" "path/filepath" + "github.com/bmatcuk/doublestar/v4" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/environment" + "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/meta" "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" ) func init() { analyzer.RegisterAnalyzer(&environmentAnalyzer{}) } -const version = 1 +const version = 2 type environmentAnalyzer struct{} @@ -26,8 +30,53 @@ func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisI if err != nil { return nil, xerrors.Errorf("unable to parse environment.yaml: %w", err) } + + if res != nil && len(res.Applications) > 0 { + // For `environment.yaml` Applications always contains only 1 Application + for i, pkg := range res.Applications[0].Packages { + licenses, err := findLicenseFromEnvDir(pkg) + if err != nil { + log.WithPrefix("conda").Debug("License didn't found", log.String("pkgName", pkg.Name), log.String("version", pkg.Version), log.Err(err)) + } + pkg.Licenses = licenses + pkg.FilePath = "" // remove path to env dir + res.Applications[0].Packages[i] = pkg + } + + } + return res, nil } + +func findLicenseFromEnvDir(pkg types.Package) ([]string, error) { + condaMetaDir := filepath.Join(pkg.FilePath, "conda-meta") + entries, err := os.ReadDir(condaMetaDir) + if err != nil { + return nil, err + } + for _, entry := range entries { + if entry.IsDir() { + continue + } + + pattern := fmt.Sprintf("%s-%s-*.json", pkg.Name, pkg.Version) + if matched, _ := doublestar.Match(pattern, entry.Name()); matched { + file, err := os.Open(filepath.Join(condaMetaDir, entry.Name())) + if err != nil { + return nil, err + } + packageJson, _, err := meta.NewParser().Parse(file) + if err != nil { + return nil, err + } + // packageJson always contain only 1 element + // cf. https://github.com/aquasecurity/trivy/blob/c3192f061d7e84eaf38df8df7c879dc00b4ca137/pkg/dependency/parser/conda/meta/parse.go#L39-L45 + return packageJson[0].Licenses, err + } + } + return nil, xerrors.Errorf("meta file didn't find") +} + func (a environmentAnalyzer) Required(filePath string, _ os.FileInfo) bool { return filepath.Base(filePath) == types.CondaEnvYml || filepath.Base(filePath) == types.CondaEnvYaml } From e4e5ac8238cc77a8935af28c7619cc81d4676af1 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 18 Jun 2024 14:30:49 +0600 Subject: [PATCH 03/16] test: update tests --- .../conda/environment/environment_test.go | 59 +++++++++++++++++++ .../conda-meta/_libgcc_mutex-0.1-main.json | 5 ++ .../conda-meta/_openmp_mutex-5.1-1_gnu.json | 5 ++ .../conda-meta/blas-1.0-openblas.json | 5 ++ .../conda-meta/bzip2-1.0.8-h5eee18b_6.json | 5 ++ .../testdata/environment-with-licenses.yaml | 9 +++ 6 files changed, 88 insertions(+) create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_libgcc_mutex-0.1-main.json create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_openmp_mutex-5.1-1_gnu.json create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/blas-1.0-openblas.json create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/bzip2-1.0.8-h5eee18b_6.json create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/environment-with-licenses.yaml diff --git a/pkg/fanal/analyzer/language/conda/environment/environment_test.go b/pkg/fanal/analyzer/language/conda/environment/environment_test.go index 044585f14683..a312ceb1198e 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment_test.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment_test.go @@ -72,6 +72,65 @@ func Test_environmentAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "happy path with licenses", + inputFile: "testdata/environment-with-licenses.yaml", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.CondaEnv, + FilePath: "testdata/environment-with-licenses.yaml", + Packages: types.Packages{ + { + Name: "_libgcc_mutex", + Locations: []types.Location{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + Name: "_openmp_mutex", + Version: "5.1", + Locations: []types.Location{ + { + StartLine: 6, + EndLine: 6, + }, + }, + Licenses: []string{ + "BSD-3-Clause", + }, + }, + { + Name: "blas", + Version: "1.0", + Locations: []types.Location{ + { + StartLine: 7, + EndLine: 7, + }, + }, + }, + { + Name: "bzip2", + Version: "1.0.8", + Locations: []types.Location{ + { + StartLine: 8, + EndLine: 8, + }, + }, + Licenses: []string{ + "bzip2-1.0.8", + }, + }, + }, + }, + }, + }, + }, { name: "invalid", inputFile: "testdata/invalid.yaml", diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_libgcc_mutex-0.1-main.json b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_libgcc_mutex-0.1-main.json new file mode 100644 index 000000000000..bfc2daa841d5 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_libgcc_mutex-0.1-main.json @@ -0,0 +1,5 @@ +{ + "license": "MIT", + "name": "_libgcc_mutex", + "version": "0.1" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_openmp_mutex-5.1-1_gnu.json b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_openmp_mutex-5.1-1_gnu.json new file mode 100644 index 000000000000..a61f193ee669 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/_openmp_mutex-5.1-1_gnu.json @@ -0,0 +1,5 @@ +{ + "license": "BSD-3-Clause", + "name": "_openmp_mutex", + "version": "5.1" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/blas-1.0-openblas.json b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/blas-1.0-openblas.json new file mode 100644 index 000000000000..9a14025cd5b2 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/blas-1.0-openblas.json @@ -0,0 +1,5 @@ +{ + "license": "", + "name": "blas", + "version": "1.0" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/bzip2-1.0.8-h5eee18b_6.json b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/bzip2-1.0.8-h5eee18b_6.json new file mode 100644 index 000000000000..413243843b73 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/conda-meta/bzip2-1.0.8-h5eee18b_6.json @@ -0,0 +1,5 @@ +{ + "license": "bzip2-1.0.8", + "name": "bzip2", + "version": "1.0.8" +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/environment-with-licenses.yaml b/pkg/fanal/analyzer/language/conda/environment/testdata/environment-with-licenses.yaml new file mode 100644 index 000000000000..a6db3a8a9e7c --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/environment-with-licenses.yaml @@ -0,0 +1,9 @@ +name: test-env +channels: + - defaults +dependencies: + - _libgcc_mutex + - _openmp_mutex=5.1 + - blas=1.0=openblas + - bzip2=1.0.8=h998d150_5 +prefix: testdata From d033e32e68fe79c3eeec902c44206be094fc7671 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 18 Jun 2024 14:30:56 +0600 Subject: [PATCH 04/16] refactor --- .../analyzer/language/conda/environment/environment.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index 76e4757d93e3..3a08faffd336 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -52,7 +52,7 @@ func findLicenseFromEnvDir(pkg types.Package) ([]string, error) { condaMetaDir := filepath.Join(pkg.FilePath, "conda-meta") entries, err := os.ReadDir(condaMetaDir) if err != nil { - return nil, err + return nil, xerrors.Errorf("unable to read conda-meta dir: %w", err) } for _, entry := range entries { if entry.IsDir() { @@ -63,15 +63,15 @@ func findLicenseFromEnvDir(pkg types.Package) ([]string, error) { if matched, _ := doublestar.Match(pattern, entry.Name()); matched { file, err := os.Open(filepath.Join(condaMetaDir, entry.Name())) if err != nil { - return nil, err + return nil, xerrors.Errorf("unable to open packageJSON file: %w", err) } packageJson, _, err := meta.NewParser().Parse(file) if err != nil { - return nil, err + return nil, xerrors.Errorf("unable to parse packageJSON file: %w", err) } // packageJson always contain only 1 element // cf. https://github.com/aquasecurity/trivy/blob/c3192f061d7e84eaf38df8df7c879dc00b4ca137/pkg/dependency/parser/conda/meta/parse.go#L39-L45 - return packageJson[0].Licenses, err + return packageJson[0].Licenses, nil } } return nil, xerrors.Errorf("meta file didn't find") From 10518c9deece450943e319c6276172c40a9838af Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 19 Jun 2024 13:46:44 +0600 Subject: [PATCH 05/16] test(parser): update test --- .../parser/conda/environment/parse_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg/dependency/parser/conda/environment/parse_test.go b/pkg/dependency/parser/conda/environment/parse_test.go index 6283e8e135d6..b019aa1c7e08 100644 --- a/pkg/dependency/parser/conda/environment/parse_test.go +++ b/pkg/dependency/parser/conda/environment/parse_test.go @@ -30,6 +30,7 @@ func TestParse(t *testing.T) { EndLine: 6, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "asgiref", @@ -40,6 +41,7 @@ func TestParse(t *testing.T) { EndLine: 21, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "blas", @@ -50,6 +52,7 @@ func TestParse(t *testing.T) { EndLine: 5, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "bzip2", @@ -60,6 +63,7 @@ func TestParse(t *testing.T) { EndLine: 19, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "ca-certificates", @@ -70,6 +74,7 @@ func TestParse(t *testing.T) { EndLine: 7, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "django", @@ -80,6 +85,7 @@ func TestParse(t *testing.T) { EndLine: 22, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "ld_impl_linux-aarch64", @@ -89,6 +95,7 @@ func TestParse(t *testing.T) { EndLine: 8, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libblas", @@ -98,6 +105,7 @@ func TestParse(t *testing.T) { EndLine: 9, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libcblas", @@ -107,6 +115,7 @@ func TestParse(t *testing.T) { EndLine: 10, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libexpat", @@ -117,6 +126,7 @@ func TestParse(t *testing.T) { EndLine: 11, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libffi", @@ -127,6 +137,7 @@ func TestParse(t *testing.T) { EndLine: 12, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libgcc-ng", @@ -136,6 +147,7 @@ func TestParse(t *testing.T) { EndLine: 13, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libgfortran-ng", @@ -145,6 +157,7 @@ func TestParse(t *testing.T) { EndLine: 14, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libgfortran5", @@ -154,6 +167,7 @@ func TestParse(t *testing.T) { EndLine: 15, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libgomp", @@ -164,6 +178,7 @@ func TestParse(t *testing.T) { EndLine: 16, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "liblapack", @@ -173,6 +188,7 @@ func TestParse(t *testing.T) { EndLine: 17, }, }, + FilePath: "/opt/conda/envs/test-env", }, { Name: "libnsl", @@ -183,6 +199,7 @@ func TestParse(t *testing.T) { EndLine: 18, }, }, + FilePath: "/opt/conda/envs/test-env", }, }, }, From ab098b4dfb7222b5280d2569bdefe80b8270bf6e Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 19 Jun 2024 13:57:55 +0600 Subject: [PATCH 06/16] refactor: show warning once --- .../language/conda/environment/environment.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index 3a08faffd336..80df82c93fb9 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "sync" "github.com/bmatcuk/doublestar/v4" "golang.org/x/xerrors" @@ -32,14 +33,18 @@ func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisI } if res != nil && len(res.Applications) > 0 { + once := sync.Once{} // For `environment.yaml` Applications always contains only 1 Application for i, pkg := range res.Applications[0].Packages { licenses, err := findLicenseFromEnvDir(pkg) if err != nil { - log.WithPrefix("conda").Debug("License didn't found", log.String("pkgName", pkg.Name), log.String("version", pkg.Version), log.Err(err)) + // Show warning once per file + once.Do(func() { + log.WithPrefix("conda").Debug("License didn't found", log.String("file", input.FilePath), log.Err(err)) + }) } pkg.Licenses = licenses - pkg.FilePath = "" // remove path to env dir + pkg.FilePath = "" // remove `prefix` from FilePath res.Applications[0].Packages[i] = pkg } @@ -60,7 +65,11 @@ func findLicenseFromEnvDir(pkg types.Package) ([]string, error) { } pattern := fmt.Sprintf("%s-%s-*.json", pkg.Name, pkg.Version) - if matched, _ := doublestar.Match(pattern, entry.Name()); matched { + matched, err := doublestar.Match(pattern, entry.Name()) + if err != nil { + return nil, xerrors.Errorf("incorrect packageJSON file pattern: %w", err) + } + if matched { file, err := os.Open(filepath.Join(condaMetaDir, entry.Name())) if err != nil { return nil, xerrors.Errorf("unable to open packageJSON file: %w", err) From 6ffd25bbda9e409aeabb121a6be949754e411b3f Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 19 Jun 2024 15:01:17 +0600 Subject: [PATCH 07/16] refactor: update log message --- .../language/conda/environment/environment.go | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index 80df82c93fb9..e4d31189f11e 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -36,14 +36,18 @@ func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisI once := sync.Once{} // For `environment.yaml` Applications always contains only 1 Application for i, pkg := range res.Applications[0].Packages { - licenses, err := findLicenseFromEnvDir(pkg) - if err != nil { - // Show warning once per file - once.Do(func() { - log.WithPrefix("conda").Debug("License didn't found", log.String("file", input.FilePath), log.Err(err)) - }) + // Skip packages without a version, because in this case we will not be able to get the correct file name. + if pkg.Version != "" { + licenses, err := findLicenseFromEnvDir(pkg) + if err != nil { + // Show log once per file + once.Do(func() { + log.WithPrefix("conda").Debug("License not found. For more information, see https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#environmentyml2.”", + log.String("file", input.FilePath), log.String("pkg", pkg.Name), log.Err(err)) + }) + } + pkg.Licenses = licenses } - pkg.Licenses = licenses pkg.FilePath = "" // remove `prefix` from FilePath res.Applications[0].Packages[i] = pkg } @@ -54,6 +58,9 @@ func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisI } func findLicenseFromEnvDir(pkg types.Package) ([]string, error) { + if pkg.FilePath == "" { + return nil, xerrors.Errorf("`prefix` field doesn't exist") + } condaMetaDir := filepath.Join(pkg.FilePath, "conda-meta") entries, err := os.ReadDir(condaMetaDir) if err != nil { From 16ea5793a36095362702158d02ab75393a3f1a22 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 19 Jun 2024 15:11:15 +0600 Subject: [PATCH 08/16] docs: update coverage page --- docs/docs/coverage/os/conda.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/docs/coverage/os/conda.md b/docs/docs/coverage/os/conda.md index 79a49194fd66..69f8eeceb19d 100644 --- a/docs/docs/coverage/os/conda.md +++ b/docs/docs/coverage/os/conda.md @@ -6,7 +6,7 @@ Trivy supports the following scanners for Conda packages. |:-------------:|:---------:| | SBOM | ✓ | | Vulnerability | - | -| License | ✓[^1] | +| License | ✓ | ## SBOM @@ -16,21 +16,24 @@ Trivy detects packages that have been installed with `Conda`. ### `.json` Trivy parses `/envs//conda-meta/.json` files to find the version and license for the dependencies installed in your env. -### `environment.yml`[^2] -Trivy supports parsing [environment.yml][environment.yml][^2] files to find dependency list. +### `environment.yml`[^1] +Trivy supports parsing [environment.yml][environment.yml][^1] files to find dependency list. -!!! note - License detection is currently not supported. - -`environment.yml`[^2] files supports [version range][env-version-range]. We can't be sure about versions for these dependencies. -Therefore, you need to use `conda env export` command to get dependency list in `Conda` default format before scanning `environment.yml`[^2] file. +`environment.yml`[^1] files supports [version range][env-version-range]. We can't be sure about versions for these dependencies. +Therefore, you need to use `conda env export` command to get dependency list in `Conda` default format before scanning `environment.yml`[^1] file. !!! note For dependencies in a non-Conda format, Trivy doesn't include a version of them. +#### licenses +Trivy parses `conda-meta/.json` files at the [prefix] path. +To correctly define licenses, make sure your `environment.yml`[^1] contains `prefix` field and `prefix` directory contains `package.json` files. + +!!! note + To get correct `environment.yml`[^1] file and fill `prefix` directory - use `conda env export` command. -[^1]: License detection is only supported for `.json` files -[^2]: Trivy supports both `yaml` and `yml` extensions. +[^1]: Trivy supports both `yaml` and `yml` extensions. [environment.yml]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#sharing-an-environment [env-version-range]: https://docs.conda.io/projects/conda-build/en/latest/resources/package-spec.html#examples-of-package-specs +[prefix]: https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#specifying-a-location-for-an-environment From e3539b6f748ffd35857c8db50936520ae2380dd2 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 19 Jun 2024 15:12:21 +0600 Subject: [PATCH 09/16] refactor: update link to license page --- pkg/fanal/analyzer/language/conda/environment/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index e4d31189f11e..cd0465412ade 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -42,7 +42,7 @@ func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisI if err != nil { // Show log once per file once.Do(func() { - log.WithPrefix("conda").Debug("License not found. For more information, see https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#environmentyml2.”", + log.WithPrefix("conda").Debug("License not found. For more information, see https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#licenses", log.String("file", input.FilePath), log.String("pkg", pkg.Name), log.Err(err)) }) } From d75bce22db40b17424f578e70edaef6e73935a1a Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Thu, 27 Jun 2024 09:34:39 +0600 Subject: [PATCH 10/16] refactor: use early return --- .../language/conda/environment/environment.go | 37 ++++++++++--------- .../conda/environment/environment_test.go | 4 ++ .../conda/environment/testdata/empty.yaml | 5 +++ 3 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 pkg/fanal/analyzer/language/conda/environment/testdata/empty.yaml diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index cd0465412ade..e20d20b10fe5 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -32,25 +32,28 @@ func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisI return nil, xerrors.Errorf("unable to parse environment.yaml: %w", err) } - if res != nil && len(res.Applications) > 0 { - once := sync.Once{} - // For `environment.yaml` Applications always contains only 1 Application - for i, pkg := range res.Applications[0].Packages { - // Skip packages without a version, because in this case we will not be able to get the correct file name. - if pkg.Version != "" { - licenses, err := findLicenseFromEnvDir(pkg) - if err != nil { - // Show log once per file - once.Do(func() { - log.WithPrefix("conda").Debug("License not found. For more information, see https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#licenses", - log.String("file", input.FilePath), log.String("pkg", pkg.Name), log.Err(err)) - }) - } - pkg.Licenses = licenses + if res == nil { + return nil, nil + } + + once := sync.Once{} + // res always contains only 1 Application + // cf. https://github.com/aquasecurity/trivy/blob/0ccdbfbb6598a52de7cda603ab22e794f710e86c/pkg/fanal/analyzer/language/analyze.go#L32 + for i, pkg := range res.Applications[0].Packages { + // Skip packages without a version, because in this case we will not be able to get the correct file name. + if pkg.Version != "" { + licenses, err := findLicenseFromEnvDir(pkg) + if err != nil { + // Show log once per file + once.Do(func() { + log.WithPrefix("conda").Debug("License not found. For more information, see https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#licenses", + log.String("file", input.FilePath), log.String("pkg", pkg.Name), log.Err(err)) + }) } - pkg.FilePath = "" // remove `prefix` from FilePath - res.Applications[0].Packages[i] = pkg + pkg.Licenses = licenses } + pkg.FilePath = "" // remove `prefix` from FilePath + res.Applications[0].Packages[i] = pkg } diff --git a/pkg/fanal/analyzer/language/conda/environment/environment_test.go b/pkg/fanal/analyzer/language/conda/environment/environment_test.go index a312ceb1198e..02a188a87f2a 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment_test.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment_test.go @@ -131,6 +131,10 @@ func Test_environmentAnalyzer_Analyze(t *testing.T) { }, }, }, + { + name: "empty", + inputFile: "testdata/empty.yaml", + }, { name: "invalid", inputFile: "testdata/invalid.yaml", diff --git a/pkg/fanal/analyzer/language/conda/environment/testdata/empty.yaml b/pkg/fanal/analyzer/language/conda/environment/testdata/empty.yaml new file mode 100644 index 000000000000..f9622b8a1030 --- /dev/null +++ b/pkg/fanal/analyzer/language/conda/environment/testdata/empty.yaml @@ -0,0 +1,5 @@ +name: test-env +channels: + - defaults +dependencies: +prefix: /opt/conda/envs/test-env From 26e339128d4f9880556b7084e72a62ad048f8d5f Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Thu, 27 Jun 2024 09:50:46 +0600 Subject: [PATCH 11/16] docs: refactoring struct of conda page --- docs/docs/coverage/os/conda.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/docs/coverage/os/conda.md b/docs/docs/coverage/os/conda.md index 69f8eeceb19d..10c1e93c1b8c 100644 --- a/docs/docs/coverage/os/conda.md +++ b/docs/docs/coverage/os/conda.md @@ -9,14 +9,17 @@ Trivy supports the following scanners for Conda packages. | License | ✓ | -## SBOM -Trivy detects packages that have been installed with `Conda`. +## `.json` +### SBOM +Trivy parses `/envs//conda-meta/.json` files to find the dependencies installed in your env. -### `.json` -Trivy parses `/envs//conda-meta/.json` files to find the version and license for the dependencies installed in your env. +### License +The `.json` files contain package license information. +Trivy includes licenses for the packages it finds without having to parse additional files. -### `environment.yml`[^1] +## `environment.yml`[^1] +### SBOM Trivy supports parsing [environment.yml][environment.yml][^1] files to find dependency list. `environment.yml`[^1] files supports [version range][env-version-range]. We can't be sure about versions for these dependencies. @@ -25,8 +28,9 @@ Therefore, you need to use `conda env export` command to get dependency list in !!! note For dependencies in a non-Conda format, Trivy doesn't include a version of them. -#### licenses +### License Trivy parses `conda-meta/.json` files at the [prefix] path. + To correctly define licenses, make sure your `environment.yml`[^1] contains `prefix` field and `prefix` directory contains `package.json` files. !!! note From 67daea7c8b66c589090e9bef8d9cc6a6f8c94839 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 1 Jul 2024 11:00:15 +0600 Subject: [PATCH 12/16] refactor(parser): wrap ftypes.Packages --- .../parser/conda/environment/parse.go | 19 +- .../parser/conda/environment/parse_test.go | 350 +++++++++--------- 2 files changed, 181 insertions(+), 188 deletions(-) diff --git a/pkg/dependency/parser/conda/environment/parse.go b/pkg/dependency/parser/conda/environment/parse.go index 25be26b6a3bb..3aaccbc1d34f 100644 --- a/pkg/dependency/parser/conda/environment/parse.go +++ b/pkg/dependency/parser/conda/environment/parse.go @@ -28,6 +28,11 @@ type Dependency struct { Line int } +type Packages struct { + Packages ftypes.Packages + Prefix string +} + type Parser struct { logger *log.Logger once sync.Once @@ -40,16 +45,16 @@ func NewParser() *Parser { } } -func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { +func (p *Parser) Parse(r xio.ReadSeekerAt) (Packages, error) { var env environment if err := yaml.NewDecoder(r).Decode(&env); err != nil { - return nil, nil, xerrors.Errorf("unable to decode conda environment.yml file: %w", err) + return Packages{}, xerrors.Errorf("unable to decode conda environment.yml file: %w", err) } var pkgs ftypes.Packages for _, entry := range env.Entries { for _, dep := range entry.Dependencies { - pkg := p.toPackage(dep, env.Prefix) + pkg := p.toPackage(dep) // Skip empty pkgs if pkg.Name == "" { continue @@ -59,10 +64,13 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc } sort.Sort(pkgs) - return pkgs, nil, nil + return Packages{ + Packages: pkgs, + Prefix: env.Prefix, + }, nil } -func (p *Parser) toPackage(dep Dependency, prefix string) ftypes.Package { +func (p *Parser) toPackage(dep Dependency) ftypes.Package { name, ver := p.parseDependency(dep.Value) if ver == "" { p.once.Do(func() { @@ -78,7 +86,6 @@ func (p *Parser) toPackage(dep Dependency, prefix string) ftypes.Package { EndLine: dep.Line, }, }, - FilePath: prefix, } } diff --git a/pkg/dependency/parser/conda/environment/parse_test.go b/pkg/dependency/parser/conda/environment/parse_test.go index b019aa1c7e08..9323f65c6ee4 100644 --- a/pkg/dependency/parser/conda/environment/parse_test.go +++ b/pkg/dependency/parser/conda/environment/parse_test.go @@ -15,192 +15,178 @@ func TestParse(t *testing.T) { tests := []struct { name string input string - want []ftypes.Package + want environment.Packages wantErr string }{ { name: "happy path", input: "testdata/happy.yaml", - want: []ftypes.Package{ - { - Name: "_openmp_mutex", - Locations: ftypes.Locations{ - { - StartLine: 6, - EndLine: 6, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "asgiref", - Version: "3.8.1", - Locations: ftypes.Locations{ - { - StartLine: 21, - EndLine: 21, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "blas", - Version: "1.0", - Locations: ftypes.Locations{ - { - StartLine: 5, - EndLine: 5, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "bzip2", - Version: "1.0.8", - Locations: ftypes.Locations{ - { - StartLine: 19, - EndLine: 19, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "ca-certificates", - Version: "2024.2", - Locations: ftypes.Locations{ - { - StartLine: 7, - EndLine: 7, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "django", - Version: "5.0.6", - Locations: ftypes.Locations{ - { - StartLine: 22, - EndLine: 22, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "ld_impl_linux-aarch64", - Locations: ftypes.Locations{ - { - StartLine: 8, - EndLine: 8, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libblas", - Locations: ftypes.Locations{ - { - StartLine: 9, - EndLine: 9, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libcblas", - Locations: ftypes.Locations{ - { - StartLine: 10, - EndLine: 10, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libexpat", - Version: "2.6.2", - Locations: ftypes.Locations{ - { - StartLine: 11, - EndLine: 11, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libffi", - Version: "3.4.2", - Locations: ftypes.Locations{ - { - StartLine: 12, - EndLine: 12, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libgcc-ng", - Locations: ftypes.Locations{ - { - StartLine: 13, - EndLine: 13, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libgfortran-ng", - Locations: ftypes.Locations{ - { - StartLine: 14, - EndLine: 14, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libgfortran5", - Locations: ftypes.Locations{ - { - StartLine: 15, - EndLine: 15, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libgomp", - Version: "13.2.0", - Locations: ftypes.Locations{ - { - StartLine: 16, - EndLine: 16, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "liblapack", - Locations: ftypes.Locations{ - { - StartLine: 17, - EndLine: 17, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, - { - Name: "libnsl", - Version: "2.0.1", - Locations: ftypes.Locations{ - { - StartLine: 18, - EndLine: 18, - }, - }, - FilePath: "/opt/conda/envs/test-env", - }, + want: environment.Packages{ + Packages: []ftypes.Package{ + { + Name: "_openmp_mutex", + Locations: ftypes.Locations{ + { + StartLine: 6, + EndLine: 6, + }, + }, + }, + { + Name: "asgiref", + Version: "3.8.1", + Locations: ftypes.Locations{ + { + StartLine: 21, + EndLine: 21, + }, + }, + }, + { + Name: "blas", + Version: "1.0", + Locations: ftypes.Locations{ + { + StartLine: 5, + EndLine: 5, + }, + }, + }, + { + Name: "bzip2", + Version: "1.0.8", + Locations: ftypes.Locations{ + { + StartLine: 19, + EndLine: 19, + }, + }, + }, + { + Name: "ca-certificates", + Version: "2024.2", + Locations: ftypes.Locations{ + { + StartLine: 7, + EndLine: 7, + }, + }, + }, + { + Name: "django", + Version: "5.0.6", + Locations: ftypes.Locations{ + { + StartLine: 22, + EndLine: 22, + }, + }, + }, + { + Name: "ld_impl_linux-aarch64", + Locations: ftypes.Locations{ + { + StartLine: 8, + EndLine: 8, + }, + }, + }, + { + Name: "libblas", + Locations: ftypes.Locations{ + { + StartLine: 9, + EndLine: 9, + }, + }, + }, + { + Name: "libcblas", + Locations: ftypes.Locations{ + { + StartLine: 10, + EndLine: 10, + }, + }, + }, + { + Name: "libexpat", + Version: "2.6.2", + Locations: ftypes.Locations{ + { + StartLine: 11, + EndLine: 11, + }, + }, + }, + { + Name: "libffi", + Version: "3.4.2", + Locations: ftypes.Locations{ + { + StartLine: 12, + EndLine: 12, + }, + }, + }, + { + Name: "libgcc-ng", + Locations: ftypes.Locations{ + { + StartLine: 13, + EndLine: 13, + }, + }, + }, + { + Name: "libgfortran-ng", + Locations: ftypes.Locations{ + { + StartLine: 14, + EndLine: 14, + }, + }, + }, + { + Name: "libgfortran5", + Locations: ftypes.Locations{ + { + StartLine: 15, + EndLine: 15, + }, + }, + }, + { + Name: "libgomp", + Version: "13.2.0", + Locations: ftypes.Locations{ + { + StartLine: 16, + EndLine: 16, + }, + }, + }, + { + Name: "liblapack", + Locations: ftypes.Locations{ + { + StartLine: 17, + EndLine: 17, + }, + }, + }, + { + Name: "libnsl", + Version: "2.0.1", + Locations: ftypes.Locations{ + { + StartLine: 18, + EndLine: 18, + }, + }, + }, + }, + Prefix: "/opt/conda/envs/test-env", }, }, { @@ -230,7 +216,7 @@ func TestParse(t *testing.T) { require.NoError(t, err) defer f.Close() - got, _, err := environment.NewParser().Parse(f) + got, err := environment.NewParser().Parse(f) if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr) From 9200c44f449a502e5a80a4ebc781cb0eadb8e3bd Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 1 Jul 2024 11:06:30 +0600 Subject: [PATCH 13/16] refactor(analyzer): add `Parser` func --- .../language/conda/environment/environment.go | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index e20d20b10fe5..ca52cb31bc1b 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -16,6 +16,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + xio "github.com/aquasecurity/trivy/pkg/x/io" ) func init() { @@ -24,47 +25,56 @@ func init() { const version = 2 -type environmentAnalyzer struct{} +type parser struct{} -func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - res, err := language.Analyze(types.CondaEnv, input.FilePath, input.Content, environment.NewParser()) +func (*parser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) { + p := environment.NewParser() + pkgs, err := p.Parse(r) if err != nil { - return nil, xerrors.Errorf("unable to parse environment.yaml: %w", err) - } - - if res == nil { - return nil, nil + return nil, nil, err } once := sync.Once{} // res always contains only 1 Application // cf. https://github.com/aquasecurity/trivy/blob/0ccdbfbb6598a52de7cda603ab22e794f710e86c/pkg/fanal/analyzer/language/analyze.go#L32 - for i, pkg := range res.Applications[0].Packages { + for i, pkg := range pkgs.Packages { // Skip packages without a version, because in this case we will not be able to get the correct file name. if pkg.Version != "" { - licenses, err := findLicenseFromEnvDir(pkg) + licenses, err := findLicenseFromEnvDir(pkg, pkgs.Prefix) if err != nil { // Show log once per file once.Do(func() { - log.WithPrefix("conda").Debug("License not found. For more information, see https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#licenses", - log.String("file", input.FilePath), log.String("pkg", pkg.Name), log.Err(err)) + log.WithPrefix("conda").Debug("License not found. See https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#licenses for details", + log.String("pkg", pkg.Name), log.Err(err)) }) } pkg.Licenses = licenses } - pkg.FilePath = "" // remove `prefix` from FilePath - res.Applications[0].Packages[i] = pkg + pkgs.Packages[i] = pkg + } + + return pkgs.Packages, nil, nil +} + +type environmentAnalyzer struct{} +func (a environmentAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + res, err := language.Analyze(types.CondaEnv, input.FilePath, input.Content, &parser{}) + if err != nil { + return nil, xerrors.Errorf("unable to parse environment.yaml: %w", err) } + if res == nil { + return nil, nil + } return res, nil } -func findLicenseFromEnvDir(pkg types.Package) ([]string, error) { - if pkg.FilePath == "" { +func findLicenseFromEnvDir(pkg types.Package, prefix string) ([]string, error) { + if prefix == "" { return nil, xerrors.Errorf("`prefix` field doesn't exist") } - condaMetaDir := filepath.Join(pkg.FilePath, "conda-meta") + condaMetaDir := filepath.Join(prefix, "conda-meta") entries, err := os.ReadDir(condaMetaDir) if err != nil { return nil, xerrors.Errorf("unable to read conda-meta dir: %w", err) From 61e9c7e714920cc3c21b4111bf19b0c4aa5b9995 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 1 Jul 2024 11:28:45 +0600 Subject: [PATCH 14/16] refactor(analyzer): remove unneeded comment --- pkg/fanal/analyzer/language/conda/environment/environment.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index ca52cb31bc1b..1eaf279efd51 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -35,8 +35,6 @@ func (*parser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, e } once := sync.Once{} - // res always contains only 1 Application - // cf. https://github.com/aquasecurity/trivy/blob/0ccdbfbb6598a52de7cda603ab22e794f710e86c/pkg/fanal/analyzer/language/analyze.go#L32 for i, pkg := range pkgs.Packages { // Skip packages without a version, because in this case we will not be able to get the correct file name. if pkg.Version != "" { @@ -44,7 +42,7 @@ func (*parser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, e if err != nil { // Show log once per file once.Do(func() { - log.WithPrefix("conda").Debug("License not found. See https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#licenses for details", + log.WithPrefix("conda").Debug("License not found. See https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#license_1 for details", log.String("pkg", pkg.Name), log.Err(err)) }) } From 66a519628c9716a270f384dea73469d47709341d Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:19:13 +0600 Subject: [PATCH 15/16] Update link Co-authored-by: Teppei Fukuda --- pkg/fanal/analyzer/language/conda/environment/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index 1eaf279efd51..ae6effbbbd11 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -42,7 +42,7 @@ func (*parser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, e if err != nil { // Show log once per file once.Do(func() { - log.WithPrefix("conda").Debug("License not found. See https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#license_1 for details", + log.WithPrefix("conda").Debugf("License not found. See %s for details", doc.URL("docs/coverage/os/conda/", "license_1")) log.String("pkg", pkg.Name), log.Err(err)) }) } From b68efe76172261a54bb3a35096d94a919f9ecbf4 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 1 Jul 2024 12:29:10 +0600 Subject: [PATCH 16/16] fix: add import + update link --- pkg/fanal/analyzer/language/conda/environment/environment.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/fanal/analyzer/language/conda/environment/environment.go b/pkg/fanal/analyzer/language/conda/environment/environment.go index ae6effbbbd11..305ac4821855 100644 --- a/pkg/fanal/analyzer/language/conda/environment/environment.go +++ b/pkg/fanal/analyzer/language/conda/environment/environment.go @@ -16,6 +16,7 @@ import ( "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/version/doc" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -42,7 +43,8 @@ func (*parser) Parse(r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, e if err != nil { // Show log once per file once.Do(func() { - log.WithPrefix("conda").Debugf("License not found. See %s for details", doc.URL("docs/coverage/os/conda/", "license_1")) + // e.g. https://aquasecurity.github.io/trivy/latest/docs/coverage/os/conda/#license_1 + log.WithPrefix("conda").Debug(fmt.Sprintf("License not found. See %s for details.", doc.URL("docs/coverage/os/conda/", "license_1")), log.String("pkg", pkg.Name), log.Err(err)) }) }