Compare commits

...
54 Commits
Author SHA1 Message Date
Jonas XavierandAlex Goodman 69fb0a6f3b Overwrite deprecated SPDX licenses automatically (#1009)
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
2022-08-02 15:25:33 -04:00
Christopher Angelo Phillips e68f384063 disable release for docker assets (#1137) 2022-08-02 14:47:07 -04:00
Christopher Angelo Phillips f5d02d4e52 improve docker release bootstrap (#1136) 2022-08-02 15:44:24 +00:00
Adam HughesandAlex Goodman d361d40cfa Singularity Image Support (#974)
* docs: add Singularity image support

Add "singularity-image" scheme to CLI documentation and README.

Signed-off-by: Adam Hughes <9903835+tri-adam@users.noreply.github.com>

* upgrade stereoscope + docs

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
2022-08-02 11:42:46 -04:00
Christopher Angelo Phillips b7f587f5dc remove docker login from keychain (#1135) 2022-08-02 09:42:12 -04:00
Christopher Angelo Phillips d196ab70a0 remove ENV checks from siging script (#1134) 2022-08-01 22:08:41 +00:00
Christopher Angelo Phillips 1bf97af3fb remove docker assets from main goreleaser configuration to reduce mac-os runner friction (#1133) 2022-08-01 21:08:38 +00:00
Christopher Angelo Phillips ca69fb8370 remove prefixed v from tag to match release (#1131) 2022-08-01 15:07:58 +00:00
Christopher Angelo Phillips 8f21180681 rollback actions-setup-docker to earlier version (#1130) 2022-08-01 14:10:50 +00:00
Tom Fay b4c272885d Bump go-rustaudit to support rustaudit 0.2.0 (#1127) 2022-08-01 09:20:31 -04:00
Weston Steimel fb8f24dc1e bump bouncer to v0.4.0 (#1125)
Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
2022-07-29 16:57:59 +01:00
Mayur Waghmode f1c5463a6b Added ppc64le supported to the syft:debug image (#1124) 2022-07-28 14:18:11 -04:00
Tom Fay 9896ff1b1f add a cataloger for binaries built with rust-audit (#1116)
* add a cataloger for binaries built with rust-audit

Signed-off-by: Tom Fay <tomfay@microsoft.com>
2022-07-28 18:17:38 +00:00
Weston SteimelandChristopher Phillips 62897fbc89 bump goreleaser to v1.10.3 (#1123)
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2022-07-28 17:10:32 +00:00
Weston Steimel d1729ee7e2 bump golangci-lint to v1.47.2 (#1122) 2022-07-28 09:40:53 -04:00
Weston Steimel 43715d380b bump cosign in bootstrap-tools to v1.10.0 (#1121) 2022-07-28 09:40:42 -04:00
Mayur Waghmode af330c8a37 Added s390x support (#1117) 2022-07-27 16:33:54 -04:00
Christopher Angelo Phillips 20ad59ad1b Delete pr_action.yaml (#1120) 2022-07-27 17:12:00 +00:00
cpendery 8235e8e581 fix: use generic instead of not generating purl (#1119) 2022-07-27 09:06:37 -04:00
Weston Steimel b720a3c81c bump cosign to v1.10.0 (#1114)
Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
2022-07-22 09:41:38 -04:00
Marco Deicas ba9adb17eb Update sigstore/rekor dependency (#1112) 2022-07-21 09:17:16 -04:00
Mayur WaghmodeandChristopher Phillips 51727fcf2d Added ppc64le support (#1099)
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2022-07-21 09:12:52 -04:00
Christopher Angelo Phillips 5dc729b67e patch-distroless-ghcr (#1110) 2022-07-20 16:51:49 -04:00
Christopher Angelo Phillips 7bae9d4b4e add distroless debug image to published release (#1106)
add debug distroless image to published release

Debian was chosen based on the fact that it is the smallest available distroless image
The new tag is `anchore/syft:debug`

Closes #833
2022-07-20 15:54:46 +00:00
Christopher Angelo Phillips 571de3602a update help formatting (#1105) 2022-07-19 14:46:48 +00:00
cpendery 9b1adce19a feat: implement haskell support (#1096) 2022-07-18 15:33:54 -04:00
Josh Bressers 00e12329d0 Add the -r argument for gnu xargs (#1103) 2022-07-18 08:45:37 -04:00
cpendery 922663c987 fix: -o output option to include formats (#1102) 2022-07-15 09:55:15 -04:00
Christopher Angelo Phillips 64b4852c2a moves go-rpmdb to latest; libc => v1.16.7 (#1098) 2022-07-12 10:30:21 -04:00
cpendery 470b13045b feat: add support for cocoapods (Swift/Objective-C) (#1081) 2022-07-11 10:09:08 -04:00
Rob Best 2f1aa33ce8 Fix package url for Go modules with no / (#1092) 2022-07-11 10:07:34 -04:00
anchore-actions-token-generator[bot]andkzantow b3a7b912e1 Update Stereoscope to 777471f38c5b2f15c19d6cffe093ce6392d8040c (#1090)
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2022-07-11 09:42:59 -04:00
Batuhan Apaydın 5206193b23 feat: output attestation to file (#1087) 2022-07-08 13:05:20 -04:00
anchore-actions-token-generator[bot]andkzantow c7fa498a1b Update Stereoscope to cfbd966e5a8d11d73cd17adc8b8ab8468a086f1e (#1089)
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2022-07-07 10:05:55 -04:00
Zac MedicoandChristopher Phillips 4c55c62834 Add portage support for Gentoo Linux (#1076)
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2022-07-06 16:18:54 -04:00
Christopher Angelo Phillips ba685eada8 Add PR action back to workflow with new token (#1086) 2022-07-06 09:31:51 -04:00
Batuhan ApaydınandChristopher Phillips 69134ed3b5 feat: add new login cmd (#1068)
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2022-07-05 11:57:28 -04:00
Christopher Angelo Phillips 47df66960f update AltRpmDbGlob with comment and context (#1085) 2022-07-05 11:29:31 -04:00
cpendery 57323a1666 feat: add support for conan packages (C/C++) (#1083) 2022-07-05 10:49:24 -04:00
Jonas XavierandChristopher Phillips 6b28a46ebe add golang main module and pseudo-version (#916)
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2022-07-01 13:01:57 -04:00
Dan Nurmi 3ae728ede1 fix: add glob to filter list to ensure rpm metadata files are matched… (#1079) 2022-06-30 19:50:24 -04:00
Christopher Angelo Phillips 3ce1a4aac1 remove pr automation until service account creation (#1080) 2022-06-30 21:43:24 +00:00
cpendery bc054e4724 fix: purl generation for pom.xml (#1078) 2022-06-30 10:31:36 -04:00
anchore-actions-token-generator[bot]andkzantow 1e3ffbebb9 Update Stereoscope to 5bd627c0f9ce7facbd63ed1f0cf894d97021aa5e (#1072)
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2022-06-29 10:18:41 -04:00
cpendery 0853825931 fix: add new languages found in cpes (#1069) 2022-06-28 10:05:13 -04:00
cpenderyandChristopher Phillips 73262c7258 fix: add php catalogers to all catalogers (#1065)
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2022-06-27 14:53:10 +00:00
cpendery 2b8e15b638 feat: add use-all-catalogers flag (#1050) 2022-06-27 10:24:45 -04:00
MikiandChristopher Phillips d5e12ff89c Updates parsing of yarn.lock to use resolved URLs that are pulled from yarn and npm registries (#926)
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2022-06-24 11:05:25 -04:00
Christopher Angelo Phillips bafc66a5e7 remove OSS Meetup message (#1057) 2022-06-23 18:07:23 +00:00
Jonas XavierandChristopher Phillips 1d14f22e45 add pom.xml cataloger (#1055)
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2022-06-22 14:19:10 -04:00
Tom Fay 3f6afd572a Add support for CBL-Mariner distroless images (#1045) 2022-06-21 13:27:03 -04:00
Alex Goodmanandramanan-ravi ea611dab5f Add catalogers configuration (#1038)
* Option to enable specific language or ecosystem cataloger

Signed-off-by: ramanan-ravi <ramanan@deepfence.io>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* Disable dotnet cataloger

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* Option to enable specific language or ecosystem cataloger

Signed-off-by: Ramanan Ravikumar <ramanan@deepfence.io>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* rename "enable-cataloger" option to "catalogers"

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add cli test for --catalogers option

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update readme with latest cataloger names

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* enable dotnet cataloger

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix linting

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix cataloger imports

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update readme with alpmdb cataloger config example

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Co-authored-by: ramanan-ravi <ramanan@deepfence.io>
2022-06-21 13:06:25 +00:00
Jonas Xavier aed1599c4d add template output (#1051)
* add template output

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* remove dead code

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* fix template cli flag

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* implement template's own format type

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* simpler code

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* fix readme link to Go template

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* feedback changes

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* simpler func signature patter

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* nit

Signed-off-by: Jonas Xavier <jonasx@anchore.com>

* fix linter error

Signed-off-by: Jonas Xavier <jonasx@anchore.com>
2022-06-17 14:04:31 -04:00
Christopher Angelo Phillips 03e37044d4 update stereoscope to latest version (#1052) 2022-06-16 14:56:33 -04:00
234 changed files with 14264 additions and 13161 deletions
@@ -14,23 +14,6 @@ if [ -z "$APPLE_DEVELOPER_ID_CERT_PASS" ]; then
exit_with_error "APPLE_DEVELOPER_ID_CERT_PASS not set"
fi
if [ -z "$DOCKER_USERNAME" ]; then
exit_with_error "DOCKER_USERNAME not set"
fi
if [ -z "$DOCKER_PASSWORD" ]; then
exit_with_error "DOCKER_PASSWORD not set"
fi
if [ -z "$GHCR_USERNAME" ]; then
exit_with_error "GHCR_USERNAME not set"
fi
if [ -z "$GHCR_PASSWORD" ]; then
exit_with_error "GHCR_PASSWORD not set"
fi
set -u
# setup_signing
#
# preps the MAC_SIGNING_IDENTITY env var for use in the signing process, using production certificate material
@@ -46,8 +29,4 @@ setup_signing() {
# Make this new keychain the user's default keychain, so that codesign will be able to find this certificate when we specify it during signing.
security default-keychain -d "user" -s "${KEYCHAIN_PATH}"
commentary "log into docker -- required for publishing (since the default keychain has now been replaced)"
echo "${DOCKER_PASSWORD}" | docker login docker.io -u "${DOCKER_USERNAME}" --password-stdin
echo "${GHCR_PASSWORD}" | docker login ghcr.io -u "${GHCR_USERNAME}" --password-stdin
}
-17
View File
@@ -1,17 +0,0 @@
# Uses https://github.com/actions/add-to-project example to add issues to Anchore OSS project
name: Add issues to OSS project
on:
issues:
types:
- opened
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@main
with:
project-url: https://github.com/orgs/anchore/projects/22
github-token: ${{ secrets.CI_WRITE_GITHUB_TOKEN }}
-17
View File
@@ -1,17 +0,0 @@
# Uses https://github.com/actions/add-to-project example to add PR to Anchore OSS project
name: Add pr to OSS project
on:
pull_request:
types:
- opened
jobs:
add-to-project:
name: Add pr to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@main
with:
project-url: https://github.com/orgs/anchore/projects/22
github-token: ${{ secrets.CI_WRITE_GITHUB_TOKEN }}
+41 -7
View File
@@ -97,8 +97,6 @@ jobs:
permissions:
packages: write
steps:
- uses: docker-practice/actions-setup-docker@v1
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
@@ -130,11 +128,6 @@ jobs:
- name: Build & publish release artifacts
run: make release
env:
GHCR_USERNAME: ${{ github.actor }}
GHCR_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
DOCKER_USERNAME: ${{ secrets.TOOLBOX_DOCKER_USER }}
DOCKER_PASSWORD: ${{ secrets.TOOLBOX_DOCKER_PASS }}
# we use a different token than GITHUB_SECRETS to additionally allow updating the homebrew repos
GITHUB_TOKEN: ${{ secrets.ANCHORE_GIT_READ_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
@@ -164,3 +157,44 @@ jobs:
with:
name: artifacts
path: dist/**/*
release-docker-assets:
needs: [release]
# code signing requires we run on mac-os runners. docker does not come installed on the mac-os runner
# a previous release process installed and configured docker on the mac-os runner which lead to blocked releases
# the anchore tools team opted to break this step out to a separate process to remove this work constraint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.TOOLBOX_DOCKER_USER }}
password: ${{ secrets.TOOLBOX_DOCKER_PASS }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Restore go cache
id: go-cache
uses: actions/cache@v2.1.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: (cache-miss) Bootstrap all project dependencies
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Build & Publish docker images
run: make release-docker-assets
+2 -59
View File
@@ -2,10 +2,6 @@ release:
prerelease: auto
draft: false
env:
# required to support multi architecture docker builds
- DOCKER_CLI_EXPERIMENTAL=enabled
before:
hooks:
- ./.github/scripts/apple-signing/setup.sh {{ .IsSnapshot }}
@@ -19,6 +15,8 @@ builds:
goarch:
- amd64
- arm64
- ppc64le
- s390x
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
env: &build-env
@@ -95,58 +93,3 @@ brews:
homepage: *website
description: *description
license: "Apache License 2.0"
dockers:
- image_templates:
- "anchore/syft:latest"
- "anchore/syft:{{ .Tag }}-amd64"
- "anchore/syft:v{{ .Major }}-amd64"
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64"
- "ghcr.io/anchore/syft:latest"
- "ghcr.io/anchore/syft:{{ .Tag }}-amd64"
- "ghcr.io/anchore/syft:v{{ .Major }}-amd64"
- "ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64"
goarch: amd64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- "anchore/syft:{{ .Tag }}-arm64v8"
- "anchore/syft:v{{ .Major }}-arm64v8"
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8"
- "ghcr.io/anchore/syft:{{ .Tag }}-arm64v8"
- "ghcr.io/anchore/syft:v{{ .Major }}-arm64v8"
- "ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8"
goarch: arm64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
docker_manifests:
- name_template: anchore/syft:{{ .Tag }}
image_templates:
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
- name_template: anchore/syft:latest
image_templates:
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
- name_template: ghcr.io/anchore/syft:{{ .Tag }}
image_templates:
- ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
- ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
- name_template: ghcr.io/anchore/syft:latest
image_templates:
- ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
- ghcr.io/anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
+193
View File
@@ -0,0 +1,193 @@
# Separate docker configuration to isolate docker dependency away from
# mac-os runner on github actions.
# See:
# https://github.com/anchore/syft/issues/577
# https://github.com/anchore/syft/issues/519
# https://github.com/anchore/syft/issues/576
release:
disable: true
env:
# required to support multi architecture docker builds
- DOCKER_CLI_EXPERIMENTAL=enabled
builds:
- id: linux-build
dir: ./cmd/syft
binary: syft
goos:
- linux
goarch:
- amd64
- arm64
- ppc64le
- s390x
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
env: &build-env
- CGO_ENABLED=0
ldflags: &build-ldflags |
-w
-s
-extldflags '-static'
-X github.com/anchore/syft/internal/version.version={{.Version}}
-X github.com/anchore/syft/internal/version.gitCommit={{.Commit}}
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
-X github.com/anchore/syft/internal/version.gitDescription={{.Summary}}
dockers:
- image_templates:
- anchore/syft:debug
- anchore/syft:{{.Tag}}-debug
- ghcr.io/anchore/syft:debug
- ghcr.io/anchore/syft:{{.Tag}}-debug
goarch: amd64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-arm64v8
- anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
goarch: arm64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-ppc64le
- anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
goarch: ppc64le
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:debug-s390x
- anchore/syft:{{.Tag}}-debug-s390x
- ghcr.io/anchore/syft:debug-s390x
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
goarch: s390x
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:latest
- anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:latest
- ghcr.io/anchore/syft:{{.Tag}}
goarch: amd64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
goarch: arm64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
goarch: ppc64le
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-s390x
- ghcr.io/anchore/syft:{{.Tag}}-s390x
goarch: s390x
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
docker_manifests:
- name_template: anchore/syft:latest
image_templates:
- anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:debug
- anchore/syft:{{.Tag}}-debug
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}
image_templates:
- anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:latest
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
+1 -4
View File
@@ -1,6 +1,4 @@
FROM alpine:latest AS build
RUN apk --no-cache add ca-certificates
FROM gcr.io/distroless/static-debian11:debug AS build
FROM scratch
# needed for version check HTTPS request
@@ -27,6 +25,5 @@ LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
LABEL io.artifacthub.package.license="Apache-2.0"
ENTRYPOINT ["/syft"]
+25
View File
@@ -0,0 +1,25 @@
FROM gcr.io/distroless/static-debian11:debug
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
COPY syft /
ARG BUILD_DATE
ARG BUILD_VERSION
ARG VCS_REF
ARG VCS_URL
LABEL org.opencontainers.image.created=$BUILD_DATE
LABEL org.opencontainers.image.title="syft"
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
LABEL org.opencontainers.image.source=$VCS_URL
LABEL org.opencontainers.image.revision=$VCS_REF
LABEL org.opencontainers.image.vendor="Anchore, Inc."
LABEL org.opencontainers.image.version=$BUILD_VERSION
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
LABEL io.artifacthub.package.license="Apache-2.0"
ENTRYPOINT ["/syft"]
+30 -6
View File
@@ -32,7 +32,7 @@ BOOTSTRAP_CACHE="c7afb99ad"
DISTDIR=./dist
SNAPSHOTDIR=./snapshot
OS=$(shell uname | tr '[:upper:]' '[:lower:]')
SNAPSHOT_BIN=$(shell realpath $(shell pwd)/$(SNAPSHOTDIR)/$(OS)-build_$(OS)_amd64/$(BIN))
SNAPSHOT_BIN=$(shell realpath $(shell pwd)/$(SNAPSHOTDIR)/$(OS)-build_$(OS)_amd64_v1/$(BIN))
## Variable assertions
@@ -106,12 +106,12 @@ $(TEMPDIR):
.PHONY: bootstrap-tools
bootstrap-tools: $(TEMPDIR)
GO111MODULE=off GOBIN=$(shell realpath $(TEMPDIR)) go get -u golang.org/x/perf/cmd/benchstat
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.45.0
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ v0.3.0
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.47.2
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ v0.4.0
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ v0.3.0
.github/scripts/goreleaser-install.sh -d -b $(TEMPDIR)/ v1.4.1
.github/scripts/goreleaser-install.sh -d -b $(TEMPDIR)/ v1.10.3
GOBIN="$(shell realpath $(TEMPDIR))" go install github.com/neilpa/yajsv@v1.4.0
GOBIN="$(shell realpath $(TEMPDIR))" go install github.com/sigstore/cosign/cmd/cosign@v1.5.1
GOBIN="$(shell realpath $(TEMPDIR))" go install github.com/sigstore/cosign/cmd/cosign@v1.10.0
.PHONY: bootstrap-go
bootstrap-go:
@@ -263,6 +263,18 @@ snapshot-with-signing: ## Build snapshot release binaries and packages (with dum
# remove the keychain with the trusted self-signed cert automatically
.github/scripts/apple-signing/cleanup.sh
snapshot-docker-assets: # Build snapshot images of docker images that will be published on release
$(call title,Building snapshot docker release assets)
# create a config with the dist dir overridden
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
cat .goreleaser_docker.yaml >> $(TEMPDIR)/goreleaser.yaml
bash -c "\
$(SNAPSHOT_CMD) \
--config $(TEMPDIR)/goreleaser.yaml \
--parallelism 1"
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
.PHONY: compare-mac
compare-mac: $(RESULTSDIR) $(SNAPSHOTDIR) ## Run compare tests on build snapshot binaries and packages (Mac)
@@ -342,6 +354,18 @@ release: clean-dist CHANGELOG.md ## Build and publish final binaries and packag
# upload the version file that supports the application version update check (excluding pre-releases)
.github/scripts/update-version-file.sh "$(DISTDIR)" "$(VERSION)"
.PHONY: release-docker-assets
release-docker-assets:
$(call title,Publishing docker release assets)
# create a config with the dist dir overridden
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
cat .goreleaser_docker.yaml >> $(TEMPDIR)/goreleaser.yaml
bash -c "\
$(RELEASE_CMD) \
--config $(TEMPDIR)/goreleaser.yaml \
--parallelism 1"
.PHONY: clean
clean: clean-dist clean-snapshot clean-test-image-cache ## Remove previous builds, result reports, and test cache
@@ -369,7 +393,7 @@ clean-test-image-tar-cache: ## Delete all test cache (built docker image tars)
.PHONY: clear-test-image-docker-cache
clean-test-image-docker-cache: ## Purge all test docker images
docker images --format '{{.ID}} {{.Repository}}' | grep stereoscope-fixture- | awk '{print $$1}' | uniq | xargs docker rmi --force
docker images --format '{{.ID}} {{.Repository}}' | grep stereoscope-fixture- | awk '{print $$1}' | uniq | xargs -r docker rmi --force
.PHONY: show-test-image-cache
show-test-image-cache: ## Show all docker and image tar cache
+73 -16
View File
@@ -11,14 +11,6 @@
A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner like [Grype](https://github.com/anchore/grype).
### Join our Virtual OSS Meetup!
You are invited to join us on June 15th, 11AM-Noon PT for our virtual open source meetup.
Hosts Amy Bass from Docker Desktop and Christopher Phillips from Anchore OSS will explore how Docker Extensions for Docker Desktop is supporting open source projects and we’ll have the latest update on Syft: in-toto attestations.
[Register here ->](https://get.anchore.com/anchore-oss-meetup-jun-15-2022/)
### Join our community meetings!
- Calendar: https://calendar.google.com/calendar/u/0/r?cid=Y182OTM4dGt0MjRtajI0NnNzOThiaGtnM29qNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t
@@ -38,10 +30,14 @@ Hosts Amy Bass from Docker Desktop and Christopher Phillips from Anchore OSS wil
### Supported Ecosystems
- Alpine (apk)
- C (conan)
- C++ (conan)
- Dart (pubs)
- Debian (dpkg)
- Dotnet (deps.json)
- Objective-C (cocoapods)
- Go (go.mod, Go binaries)
- Haskell (cabal, stack)
- Java (jar, ear, war, par, sar)
- JavaScript (npm, yarn)
- Jenkins Plugins (jpi, hpi)
@@ -50,6 +46,7 @@ Hosts Amy Bass from Docker Desktop and Christopher Phillips from Anchore OSS wil
- Red Hat (rpm)
- Ruby (gem)
- Rust (cargo.lock)
- Swift (cocoapods)
## Installation
@@ -185,14 +182,15 @@ syft path/to/dir
Sources can be explicitly provided with a scheme:
```
docker:yourrepo/yourimage:tag use images from the Docker daemon
podman:yourrepo/yourimage:tag use images from the Podman daemon
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
dir:path/to/yourproject read directly from a path on disk (any directory)
file:path/to/yourproject/file read directly from a path on disk (any single file)
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
docker:yourrepo/yourimage:tag use images from the Docker daemon
podman:yourrepo/yourimage:tag use images from the Podman daemon
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
dir:path/to/yourproject read directly from a path on disk (any directory)
file:path/to/yourproject/file read directly from a path on disk (any single file)
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
```
### Excluding file paths
@@ -230,6 +228,40 @@ Where the `formats` available are:
- `spdx-json`: A JSON report conforming to the [SPDX 2.2 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json).
- `github`: A JSON report conforming to GitHub's dependency snapshot format.
- `table`: A columnar summary (default).
- `template`: Lets the user specify the output format. See ["Using templates"](#using-templates) below.
#### Using templates
Syft lets you define custom output formats, using [Go templates](https://pkg.go.dev/text/template). Here's how it works:
- Define your format as a Go template, and save this template as a file.
- Set the output format to "template" (`-o template`).
- Specify the path to the template file (`-t ./path/to/custom.template`).
- Syft's template processing uses the same data models as the `json` output format — so if you're wondering what data is available as you author a template, you can use the output from `syft <image> -o json` as a reference.
**Example:** You could make Syft output data in CSV format by writing a Go template that renders CSV data and then running `syft <image> -o template -t ~/path/to/csv.tmpl`.
Here's what the `csv.tmpl` file might look like:
```gotemplate
"Package","Version Installed","Found by"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
{{- end}}
```
Which would produce output like:
```text
"Package","Version Installed","Found by"
"alpine-baselayout","3.2.0-r20","apkdb-cataloger"
"alpine-baselayout-data","3.2.0-r20","apkdb-cataloger"
"alpine-keys","2.4-r1","apkdb-cataloger"
...
```
Syft also includes a vast array of utility templating functions from [sprig](http://masterminds.github.io/sprig/) apart from the default Golang [text/template](https://pkg.go.dev/text/template#hdr-Functions) to allow users to customize the output format.
#### Multiple outputs
@@ -360,6 +392,31 @@ exclude: []
# same as --platform; SYFT_PLATFORM env var
platform: ""
# set the list of package catalogers to use when generating the SBOM
# default = empty (cataloger set determined automatically by the source type [image or file/directory])
# catalogers:
# - ruby-gemfile
# - ruby-gemspec
# - python-index
# - python-package
# - javascript-lock
# - javascript-package
# - php-composer-installed
# - php-composer-lock
# - alpmdb
# - dpkgdb
# - rpmdb
# - java
# - apkdb
# - go-module-binary
# - go-mod-file
# - dartlang-lock
# - rust
# - dotnet-deps
# rust-audit-binary scans Rust binaries built with https://github.com/Shnatsel/rust-audit
# - rust-audit-binary
catalogers:
# cataloging packages is exposed through the packages and power-user subcommands
package:
+10 -5
View File
@@ -102,7 +102,7 @@ func Run(ctx context.Context, app *config.Application, ko sigopts.KeyOpts, args
subscription := eventBus.Subscribe()
return eventloop.EventLoop(
execWorker(app, *si, format, predicateType, sv),
execWorker(app, *si, format, predicateType, sv, app.File),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
@@ -137,7 +137,7 @@ func parseImageSource(userInput string, app *config.Application) (s *source.Inpu
return si, nil
}
func execWorker(app *config.Application, sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error {
func execWorker(app *config.Application, sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier, file string) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
@@ -163,7 +163,7 @@ func execWorker(app *config.Application, sourceInput source.Input, format sbom.F
return
}
err = generateAttestation(app, sbomBytes, src, sv, predicateType)
err = generateAttestation(app, sbomBytes, src, sv, predicateType, file)
if err != nil {
errs <- err
return
@@ -172,7 +172,7 @@ func execWorker(app *config.Application, sourceInput source.Input, format sbom.F
return errs
}
func generateAttestation(app *config.Application, predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) error {
func generateAttestation(app *config.Application, predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string, file string) error {
switch len(src.Image.Metadata.RepoDigests) {
case 0:
return fmt.Errorf("cannot generate attestation since no repo digests were found; make sure you're passing an OCI registry source for the attest command")
@@ -219,7 +219,12 @@ func generateAttestation(app *config.Application, predicate []byte, src *source.
bus.Publish(partybus.Event{
Type: event.Exit,
Value: func() error {
_, err := os.Stdout.Write(signedPayload)
var err error
if file != "" {
err = os.WriteFile(file, signedPayload, 0600)
} else {
_, err = os.Stdout.Write(signedPayload)
}
return err
},
})
+17 -6
View File
@@ -15,6 +15,7 @@ import (
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/event"
cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd"
"github.com/gookit/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -30,6 +31,7 @@ const indent = " "
// at this level. Values from the config should only be used after `app.LoadAllValues` has been called.
// Cobra does not have knowledge of the user provided flags until the `RunE` block of each command.
// `RunE` is the earliest that the complete application configuration can be loaded.
// nolint:funlen
func New() (*cobra.Command, error) {
app := &config.Application{}
@@ -82,13 +84,22 @@ func New() (*cobra.Command, error) {
return nil, err
}
// commands to add to root
cmds := []*cobra.Command{
packagesCmd,
attestCmd,
convertCmd,
poweruserCmd,
poweruserCmd,
Completion(),
Version(v, app),
cranecmd.NewCmdAuthLogin("syft"),
}
// Add sub-commands.
rootCmd.AddCommand(packagesCmd)
rootCmd.AddCommand(attestCmd)
rootCmd.AddCommand(convertCmd)
rootCmd.AddCommand(poweruserCmd)
rootCmd.AddCommand(Completion())
rootCmd.AddCommand(Version(v, app))
for _, cmd := range cmds {
rootCmd.AddCommand(cmd)
}
return rootCmd, err
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
func Run(ctx context.Context, app *config.Application, args []string) error {
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
writer, err := options.MakeWriter(app.Outputs, app.File)
writer, err := options.MakeWriter(app.Outputs, app.File, "")
if err != nil {
return err
}
+1 -1
View File
@@ -46,7 +46,7 @@ func generateCatalogPackagesTask(app *config.Application) (Task, error) {
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, app.Package.ToConfig())
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, app.ToCatalogerConfig())
if err != nil {
return nil, err
}
+28 -12
View File
@@ -15,6 +15,7 @@ import (
type PackagesOptions struct {
Scope string
Output []string
OutputTemplatePath string
File string
Platform string
Host string
@@ -24,45 +25,52 @@ type PackagesOptions struct {
Exclude []string
OverwriteExistingImage bool
ImportTimeout uint
Catalogers []string
}
var _ Interface = (*PackagesOptions)(nil)
func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
cmd.PersistentFlags().StringVarP(&o.Scope, "scope", "s", cataloger.DefaultSearchConfig().Scope.String(),
cmd.Flags().StringVarP(&o.Scope, "scope", "s", cataloger.DefaultSearchConfig().Scope.String(),
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))
cmd.PersistentFlags().StringArrayVarP(&o.Output, "output", "o", FormatAliases(table.ID),
cmd.Flags().StringArrayVarP(&o.Output, "output", "o", FormatAliases(table.ID),
fmt.Sprintf("report output format, options=%v", FormatAliases(syft.FormatIDs()...)))
cmd.PersistentFlags().StringVarP(&o.File, "file", "", "",
cmd.Flags().StringVarP(&o.File, "file", "", "",
"file to write the default report output to (default is STDOUT)")
cmd.PersistentFlags().StringVarP(&o.Platform, "platform", "", "",
cmd.Flags().StringVarP(&o.OutputTemplatePath, "template", "t", "",
"specify the path to a Go template file")
cmd.Flags().StringVarP(&o.Platform, "platform", "", "",
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")
cmd.PersistentFlags().StringVarP(&o.Host, "host", "H", "",
cmd.Flags().StringVarP(&o.Host, "host", "H", "",
"the hostname or URL of the Anchore Enterprise instance to upload to")
cmd.PersistentFlags().StringVarP(&o.Username, "username", "u", "",
cmd.Flags().StringVarP(&o.Username, "username", "u", "",
"the username to authenticate against Anchore Enterprise")
cmd.PersistentFlags().StringVarP(&o.Password, "password", "p", "",
cmd.Flags().StringVarP(&o.Password, "password", "p", "",
"the password to authenticate against Anchore Enterprise")
cmd.PersistentFlags().StringVarP(&o.Dockerfile, "dockerfile", "d", "",
cmd.Flags().StringVarP(&o.Dockerfile, "dockerfile", "d", "",
"include dockerfile for upload to Anchore Enterprise")
cmd.PersistentFlags().StringArrayVarP(&o.Exclude, "exclude", "", nil,
cmd.Flags().StringArrayVarP(&o.Exclude, "exclude", "", nil,
"exclude paths from being scanned using a glob expression")
cmd.PersistentFlags().BoolVarP(&o.OverwriteExistingImage, "overwrite-existing-image", "", false,
cmd.Flags().StringArrayVarP(&o.Catalogers, "catalogers", "", nil,
"enable one or more package catalogers")
cmd.Flags().BoolVarP(&o.OverwriteExistingImage, "overwrite-existing-image", "", false,
"overwrite an existing image during the upload to Anchore Enterprise")
cmd.PersistentFlags().UintVarP(&o.ImportTimeout, "import-timeout", "", 30,
cmd.Flags().UintVarP(&o.ImportTimeout, "import-timeout", "", 30,
"set a timeout duration (in seconds) for the upload to Anchore Enterprise")
return bindPackageConfigOptions(cmd.PersistentFlags(), v)
return bindPackageConfigOptions(cmd.Flags(), v)
}
func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
@@ -80,10 +88,18 @@ func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
return err
}
if err := v.BindPFlag("catalogers", flags.Lookup("catalogers")); err != nil {
return err
}
if err := v.BindPFlag("output", flags.Lookup("output")); err != nil {
return err
}
if err := v.BindPFlag("output-template-path", flags.Lookup("template")); err != nil {
return err
}
if err := v.BindPFlag("platform", flags.Lookup("platform")); err != nil {
return err
}
+10 -4
View File
@@ -5,6 +5,7 @@ import (
"strings"
"github.com/anchore/syft/internal/formats/table"
"github.com/anchore/syft/internal/formats/template"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/sbom"
"github.com/hashicorp/go-multierror"
@@ -12,8 +13,8 @@ import (
// makeWriter creates a sbom.Writer for output or returns an error. this will either return a valid writer
// or an error but neither both and if there is no error, sbom.Writer.Close() should be called
func MakeWriter(outputs []string, defaultFile string) (sbom.Writer, error) {
outputOptions, err := parseOutputs(outputs, defaultFile)
func MakeWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Writer, error) {
outputOptions, err := parseOutputs(outputs, defaultFile, templateFilePath)
if err != nil {
return nil, err
}
@@ -27,7 +28,7 @@ func MakeWriter(outputs []string, defaultFile string) (sbom.Writer, error) {
}
// parseOptions utility to parse command-line option strings and retain the existing behavior of default format and file
func parseOutputs(outputs []string, defaultFile string) (out []sbom.WriterOption, errs error) {
func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out []sbom.WriterOption, errs error) {
// always should have one option -- we generally get the default of "table", but just make sure
if len(outputs) == 0 {
outputs = append(outputs, string(table.ID))
@@ -52,10 +53,15 @@ func parseOutputs(outputs []string, defaultFile string) (out []sbom.WriterOption
format := syft.FormatByName(name)
if format == nil {
errs = multierror.Append(errs, fmt.Errorf("bad output format: '%s'", name))
errs = multierror.Append(errs, fmt.Errorf(`unsupported output format "%s", supported formats are: %+v`, name, FormatAliases(syft.FormatIDs()...)))
continue
}
if tmpl, ok := format.(template.OutputFormat); ok {
tmpl.SetTemplatePath(templateFilePath)
format = tmpl
}
out = append(out, sbom.NewWriterOption(format, file))
}
return out, errs
+2 -2
View File
@@ -22,13 +22,13 @@ func TestIsSupportedFormat(t *testing.T) {
{
outputs: []string{"unknown"},
wantErr: func(t assert.TestingT, err error, bla ...interface{}) bool {
return assert.ErrorContains(t, err, "bad output format: 'unknown'")
return assert.ErrorContains(t, err, `unsupported output format "unknown", supported formats are: [`)
},
},
}
for _, tt := range tests {
_, err := MakeWriter(tt.outputs, "")
_, err := MakeWriter(tt.outputs, "", "")
tt.wantErr(t, err)
}
}
+17 -15
View File
@@ -13,13 +13,14 @@ import (
)
const (
packagesExample = ` {{.appName}} {{.command}} alpine:latest a summary of discovered packages
{{.appName}} {{.command}} alpine:latest -o json show all possible cataloging details
{{.appName}} {{.command}} alpine:latest -o cyclonedx show a CycloneDX formatted SBOM
{{.appName}} {{.command}} alpine:latest -o cyclonedx-json show a CycloneDX JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.2 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.2 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
packagesExample = ` {{.appName}} {{.command}} alpine:latest a summary of discovered packages
{{.appName}} {{.command}} alpine:latest -o json show all possible cataloging details
{{.appName}} {{.command}} alpine:latest -o cyclonedx show a CycloneDX formatted SBOM
{{.appName}} {{.command}} alpine:latest -o cyclonedx-json show a CycloneDX JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.2 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.2 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
{{.appName}} {{.command}} alpine:latest -o template -t my_format.tmpl show a SBOM formatted according to given template file
Supports the following image sources:
{{.appName}} {{.command}} yourrepo/yourimage:tag defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry.
@@ -27,15 +28,16 @@ const (
`
schemeHelpHeader = "You can also explicitly specify the scheme to use:"
imageSchemeHelp = ` {{.appName}} {{.command}} docker:yourrepo/yourimage:tag explicitly use the Docker daemon
{{.appName}} {{.command}} podman:yourrepo/yourimage:tag explicitly use the Podman daemon
{{.appName}} {{.command}} registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
{{.appName}} {{.command}} docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
{{.appName}} {{.command}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
{{.appName}} {{.command}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
imageSchemeHelp = ` {{.appName}} {{.command}} docker:yourrepo/yourimage:tag explicitly use the Docker daemon
{{.appName}} {{.command}} podman:yourrepo/yourimage:tag explicitly use the Podman daemon
{{.appName}} {{.command}} registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
{{.appName}} {{.command}} docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
{{.appName}} {{.command}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
{{.appName}} {{.command}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
{{.appName}} {{.command}} singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
`
nonImageSchemeHelp = ` {{.appName}} {{.command}} dir:path/to/yourproject read directly from a path on disk (any directory)
{{.appName}} {{.command}} file:path/to/yourproject/file read directly from a path on disk (any single file)
nonImageSchemeHelp = ` {{.appName}} {{.command}} dir:path/to/yourproject read directly from a path on disk (any directory)
{{.appName}} {{.command}} file:path/to/yourproject/file read directly from a path on disk (any single file)
`
packagesSchemeHelp = "\n" + indent + schemeHelpHeader + "\n" + imageSchemeHelp + nonImageSchemeHelp
+23 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/anchore/syft/internal/anchore"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/formats/template"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/internal/version"
@@ -25,7 +26,12 @@ import (
)
func Run(ctx context.Context, app *config.Application, args []string) error {
writer, err := options.MakeWriter(app.Outputs, app.File)
err := validateOutputOptions(app)
if err != nil {
return err
}
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
if err != nil {
return err
}
@@ -185,3 +191,19 @@ func runPackageSbomUpload(src *source.Source, s sbom.SBOM, app *config.Applicati
return nil
}
func validateOutputOptions(app *config.Application) error {
var usesTemplateOutput bool
for _, o := range app.Outputs {
if o == template.ID.String() {
usesTemplateOutput = true
break
}
}
if usesTemplateOutput && app.OutputTemplatePath == "" {
return fmt.Errorf(`must specify path to template file when using "template" output format`)
}
return nil
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
const powerUserExample = ` {{.appName}} {{.command}} <image>
DEPRECATED - THIS COMMAND WILL BE REMOVED in v1.0.0
Only image sources are supported (e.g. docker: , podman: , docker-archive: , oci: , etc.), the directory source (dir:) is not supported.
Only image sources are supported (e.g. docker: , podman: , docker-archive: , oci: , etc.), the directory source (dir:) is not supported, template outputs are not supported.
All behavior is controlled via application configuration and environment variables (see https://github.com/anchore/syft#configuration)
`
+109 -103
View File
@@ -6,14 +6,14 @@ require (
github.com/CycloneDX/cyclonedx-go v0.5.2
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/acobaugh/osrelease v0.1.0
github.com/adrg/xdg v0.2.1
github.com/adrg/xdg v0.3.3
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/packageurl-go v0.1.1-0.20220428202044-a072fa3cb6d7
github.com/anchore/stereoscope v0.0.0-20220518185348-c97a3c6ffc67
github.com/anchore/stereoscope v0.0.0-20220802140318-49d33a1fb915
github.com/antihax/optional v1.0.0
github.com/bmatcuk/doublestar/v4 v4.0.2
github.com/dustin/go-humanize v1.0.0
@@ -26,6 +26,7 @@ require (
github.com/jinzhu/copier v0.3.2
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/archiver/v3 v3.5.1
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/mitchellh/mapstructure v1.5.0
@@ -37,10 +38,10 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/spdx/tools-golang v0.2.0
github.com/spf13/afero v1.8.2
github.com/spf13/cobra v1.4.0
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.7.1
github.com/stretchr/testify v1.8.0
github.com/vifraa/gopom v0.1.0
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5
github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240
@@ -48,29 +49,28 @@ require (
github.com/x-cray/logrus-prefixed-formatter v0.5.2
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/docker/docker v20.10.12+incompatible
github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839
github.com/in-toto/in-toto-golang v0.3.4-0.20211211042327-af1f9fb822bf
github.com/knqyf263/go-rpmdb v0.0.0-20220530130908-5101b033fa0d
github.com/sigstore/cosign v1.9.0
github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3
github.com/sigstore/sigstore v1.2.1-0.20220424143412-3d41663116d5
github.com/Masterminds/sprig/v3 v3.2.2
github.com/docker/docker v20.10.17+incompatible
github.com/google/go-containerregistry v0.10.0
github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add
github.com/knqyf263/go-rpmdb v0.0.0-20220629110411-9a3bd2ebb923
github.com/sigstore/cosign v1.10.0
github.com/sigstore/rekor v0.9.1
github.com/sigstore/sigstore v1.2.1-0.20220614141825-9c0e2e247545
github.com/vbatts/go-mtree v0.5.0
gopkg.in/yaml.v3 v3.0.1
)
require (
bitbucket.org/creachadair/shell v0.0.6 // indirect
cloud.google.com/go v0.100.2 // indirect
cloud.google.com/go/compute v1.6.1 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/storage v1.22.1 // indirect
github.com/Azure/azure-sdk-for-go v63.3.0+incompatible // indirect
bitbucket.org/creachadair/shell v0.0.7 // indirect
cloud.google.com/go/compute v1.7.0 // indirect
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
@@ -79,78 +79,80 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/CalebQ42/squashfs v0.5.4 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/PaesslerAG/gval v1.0.0 // indirect
github.com/PaesslerAG/jsonpath v0.1.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go-v2 v1.16.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.14.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.9.0 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.11.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.3.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.6 // indirect
github.com/aws/aws-sdk-go-v2 v1.16.5 // indirect
github.com/aws/aws-sdk-go-v2/config v1.15.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.12.5 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.15.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.12.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.10.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.15.0 // indirect
github.com/aws/smithy-go v1.11.2 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220228164355-396b2034c795 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.16.7 // indirect
github.com/aws/smithy-go v1.11.3 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 // indirect
github.com/benbjohnson/clock v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21 // indirect
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/containerd v1.5.10 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
github.com/containerd/containerd v1.5.13 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
github.com/coreos/go-oidc/v3 v3.2.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20210823021906-dc406ceaf94b // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/cli v20.10.12+incompatible // indirect
github.com/docker/distribution v2.8.0+incompatible // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fullstorydev/grpcurl v1.8.2 // indirect
github.com/fullstorydev/grpcurl v1.8.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.2 // indirect
github.com/go-openapi/errors v0.20.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/loads v0.21.1 // indirect
github.com/go-openapi/runtime v0.24.1 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/strfmt v0.21.2 // indirect
github.com/go-openapi/spec v0.20.6 // indirect
github.com/go-openapi/strfmt v0.21.3 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-openapi/validate v0.21.0 // indirect
github.com/go-openapi/validate v0.22.0 // indirect
github.com/go-piv/piv-go v1.9.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.3.0 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -158,34 +160,35 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/certificate-transparency-go v1.1.2 // indirect
github.com/google/go-github/v42 v42.0.0 // indirect
github.com/google/certificate-transparency-go v1.1.3 // indirect
github.com/google/go-github/v45 v45.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/trillian v1.4.0 // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/google/trillian v1.4.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/googleapis/go-type-adapters v1.0.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jedisct1/go-minisign v0.0.0-20210703085342-c1f07ee84431 // indirect
github.com/jhump/protoreflect v1.9.0 // indirect
github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect
github.com/jhump/protoreflect v1.10.3 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/jonboulle/clockwork v0.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.14.2 // indirect
github.com/klauspost/compress v1.15.4 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/letsencrypt/boulder v0.0.0-20220331220046-b23ab962616e // indirect
@@ -197,6 +200,8 @@ require (
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
@@ -205,12 +210,13 @@ require (
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pierrec/lz4/v4 v4.1.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/common v0.34.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -218,25 +224,28 @@ require (
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sigstore/fulcio v0.1.2-0.20220114150912-86a2036f9bc7 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.3.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.1.1 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/sylabs/sif/v2 v2.7.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
github.com/theupdateframework/go-tuf v0.3.0 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/theupdateframework/go-tuf v0.3.1 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
github.com/transparency-dev/merkle v0.0.1 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/urfave/cli v1.22.5 // indirect
github.com/urfave/cli v1.22.7 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/xanzy/go-gitlab v0.68.0 // indirect
github.com/xanzy/go-gitlab v0.69.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
@@ -244,50 +253,47 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/zeebo/errs v1.2.2 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
go.etcd.io/etcd/client/v2 v2.305.4 // indirect
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
go.etcd.io/etcd/etcdctl/v3 v3.5.0 // indirect
go.etcd.io/etcd/etcdutl/v3 v3.5.0 // indirect
go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect
go.etcd.io/etcd/raft/v3 v3.5.0 // indirect
go.etcd.io/etcd/server/v3 v3.5.0 // indirect
go.etcd.io/etcd/tests/v3 v3.5.0 // indirect
go.etcd.io/etcd/v3 v3.5.0 // indirect
go.mongodb.org/mongo-driver v1.8.3 // indirect
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 // indirect
go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/etcdctl/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/etcdutl/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/server/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/tests/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/v3 v3.6.0-alpha.0 // indirect
go.mongodb.org/mongo-driver v1.10.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/contrib v1.3.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
go.opentelemetry.io/otel v0.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
go.opentelemetry.io/otel/metric v0.20.0 // indirect
go.opentelemetry.io/otel/sdk v0.20.0 // indirect
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
go.opentelemetry.io/otel/trace v0.20.0 // indirect
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
go.opentelemetry.io/otel v1.7.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect
go.opentelemetry.io/otel/sdk v1.7.0 // indirect
go.opentelemetry.io/otel/trace v1.7.0 // indirect
go.opentelemetry.io/proto/otlp v0.16.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/api v0.81.0 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/api v0.88.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
google.golang.org/grpc v1.46.2 // indirect
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
k8s.io/api v0.23.5 // indirect
k8s.io/apimachinery v0.23.5 // indirect
k8s.io/client-go v0.23.5 // indirect
@@ -296,17 +302,17 @@ require (
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
knative.dev/pkg v0.0.0-20220325200448-1f7514acd0c2 // indirect
lukechampine.com/uint128 v1.1.1 // indirect
modernc.org/cc/v3 v3.35.22 // indirect
modernc.org/ccgo/v3 v3.15.1 // indirect
modernc.org/libc v1.14.1 // indirect
modernc.org/cc/v3 v3.36.0 // indirect
modernc.org/ccgo/v3 v3.16.6 // indirect
modernc.org/libc v1.16.7 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.0.5 // indirect
modernc.org/memory v1.1.1 // indirect
modernc.org/opt v0.1.1 // indirect
modernc.org/sqlite v1.14.5 // indirect
modernc.org/sqlite v1.17.3 // indirect
modernc.org/strutil v1.1.1 // indirect
modernc.org/token v1.0.0 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/release-utils v0.6.0 // indirect
sigs.k8s.io/release-utils v0.7.1 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
@@ -316,5 +322,5 @@ require (
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/pkg/errors v0.9.1
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
)
+310 -529
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -5,8 +5,11 @@ import (
"fmt"
"path"
"reflect"
"sort"
"strings"
"github.com/anchore/syft/syft/pkg/cataloger"
"github.com/sirupsen/logrus"
"github.com/adrg/xdg"
@@ -38,11 +41,13 @@ type Application struct {
// -q, indicates to not show any status output to stderr (ETUI or logging UI)
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
@@ -54,6 +59,17 @@ type Application struct {
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
}
func (cfg Application) ToCatalogerConfig() cataloger.Config {
return cataloger.Config{
Search: cataloger.SearchConfig{
IncludeIndexedArchives: cfg.Package.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
Scope: cfg.Package.Cataloger.ScopeOpt,
},
Catalogers: cfg.Catalogers,
}
}
func (cfg *Application) LoadAllValues(v *viper.Viper, configPath string) error {
// priority order: viper.Set, flag, env, config, kv, defaults
// flags have already been loaded into viper by command construction
@@ -85,6 +101,16 @@ func (cfg *Application) LoadAllValues(v *viper.Viper, configPath string) error {
}
func (cfg *Application) parseConfigValues() error {
// parse options on this struct
var catalogers []string
for _, c := range cfg.Catalogers {
for _, f := range strings.Split(c, ",") {
catalogers = append(catalogers, strings.TrimSpace(f))
}
}
sort.Strings(catalogers)
cfg.Catalogers = catalogers
// parse application config options
for _, optionFn := range []func() error{
cfg.parseUploadOptions,
@@ -172,6 +198,7 @@ func loadDefaultValues(v *viper.Viper) {
// set the default values for primitive fields in this struct
v.SetDefault("quiet", false)
v.SetDefault("check-for-app-update", true)
v.SetDefault("catalogers", nil)
// for each field in the configuration struct, see if the field implements the defaultValueLoader interface and invoke it if it does
value := reflect.ValueOf(Application{})
-10
View File
@@ -21,13 +21,3 @@ func (cfg pkg) loadDefaultValues(v *viper.Viper) {
func (cfg *pkg) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues()
}
func (cfg pkg) ToConfig() cataloger.Config {
return cataloger.Config{
Search: cataloger.SearchConfig{
IncludeIndexedArchives: cfg.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.SearchUnindexedArchives,
Scope: cfg.Cataloger.ScopeOpt,
},
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ func (cfg registry) loadDefaultValues(v *viper.Viper) {
v.SetDefault("registry.auth", []RegistryCredentials{})
}
// nolint: unparam
// nolint:unparam
func (cfg *registry) parseConfigValues() error {
// there may be additional credentials provided by env var that should be appended to the set of credentials
authority, username, password, token :=
+1 -1
View File
@@ -6,5 +6,5 @@ const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "3.3.0"
JSONSchemaVersion = "3.3.1"
)
@@ -49,7 +49,7 @@ func Test_encodeLicense(t *testing.T) {
},
expected: &cyclonedx.Licenses{
{License: &cyclonedx.License{ID: "MIT"}},
{License: &cyclonedx.License{ID: "GPL-3.0"}},
{License: &cyclonedx.License{ID: "GPL-3.0-only"}},
},
},
{
@@ -60,7 +60,7 @@ func Test_encodeLicense(t *testing.T) {
},
},
expected: &cyclonedx.Licenses{
{License: &cyclonedx.License{ID: "GPL-3.0"}},
{License: &cyclonedx.License{ID: "GPL-3.0-only"}},
},
},
{
@@ -71,7 +71,7 @@ func Test_encodeLicense(t *testing.T) {
},
},
expected: &cyclonedx.Licenses{
{License: &cyclonedx.License{ID: "GPL-2.0"}},
{License: &cyclonedx.License{ID: "GPL-2.0-only"}},
},
},
}
@@ -44,7 +44,7 @@ func Test_License(t *testing.T) {
"GPL-3.0",
},
},
expected: "MIT AND GPL-3.0",
expected: "MIT AND GPL-3.0-only",
},
{
name: "cap insensitive",
@@ -53,7 +53,7 @@ func Test_License(t *testing.T) {
"gpl-3.0",
},
},
expected: "GPL-3.0",
expected: "GPL-3.0-only",
},
{
name: "debian to spdx conversion",
@@ -62,7 +62,7 @@ func Test_License(t *testing.T) {
"GPL-2",
},
},
expected: "GPL-2.0",
expected: "GPL-2.0-only",
},
}
for _, test := range tests {
@@ -6,6 +6,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
//nolint:funlen
func SourceInfo(p pkg.Package) string {
answer := ""
switch p.Type {
@@ -35,6 +36,14 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from rust cargo manifest"
case pkg.PhpComposerPkg:
answer = "acquired package info from PHP composer manifest"
case pkg.CocoapodsPkg:
answer = "acquired package info from installed cocoapods manifest file"
case pkg.ConanPkg:
answer = "acquired package info from conan manifest"
case pkg.PortagePkg:
answer = "acquired package info from portage DB"
case pkg.HackagePkg:
answer = "acquired package info from cabal or stack manifest files"
default:
answer = "acquired package info from the following paths"
}
@@ -150,6 +150,38 @@ func Test_SourceInfo(t *testing.T) {
"from ALPM DB",
},
},
{
input: pkg.Package{
Type: pkg.CocoapodsPkg,
},
expected: []string{
"installed cocoapods manifest file",
},
},
{
input: pkg.Package{
Type: pkg.ConanPkg,
},
expected: []string{
"from conan manifest",
},
},
{
input: pkg.Package{
Type: pkg.PortagePkg,
},
expected: []string{
"from portage DB",
},
},
{
input: pkg.Package{
Type: pkg.HackagePkg,
},
expected: []string{
"from cabal or stack manifest files",
},
},
}
var pkgTypes []pkg.Type
for _, test := range tests {
+25 -1
View File
@@ -63,7 +63,7 @@ func (p *Package) UnmarshalJSON(b []byte) error {
return unpackMetadata(p, unpacker)
}
// nolint:funlen
// nolint:funlen,gocognit,gocyclo
func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error {
p.MetadataType = unpacker.MetadataType
switch p.MetadataType {
@@ -145,12 +145,36 @@ func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error {
return err
}
p.Metadata = payload
case pkg.CocoapodsMetadataType:
var payload pkg.CocoapodsMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.ConanaMetadataType:
var payload pkg.ConanMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.DotnetDepsMetadataType:
var payload pkg.DotnetDepsMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.PortageMetadataType:
var payload pkg.PortageMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.HackageMetadataType:
var payload pkg.HackageMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
default:
log.Warnf("unknown package metadata type=%q for packageID=%q", p.MetadataType, p.ID)
}
@@ -88,7 +88,7 @@
}
},
"schema": {
"version": "3.3.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.0.json"
"version": "3.3.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.1.json"
}
}
@@ -184,7 +184,7 @@
}
},
"schema": {
"version": "3.3.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.0.json"
"version": "3.3.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.1.json"
}
}
@@ -111,7 +111,7 @@
}
},
"schema": {
"version": "3.3.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.0.json"
"version": "3.3.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.3.1.json"
}
}
+49
View File
@@ -0,0 +1,49 @@
package template
import (
"errors"
"fmt"
"os"
"reflect"
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/mitchellh/go-homedir"
)
func makeTemplateExecutor(templateFilePath string) (*template.Template, error) {
if templateFilePath == "" {
return nil, errors.New("no template file: please provide a template path")
}
expandedPathToTemplateFile, err := homedir.Expand(templateFilePath)
if err != nil {
return nil, fmt.Errorf("unable to expand path %s", templateFilePath)
}
templateContents, err := os.ReadFile(expandedPathToTemplateFile)
if err != nil {
return nil, fmt.Errorf("unable to get template content: %w", err)
}
templateName := expandedPathToTemplateFile
tmpl, err := template.New(templateName).Funcs(funcMap).Parse(string(templateContents))
if err != nil {
return nil, fmt.Errorf("unable to parse template: %w", err)
}
return tmpl, nil
}
// These are custom functions available to template authors.
var funcMap = func() template.FuncMap {
f := sprig.HermeticTxtFuncMap()
f["getLastIndex"] = func(collection interface{}) int {
if v := reflect.ValueOf(collection); v.Kind() == reflect.Slice {
return v.Len() - 1
}
return 0
}
return f
}()
+29
View File
@@ -0,0 +1,29 @@
package template
import (
"flag"
"testing"
"github.com/anchore/syft/internal/formats/common/testutils"
"github.com/stretchr/testify/assert"
)
var updateTmpl = flag.Bool("update-tmpl", false, "update the *.golden files for json encoders")
func TestFormatWithOption(t *testing.T) {
f := OutputFormat{}
f.SetTemplatePath("test-fixtures/csv.template")
testutils.AssertEncoderAgainstGoldenSnapshot(t,
f,
testutils.DirectoryInput(t),
*updateTmpl,
)
}
func TestFormatWithoutOptions(t *testing.T) {
f := Format()
err := f.Encode(nil, testutils.DirectoryInput(t))
assert.ErrorContains(t, err, "no template file: please provide a template path")
}
+47
View File
@@ -0,0 +1,47 @@
package template
import (
"io"
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/syft/sbom"
)
const ID sbom.FormatID = "template"
func Format() sbom.Format {
return OutputFormat{}
}
// implementation of sbom.Format interface
// to make use of format options
type OutputFormat struct {
templateFilePath string
}
func (f OutputFormat) ID() sbom.FormatID {
return ID
}
func (f OutputFormat) Decode(reader io.Reader) (*sbom.SBOM, error) {
return nil, sbom.ErrDecodingNotSupported
}
func (f OutputFormat) Encode(output io.Writer, s sbom.SBOM) error {
tmpl, err := makeTemplateExecutor(f.templateFilePath)
if err != nil {
return err
}
doc := syftjson.ToFormatModel(s)
return tmpl.Execute(output, doc)
}
func (f OutputFormat) Validate(reader io.Reader) error {
return sbom.ErrValidationNotSupported
}
// SetTemplatePath sets path for template file
func (f *OutputFormat) SetTemplatePath(filePath string) {
f.templateFilePath = filePath
}
@@ -0,0 +1,4 @@
"Package","Version Installed", "Found by"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
{{- end}}
@@ -0,0 +1,3 @@
"Package","Version Installed", "Found by"
"package-1","1.0.1","the-cataloger-1"
"package-2","2.0.1","the-cataloger-2"
@@ -37,18 +37,6 @@ var licenseIDs = map[string]string{
var versionMatch = regexp.MustCompile(`-([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`)
type LicenseList struct {
Version string `json:"licenseListVersion"`
Licenses []struct {
ID string `json:"licenseId"`
Name string `json:"name"`
Text string `json:"licenseText"`
Deprecated bool `json:"isDeprecatedLicenseId"`
OSIApproved bool `json:"isOsiApproved"`
SeeAlso []string `json:"seeAlso"`
} `json:"licenses"`
}
func main() {
if err := run(); err != nil {
fmt.Println(err.Error())
@@ -102,8 +90,9 @@ func run() error {
return nil
}
// Parsing the provided SPDX license list necessitates a two pass approach.
// The first pass is only related to what SPDX considers the truth. These K:V pairs will never be overwritten.
// Parsing the provided SPDX license list necessitates a three pass approach.
// The first pass is only related to what SPDX considers the truth. We use license info to
// find replacements for deprecated licenses.
// The second pass attempts to generate known short/long version listings for each key.
// For info on some short name conventions see this document:
// https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-short-name.
@@ -111,6 +100,8 @@ func run() error {
// The new keys are then also associated with their relative SPDX value. If a key has already been entered
// we know to ignore it since it came from the first pass which is considered the SPDX source of truth.
// We also sort the licenses for the second pass so that cases like `GPL-1` associate to `GPL-1.0` and not `GPL-1.1`.
// The third pass is for overwriting deprecated licenses with replacements, for example GPL-2.0+ is deprecated
// and now maps to GPL-2.0-or-later.
func processSPDXLicense(result LicenseList) map[string]string {
// first pass build map
var licenseIDs = make(map[string]string)
@@ -122,69 +113,48 @@ func processSPDXLicense(result LicenseList) map[string]string {
licenseIDs[cleanID] = l.ID
}
// The order of variations/permutations of a license ID matters because of we how shuffle its digits,
// that is because the permutation code can generate the same value for two difference licenses,
// for example: The licenses `ABC-1.0` and `ABC-1.1` can both map to `ABC-1`,
// so we need to guarantee the order they are created to avoid mapping them wrongly. So we use a sorted list.
// To overwrite deprecated licenses during the first pass we would later on rely on map order,
// [which in go is not consistent by design](https://stackoverflow.com/a/55925880).
sort.Slice(result.Licenses, func(i, j int) bool {
return result.Licenses[i].ID < result.Licenses[j].ID
})
// second pass build exceptions
// do not overwrite if already exists
// second pass to build exceptions and replacements
replaced := strset.New()
for _, l := range result.Licenses {
var multipleID []string
cleanID := strings.ToLower(l.ID)
var replacement *License
if l.Deprecated {
replacement = result.findReplacementLicense(l)
if replacement != nil {
licenseIDs[cleanID] = replacement.ID
}
}
multipleID = append(multipleID, buildLicensePermutations(cleanID)...)
for _, id := range multipleID {
if _, exists := licenseIDs[id]; !exists {
licenseIDs[id] = l.ID
// don't make replacements for IDs that have already been replaced. Since we have a sorted license list
// the earliest replacement is correct (any future replacements are not.
// e.g. replace lgpl-2 with LGPL-2.1-only is wrong, but with LGPL-2.0-only is correct)
if replacement == nil || replaced.Has(id) {
if _, exists := licenseIDs[id]; !exists {
licenseIDs[id] = l.ID
}
} else {
// a useful debugging line during builds
log.Printf("replacing %s with %s\n", id, replacement.ID)
licenseIDs[id] = replacement.ID
replaced.Add(id)
}
}
}
return licenseIDs
}
func buildLicensePermutations(license string) (perms []string) {
lv := findLicenseVersion(license)
vp := versionPermutations(lv)
version := strings.Join(lv, ".")
for _, p := range vp {
perms = append(perms, strings.Replace(license, version, p, 1))
}
return perms
}
func findLicenseVersion(license string) (version []string) {
versionList := versionMatch.FindAllStringSubmatch(license, -1)
if len(versionList) == 0 {
return version
}
for i, v := range versionList[0] {
if v != "" && i != 0 {
version = append(version, v)
}
}
return version
}
func versionPermutations(version []string) []string {
ver := append([]string(nil), version...)
perms := strset.New()
for i := 1; i <= 3; i++ {
if len(ver) < i+1 {
ver = append(ver, "0")
}
perm := strings.Join(ver[:i], ".")
badCount := strings.Count(perm, "0") + strings.Count(perm, ".")
if badCount != len(perm) {
perms.Add(perm)
}
}
return perms.List()
}
File diff suppressed because it is too large Load Diff
+104
View File
@@ -0,0 +1,104 @@
package main
import (
"strings"
"github.com/scylladb/go-set/strset"
)
type LicenseList struct {
Version string `json:"licenseListVersion"`
Licenses []License `json:"licenses"`
}
type License struct {
ID string `json:"licenseId"`
Name string `json:"name"`
Text string `json:"licenseText"`
Deprecated bool `json:"isDeprecatedLicenseId"`
OSIApproved bool `json:"isOsiApproved"`
SeeAlso []string `json:"seeAlso"`
}
func (l License) canReplace(other License) bool {
if l.Deprecated {
return false
}
if l.Name != other.Name {
return false
}
if l.OSIApproved != other.OSIApproved {
return false
}
if len(l.SeeAlso) != len(other.SeeAlso) {
return false
}
for i, sa := range l.SeeAlso {
if sa != other.SeeAlso[i] {
return false
}
}
return l.ID != other.ID
}
func (ll LicenseList) findReplacementLicense(deprecated License) *License {
for _, l := range ll.Licenses {
if l.canReplace(deprecated) {
return &l
}
}
return nil
}
func buildLicensePermutations(license string) (perms []string) {
lv := findLicenseVersion(license)
vp := versionPermutations(lv)
version := strings.Join(lv, ".")
for _, p := range vp {
perms = append(perms, strings.Replace(license, version, p, 1))
}
return perms
}
func findLicenseVersion(license string) (version []string) {
versionList := versionMatch.FindAllStringSubmatch(license, -1)
if len(versionList) == 0 {
return version
}
for i, v := range versionList[0] {
if v != "" && i != 0 {
version = append(version, v)
}
}
return version
}
func versionPermutations(version []string) []string {
ver := append([]string(nil), version...)
perms := strset.New()
for i := 1; i <= 3; i++ {
if len(ver) < i+1 {
ver = append(ver, "0")
}
perm := strings.Join(ver[:i], ".")
badCount := strings.Count(perm, "0") + strings.Count(perm, ".")
if badCount != len(perm) {
perms.Add(perm)
}
}
return perms.List()
}
@@ -0,0 +1,220 @@
package main
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
var (
license1 = License{
ID: "ABC-1.0+",
Name: "The ABC License 1.0",
Deprecated: true,
}
license2 = License{
ID: "ABC-1.0-Or-later",
Name: "The ABC License 1.0",
}
license3 = License{
ID: "ABC-1.0",
Name: "The ABC License 1.0 Only",
Deprecated: true,
}
license4 = License{
ID: "ABC-1.0-Only",
Name: "The ABC License 1.0 Only",
}
license5 = License{
ID: "Duh-1.0",
Name: "The Duh License 1.0",
Deprecated: true,
}
license6 = License{
ID: "Duh-1.0-duh",
Name: "The Duh License 1.0",
Deprecated: true,
}
)
func TestLicense_canReplace(t *testing.T) {
tests := []struct {
l1, l2 License
expected bool
}{
{license1, license2, false},
{license2, license1, true},
{license2, license3, false},
{license3, license2, false},
}
for _, tt := range tests {
assert.Equal(t, tt.expected, tt.l1.canReplace(tt.l2))
}
}
func TestLicensePermutations(t *testing.T) {
var tests = []struct {
shortName string
permutations []string
}{
{
"GPL-1-only",
[]string{
"GPL-1-only",
"GPL-1.0-only",
"GPL-1.0.0-only",
},
},
{
"GPL-2",
[]string{
"GPL-2",
"GPL-2.0",
"GPL-2.0.0",
},
},
{
"GPL-2.0+",
[]string{
"GPL-2+",
"GPL-2.0+",
"GPL-2.0.0+",
},
},
{
"GPL-3.0.0-or-later",
[]string{
"GPL-3-or-later",
"GPL-3.0-or-later",
"GPL-3.0.0-or-later",
},
},
{
"abc-1.1",
[]string{
"abc-1",
"abc-1.1",
"abc-1.1.0",
},
},
{
"oldap-2.0",
[]string{
"oldap-2",
"oldap-2.0",
"oldap-2.0.0",
},
},
}
for _, test := range tests {
t.Run(test.shortName, func(t *testing.T) {
perms := buildLicensePermutations(test.shortName)
assert.ElementsMatch(t, test.permutations, perms)
})
}
}
func TestVersionPermutations(t *testing.T) {
var tests = []struct {
version []string
permutations []string
}{
{
[]string{"1", "0"},
[]string{"1", "1.0", "1.0.0"},
},
{
[]string{"2"},
[]string{"2", "2.0", "2.0.0"},
},
{
[]string{"2", "0"},
[]string{"2", "2.0", "2.0.0"},
},
{
[]string{"3", "0", "0"},
[]string{"3", "3.0", "3.0.0"},
},
{
[]string{"0", "3"},
[]string{"0.3", "0.3.0"},
},
{
[]string{"0", "0", "3"},
[]string{"0.0.3"},
},
}
for _, test := range tests {
t.Run(strings.Join(test.version, "."), func(t *testing.T) {
got := versionPermutations(test.version)
assert.ElementsMatch(t, test.permutations, got)
})
}
}
func TestFindLicenseVersion(t *testing.T) {
var tests = []struct {
license string
version []string
}{
{
"GPL-1.0-only",
[]string{"1", "0"},
},
{
"GPL-2.0",
[]string{"2", "0"},
},
{
"GPL-2.0.0",
[]string{"2", "0", "0"},
},
{
"GPL-2",
[]string{"2"},
},
{
"bzip2-1",
[]string{"1"},
},
{
"php-3.01",
[]string{"3", "01"},
},
{
"oldap-2.0",
[]string{"2", "0"},
},
}
for _, test := range tests {
t.Run(test.license, func(t *testing.T) {
got := findLicenseVersion(test.license)
assert.Equal(t, test.version, got)
})
}
}
func Test_findReplacementLicense(t *testing.T) {
tests := []struct {
l License
expected *License
licenses LicenseList
}{
{license1, nil, LicenseList{}},
{license1, nil, LicenseList{Licenses: []License{license3}}},
{license1, &license2, LicenseList{Licenses: []License{license2, license3}}},
{license1, &license2, LicenseList{Licenses: []License{license2, license3, license4, license5}}},
}
for _, tt := range tests {
assert.Equal(t, tt.expected, tt.licenses.findReplacementLicense(tt.l))
}
}
File diff suppressed because it is too large Load Diff
+33 -33
View File
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at 2022-05-09 10:01:37.625063 -0400 EDT m=+0.423632834
// This file was generated by robots at 2022-08-02 12:46:45.90647 -0400 EDT m=+0.327870639
// using data from https://spdx.org/licenses/licenses.json
package spdxlicense
@@ -524,20 +524,20 @@ var licenseIDs = map[string]string{
"glulxe": "Glulxe",
"glwtpl": "GLWTPL",
"gnuplot": "gnuplot",
"gpl-1": "GPL-1.0",
"gpl-1+": "GPL-1.0+",
"gpl-1": "GPL-1.0-only",
"gpl-1+": "GPL-1.0-or-later",
"gpl-1-only": "GPL-1.0-only",
"gpl-1-or-later": "GPL-1.0-or-later",
"gpl-1.0": "GPL-1.0",
"gpl-1.0+": "GPL-1.0+",
"gpl-1.0": "GPL-1.0-only",
"gpl-1.0+": "GPL-1.0-or-later",
"gpl-1.0-only": "GPL-1.0-only",
"gpl-1.0-or-later": "GPL-1.0-or-later",
"gpl-1.0.0": "GPL-1.0",
"gpl-1.0.0+": "GPL-1.0+",
"gpl-1.0.0": "GPL-1.0-only",
"gpl-1.0.0+": "GPL-1.0-or-later",
"gpl-1.0.0-only": "GPL-1.0-only",
"gpl-1.0.0-or-later": "GPL-1.0-or-later",
"gpl-2": "GPL-2.0",
"gpl-2+": "GPL-2.0+",
"gpl-2": "GPL-2.0-only",
"gpl-2+": "GPL-2.0-or-later",
"gpl-2-only": "GPL-2.0-only",
"gpl-2-or-later": "GPL-2.0-or-later",
"gpl-2-with-autoconf-exception": "GPL-2.0-with-autoconf-exception",
@@ -545,8 +545,8 @@ var licenseIDs = map[string]string{
"gpl-2-with-classpath-exception": "GPL-2.0-with-classpath-exception",
"gpl-2-with-font-exception": "GPL-2.0-with-font-exception",
"gpl-2-with-gcc-exception": "GPL-2.0-with-GCC-exception",
"gpl-2.0": "GPL-2.0",
"gpl-2.0+": "GPL-2.0+",
"gpl-2.0": "GPL-2.0-only",
"gpl-2.0+": "GPL-2.0-or-later",
"gpl-2.0-only": "GPL-2.0-only",
"gpl-2.0-or-later": "GPL-2.0-or-later",
"gpl-2.0-with-autoconf-exception": "GPL-2.0-with-autoconf-exception",
@@ -554,8 +554,8 @@ var licenseIDs = map[string]string{
"gpl-2.0-with-classpath-exception": "GPL-2.0-with-classpath-exception",
"gpl-2.0-with-font-exception": "GPL-2.0-with-font-exception",
"gpl-2.0-with-gcc-exception": "GPL-2.0-with-GCC-exception",
"gpl-2.0.0": "GPL-2.0",
"gpl-2.0.0+": "GPL-2.0+",
"gpl-2.0.0": "GPL-2.0-only",
"gpl-2.0.0+": "GPL-2.0-or-later",
"gpl-2.0.0-only": "GPL-2.0-only",
"gpl-2.0.0-or-later": "GPL-2.0-or-later",
"gpl-2.0.0-with-autoconf-exception": "GPL-2.0-with-autoconf-exception",
@@ -563,20 +563,20 @@ var licenseIDs = map[string]string{
"gpl-2.0.0-with-classpath-exception": "GPL-2.0-with-classpath-exception",
"gpl-2.0.0-with-font-exception": "GPL-2.0-with-font-exception",
"gpl-2.0.0-with-gcc-exception": "GPL-2.0-with-GCC-exception",
"gpl-3": "GPL-3.0",
"gpl-3+": "GPL-3.0+",
"gpl-3": "GPL-3.0-only",
"gpl-3+": "GPL-3.0-or-later",
"gpl-3-only": "GPL-3.0-only",
"gpl-3-or-later": "GPL-3.0-or-later",
"gpl-3-with-autoconf-exception": "GPL-3.0-with-autoconf-exception",
"gpl-3-with-gcc-exception": "GPL-3.0-with-GCC-exception",
"gpl-3.0": "GPL-3.0",
"gpl-3.0+": "GPL-3.0+",
"gpl-3.0": "GPL-3.0-only",
"gpl-3.0+": "GPL-3.0-or-later",
"gpl-3.0-only": "GPL-3.0-only",
"gpl-3.0-or-later": "GPL-3.0-or-later",
"gpl-3.0-with-autoconf-exception": "GPL-3.0-with-autoconf-exception",
"gpl-3.0-with-gcc-exception": "GPL-3.0-with-GCC-exception",
"gpl-3.0.0": "GPL-3.0",
"gpl-3.0.0+": "GPL-3.0+",
"gpl-3.0.0": "GPL-3.0-only",
"gpl-3.0.0+": "GPL-3.0-or-later",
"gpl-3.0.0-only": "GPL-3.0-only",
"gpl-3.0.0-or-later": "GPL-3.0-or-later",
"gpl-3.0.0-with-autoconf-exception": "GPL-3.0-with-autoconf-exception",
@@ -622,36 +622,36 @@ var licenseIDs = map[string]string{
"lal-1.3.0": "LAL-1.3",
"latex2e": "Latex2e",
"leptonica": "Leptonica",
"lgpl-2": "LGPL-2.0",
"lgpl-2+": "LGPL-2.0+",
"lgpl-2": "LGPL-2.1-only",
"lgpl-2+": "LGPL-2.0-or-later",
"lgpl-2-only": "LGPL-2.0-only",
"lgpl-2-or-later": "LGPL-2.0-or-later",
"lgpl-2.0": "LGPL-2.0",
"lgpl-2.0+": "LGPL-2.0+",
"lgpl-2.0": "LGPL-2.0-only",
"lgpl-2.0+": "LGPL-2.0-or-later",
"lgpl-2.0-only": "LGPL-2.0-only",
"lgpl-2.0-or-later": "LGPL-2.0-or-later",
"lgpl-2.0.0": "LGPL-2.0",
"lgpl-2.0.0+": "LGPL-2.0+",
"lgpl-2.0.0": "LGPL-2.0-only",
"lgpl-2.0.0+": "LGPL-2.0-or-later",
"lgpl-2.0.0-only": "LGPL-2.0-only",
"lgpl-2.0.0-or-later": "LGPL-2.0-or-later",
"lgpl-2.1": "LGPL-2.1",
"lgpl-2.1": "LGPL-2.1-only",
"lgpl-2.1+": "LGPL-2.1+",
"lgpl-2.1-only": "LGPL-2.1-only",
"lgpl-2.1-or-later": "LGPL-2.1-or-later",
"lgpl-2.1.0": "LGPL-2.1",
"lgpl-2.1.0": "LGPL-2.1-only",
"lgpl-2.1.0+": "LGPL-2.1+",
"lgpl-2.1.0-only": "LGPL-2.1-only",
"lgpl-2.1.0-or-later": "LGPL-2.1-or-later",
"lgpl-3": "LGPL-3.0",
"lgpl-3+": "LGPL-3.0+",
"lgpl-3": "LGPL-3.0-only",
"lgpl-3+": "LGPL-3.0-or-later",
"lgpl-3-only": "LGPL-3.0-only",
"lgpl-3-or-later": "LGPL-3.0-or-later",
"lgpl-3.0": "LGPL-3.0",
"lgpl-3.0+": "LGPL-3.0+",
"lgpl-3.0": "LGPL-3.0-only",
"lgpl-3.0+": "LGPL-3.0-or-later",
"lgpl-3.0-only": "LGPL-3.0-only",
"lgpl-3.0-or-later": "LGPL-3.0-or-later",
"lgpl-3.0.0": "LGPL-3.0",
"lgpl-3.0.0+": "LGPL-3.0+",
"lgpl-3.0.0": "LGPL-3.0-only",
"lgpl-3.0.0+": "LGPL-3.0-or-later",
"lgpl-3.0.0-only": "LGPL-3.0-only",
"lgpl-3.0.0-or-later": "LGPL-3.0-or-later",
"lgpllr": "LGPLLR",
+2 -1
View File
@@ -10,6 +10,7 @@ func TestLicenceListIDs(t *testing.T) {
// do a sanity check on the generated data
assert.Equal(t, "0BSD", licenseIDs["0bsd"])
assert.Equal(t, "ZPL-2.1", licenseIDs["zpl-2.1"])
assert.Equal(t, "GPL-2.0", licenseIDs["gpl-2"])
assert.Equal(t, "GPL-2.0-only", licenseIDs["gpl-2"])
assert.Equal(t, "GPL-2.0-or-later", licenseIDs["gpl-2+"])
assert.NotEmpty(t, Version)
}
+2 -2
View File
@@ -17,11 +17,11 @@ func TestIDParse(t *testing.T) {
},
{
"GPL-2",
"GPL-2.0",
"GPL-2.0-only",
},
{
"GPL-2+",
"GPL-2.0+",
"GPL-2.0-or-later",
},
{
"GPL-3.0.0-or-later",
+2 -2
View File
@@ -25,7 +25,7 @@ Given a version number format `MODEL.REVISION.ADDITION`:
When adding a new `pkg.*Metadata` that is assigned to the `pkg.Package.Metadata` struct field it is important that a few things
are done:
- a new integration test case is added to `test/integration/pkg_cases_test.go` that exercises the new package type with the new metadata
- a new integration test case is added to `test/integration/catalog_packages_cases_test.go` that exercises the new package type with the new metadata
- the new metadata struct is added to the `artifactMetadataContainer` struct within `schema/json/generate.go`
## Generating a New Schema
@@ -36,4 +36,4 @@ Create the new schema by running `cd schema/json && go run generate.go` (note yo
- If there is an existing schema for the given version and the new schema matches the existing schema, no action is taken
- If there is an existing schema for the given version and the new schema **does not** match the existing schema, an error is shown indicating to increment the version appropriately (see the "Versioning" section)
***Note: never delete a JSON schema and never change an existing JSON schema once it has been published in a release!*** Only add new schemas with a newly incremented version. All previous schema files must be stored in the `schema/json/` directory.
***Note: never delete a JSON schema and never change an existing JSON schema once it has been published in a release!*** Only add new schemas with a newly incremented version. All previous schema files must be stored in the `schema/json/` directory.
+14 -13
View File
@@ -27,19 +27,20 @@ can be extended to include specific package metadata struct shapes in the future
// When a new package metadata definition is created it will need to be manually added here. The variable name does
// not matter as long as it is exported.
type artifactMetadataContainer struct {
Apk pkg.ApkMetadata
Alpm pkg.AlpmMetadata
Dpkg pkg.DpkgMetadata
Gem pkg.GemMetadata
Java pkg.JavaMetadata
Npm pkg.NpmPackageJSONMetadata
Python pkg.PythonPackageMetadata
Rpm pkg.RpmdbMetadata
Cargo pkg.CargoPackageMetadata
Go pkg.GolangBinMetadata
Php pkg.PhpComposerJSONMetadata
Dart pkg.DartPubMetadata
Dotnet pkg.DotnetDepsMetadata
Apk pkg.ApkMetadata
Alpm pkg.AlpmMetadata
Dpkg pkg.DpkgMetadata
Gem pkg.GemMetadata
Java pkg.JavaMetadata
Npm pkg.NpmPackageJSONMetadata
Python pkg.PythonPackageMetadata
Rpm pkg.RpmdbMetadata
Cargo pkg.CargoPackageMetadata
Go pkg.GolangBinMetadata
Php pkg.PhpComposerJSONMetadata
Dart pkg.DartPubMetadata
Dotnet pkg.DotnetDepsMetadata
Portage pkg.PortageMetadata
}
func main() {
File diff suppressed because it is too large Load Diff
+5
View File
@@ -11,6 +11,7 @@ import (
"github.com/anchore/syft/internal/formats/spdx22tagvalue"
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/internal/formats/table"
"github.com/anchore/syft/internal/formats/template"
"github.com/anchore/syft/internal/formats/text"
"github.com/anchore/syft/syft/sbom"
)
@@ -25,6 +26,7 @@ const (
GitHubID = github.ID
SPDXTagValueFormatID = spdx22tagvalue.ID
SPDXJSONFormatID = spdx22json.ID
TemplateFormatID = template.ID
)
var formats []sbom.Format
@@ -39,6 +41,7 @@ func init() {
spdx22json.Format(),
table.Format(),
text.Format(),
template.Format(),
}
}
@@ -84,6 +87,8 @@ func FormatByName(name string) sbom.Format {
return FormatByID(table.ID)
case "text":
return FormatByID(text.ID)
case "template":
FormatByID(template.ID)
}
return nil
+6
View File
@@ -13,6 +13,7 @@ import (
"github.com/anchore/syft/internal/formats/spdx22tagvalue"
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/internal/formats/table"
"github.com/anchore/syft/internal/formats/template"
"github.com/anchore/syft/internal/formats/text"
"github.com/anchore/syft/syft/sbom"
"github.com/stretchr/testify/require"
@@ -181,6 +182,11 @@ func TestFormatByName(t *testing.T) {
name: "github-json",
want: github.ID,
},
{
name: "template",
want: template.ID,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+4
View File
@@ -64,6 +64,10 @@ func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Catalog, []
return nil, nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme)
}
if cataloger.RequestedAllCatalogers(cfg) {
catalogers = cataloger.AllCatalogers(cfg)
}
catalog, relationships, err := cataloger.Catalog(resolver, release, catalogers...)
if err != nil {
return nil, nil, nil, err
+72 -5
View File
@@ -6,24 +6,33 @@ catalogers defined in child packages as well as the interface definition to impl
package cataloger
import (
"strings"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/alpm"
"github.com/anchore/syft/syft/pkg/cataloger/apkdb"
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
"github.com/anchore/syft/syft/pkg/cataloger/dart"
"github.com/anchore/syft/syft/pkg/cataloger/deb"
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
"github.com/anchore/syft/syft/pkg/cataloger/golang"
"github.com/anchore/syft/syft/pkg/cataloger/haskell"
"github.com/anchore/syft/syft/pkg/cataloger/java"
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
"github.com/anchore/syft/syft/pkg/cataloger/php"
"github.com/anchore/syft/syft/pkg/cataloger/portage"
"github.com/anchore/syft/syft/pkg/cataloger/python"
"github.com/anchore/syft/syft/pkg/cataloger/rpmdb"
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
"github.com/anchore/syft/syft/pkg/cataloger/rust"
"github.com/anchore/syft/syft/pkg/cataloger/swift"
"github.com/anchore/syft/syft/source"
)
const AllCatalogersPattern = "all"
// Cataloger describes behavior for an object to participate in parsing container image or file system
// contents for the purpose of discovering Packages. Each concrete implementation should focus on discovering Packages
// for a specific Package Type or ecosystem.
@@ -36,7 +45,7 @@ type Cataloger interface {
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
func ImageCatalogers(cfg Config) []Cataloger {
return []Cataloger{
return filterCatalogers([]Cataloger{
alpm.NewAlpmdbCataloger(),
ruby.NewGemSpecCataloger(),
python.NewPythonPackageCataloger(),
@@ -48,12 +57,13 @@ func ImageCatalogers(cfg Config) []Cataloger {
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(),
dotnet.NewDotnetDepsCataloger(),
}
portage.NewPortageCataloger(),
}, cfg.Catalogers)
}
// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
func DirectoryCatalogers(cfg Config) []Cataloger {
return []Cataloger{
return filterCatalogers([]Cataloger{
alpm.NewAlpmdbCataloger(),
ruby.NewGemFileLockCataloger(),
python.NewPythonIndexCataloger(),
@@ -63,18 +73,23 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(),
java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(),
golang.NewGoModFileCataloger(),
rust.NewCargoLockCataloger(),
dart.NewPubspecLockCataloger(),
dotnet.NewDotnetDepsCataloger(),
}
swift.NewCocoapodsCataloger(),
cpp.NewConanfileCataloger(),
portage.NewPortageCataloger(),
haskell.NewHackageCataloger(),
}, cfg.Catalogers)
}
// AllCatalogers returns all implemented catalogers
func AllCatalogers(cfg Config) []Cataloger {
return []Cataloger{
return filterCatalogers([]Cataloger{
alpm.NewAlpmdbCataloger(),
ruby.NewGemFileLockCataloger(),
ruby.NewGemSpecCataloger(),
@@ -85,11 +100,63 @@ func AllCatalogers(cfg Config) []Cataloger {
deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(),
java.NewJavaCataloger(cfg.Java()),
java.NewJavaPomCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(),
golang.NewGoModFileCataloger(),
rust.NewCargoLockCataloger(),
rust.NewRustAuditBinaryCataloger(),
dart.NewPubspecLockCataloger(),
dotnet.NewDotnetDepsCataloger(),
php.NewPHPComposerInstalledCataloger(),
php.NewPHPComposerLockCataloger(),
swift.NewCocoapodsCataloger(),
cpp.NewConanfileCataloger(),
portage.NewPortageCataloger(),
haskell.NewHackageCataloger(),
}, cfg.Catalogers)
}
func RequestedAllCatalogers(cfg Config) bool {
for _, enableCatalogerPattern := range cfg.Catalogers {
if enableCatalogerPattern == AllCatalogersPattern {
return true
}
}
return false
}
func filterCatalogers(catalogers []Cataloger, enabledCatalogerPatterns []string) []Cataloger {
// if cataloger is not set, all applicable catalogers are enabled by default
if len(enabledCatalogerPatterns) == 0 {
return catalogers
}
for _, enableCatalogerPattern := range enabledCatalogerPatterns {
if enableCatalogerPattern == AllCatalogersPattern {
return catalogers
}
}
var keepCatalogers []Cataloger
for _, cataloger := range catalogers {
if contains(enabledCatalogerPatterns, cataloger.Name()) {
keepCatalogers = append(keepCatalogers, cataloger)
continue
}
log.Infof("skipping cataloger %q", cataloger.Name())
}
return keepCatalogers
}
func contains(enabledPartial []string, catalogerName string) bool {
catalogerName = strings.TrimSuffix(catalogerName, "-cataloger")
for _, partial := range enabledPartial {
partial = strings.TrimSuffix(partial, "-cataloger")
if partial == "" {
continue
}
if strings.Contains(catalogerName, partial) {
return true
}
}
return false
}
+201
View File
@@ -0,0 +1,201 @@
package cataloger
import (
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert"
"testing"
)
var _ Cataloger = (*dummy)(nil)
type dummy struct {
name string
}
func (d dummy) Name() string {
return d.name
}
func (d dummy) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
panic("not implemented")
}
func Test_filterCatalogers(t *testing.T) {
tests := []struct {
name string
patterns []string
catalogers []string
want []string
}{
{
name: "no filtering",
patterns: nil,
catalogers: []string{
"ruby-gemspec-cataloger",
"python-package-cataloger",
"php-composer-installed-cataloger",
"javascript-package-cataloger",
"dpkgdb-cataloger",
"rpmdb-cataloger",
"java-cataloger",
"apkdb-cataloger",
"go-module-binary-cataloger",
},
want: []string{
"ruby-gemspec-cataloger",
"python-package-cataloger",
"php-composer-installed-cataloger",
"javascript-package-cataloger",
"dpkgdb-cataloger",
"rpmdb-cataloger",
"java-cataloger",
"apkdb-cataloger",
"go-module-binary-cataloger",
},
},
{
name: "exact name match",
patterns: []string{
"rpmdb-cataloger",
"javascript-package-cataloger",
},
catalogers: []string{
"ruby-gemspec-cataloger",
"python-package-cataloger",
"php-composer-installed-cataloger",
"javascript-package-cataloger",
"dpkgdb-cataloger",
"rpmdb-cataloger",
"java-cataloger",
"apkdb-cataloger",
"go-module-binary-cataloger",
},
want: []string{
"javascript-package-cataloger",
"rpmdb-cataloger",
},
},
{
name: "partial name match",
patterns: []string{
"ruby",
"installed",
},
catalogers: []string{
"ruby-gemspec-cataloger",
"ruby-gemfile-cataloger",
"python-package-cataloger",
"php-composer-installed-cataloger",
"javascript-package-cataloger",
"dpkgdb-cataloger",
"rpmdb-cataloger",
"java-cataloger",
"apkdb-cataloger",
"go-module-binary-cataloger",
},
want: []string{
"php-composer-installed-cataloger",
"ruby-gemspec-cataloger",
"ruby-gemfile-cataloger",
},
},
{
name: "ignore 'cataloger' keyword",
patterns: []string{
"cataloger",
},
catalogers: []string{
"ruby-gemspec-cataloger",
"ruby-gemfile-cataloger",
"python-package-cataloger",
"php-composer-installed-cataloger",
"javascript-package-cataloger",
"dpkgdb-cataloger",
"rpmdb-cataloger",
"java-cataloger",
"apkdb-cataloger",
"go-module-binary-cataloger",
},
want: []string{},
},
{
name: "only some patterns match",
patterns: []string{
"cataloger",
"go-module",
},
catalogers: []string{
"ruby-gemspec-cataloger",
"ruby-gemfile-cataloger",
"python-package-cataloger",
"php-composer-installed-cataloger",
"javascript-package-cataloger",
"dpkgdb-cataloger",
"rpmdb-cataloger",
"java-cataloger",
"apkdb-cataloger",
"go-module-binary-cataloger",
},
want: []string{
"go-module-binary-cataloger",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var catalogers []Cataloger
for _, n := range tt.catalogers {
catalogers = append(catalogers, dummy{name: n})
}
got := filterCatalogers(catalogers, tt.patterns)
var gotNames []string
for _, g := range got {
gotNames = append(gotNames, g.Name())
}
assert.ElementsMatch(t, tt.want, gotNames)
})
}
}
func Test_contains(t *testing.T) {
type args struct {
}
tests := []struct {
name string
enabledCatalogers []string
catalogerName string
want bool
}{
{
name: "keep exact match",
enabledCatalogers: []string{
"php-composer-installed-cataloger",
},
catalogerName: "php-composer-installed-cataloger",
want: true,
},
{
name: "match substring",
enabledCatalogers: []string{
"python",
},
catalogerName: "python-package-cataloger",
want: true,
},
{
name: "dont match on 'cataloger'",
enabledCatalogers: []string{
"cataloger",
},
catalogerName: "python-package-cataloger",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, contains(tt.enabledCatalogers, tt.catalogerName))
})
}
}
+2 -1
View File
@@ -5,7 +5,8 @@ import (
)
type Config struct {
Search SearchConfig
Search SearchConfig
Catalogers []string
}
func DefaultConfig() Config {
+14
View File
@@ -0,0 +1,14 @@
package cpp
import (
"github.com/anchore/syft/syft/pkg/cataloger/common"
)
// NewConanfileCataloger returns a new C++ Conanfile cataloger object.
func NewConanfileCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/conanfile.txt": parseConanfile,
}
return common.NewGenericCataloger(nil, globParsers, "conan-cataloger")
}
+60
View File
@@ -0,0 +1,60 @@
package cpp
import (
"bufio"
"errors"
"fmt"
"io"
"strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
)
// integrity check
var _ common.ParserFn = parseConanfile
type Conanfile struct {
Requires []string `toml:"requires"`
}
// parseConanfile is a parser function for conanfile.txt contents, returning all packages discovered.
func parseConanfile(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
r := bufio.NewReader(reader)
inRequirements := false
pkgs := []*pkg.Package{}
for {
line, err := r.ReadString('\n')
switch {
case errors.Is(io.EOF, err):
return pkgs, nil, nil
case err != nil:
return nil, nil, fmt.Errorf("failed to parse conanfile.txt file: %w", err)
}
switch {
case strings.Contains(line, "[requires]"):
inRequirements = true
case strings.ContainsAny(line, "[]#"):
inRequirements = false
}
splits := strings.Split(strings.TrimSpace(line), "/")
if len(splits) < 2 || !inRequirements {
continue
}
pkgName, pkgVersion := splits[0], splits[1]
pkgs = append(pkgs, &pkg.Package{
Name: pkgName,
Version: pkgVersion,
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: pkgName,
Version: pkgVersion,
},
})
}
}
@@ -0,0 +1,96 @@
package cpp
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
)
func TestParseConanfile(t *testing.T) {
expected := []*pkg.Package{
{
Name: "catch2",
Version: "2.13.8",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "catch2",
Version: "2.13.8",
},
},
{
Name: "docopt.cpp",
Version: "0.6.3",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "docopt.cpp",
Version: "0.6.3",
},
},
{
Name: "fmt",
Version: "8.1.1",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "fmt",
Version: "8.1.1",
},
},
{
Name: "spdlog",
Version: "1.9.2",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "spdlog",
Version: "1.9.2",
},
},
{
Name: "sdl",
Version: "2.0.20",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "sdl",
Version: "2.0.20",
},
},
{
Name: "fltk",
Version: "1.3.8",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "fltk",
Version: "1.3.8",
},
},
}
fixture, err := os.Open("test-fixtures/conanfile.txt")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
// TODO: no relationships are under test yet
actual, _, err := parseConanfile(fixture.Name(), fixture)
if err != nil {
t.Error(err)
}
differences := deep.Equal(expected, actual)
if differences != nil {
t.Errorf("returned package list differed from expectation: %+v", differences)
}
}
@@ -0,0 +1,12 @@
# Docs at https://docs.conan.io/en/latest/reference/conanfile_txt.html
[requires]
catch2/2.13.8
docopt.cpp/0.6.3
fmt/8.1.1
spdlog/1.9.2
sdl/2.0.20
fltk/1.3.8
[generators]
cmake_find_package_multi
+2 -31
View File
@@ -4,15 +4,13 @@ Package golang provides a concrete Cataloger implementation for go.mod files.
package golang
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
"github.com/anchore/syft/syft/source"
)
@@ -46,7 +44,7 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []arti
continue
}
reader, err := getUnionReader(readerCloser)
reader, err := unionreader.GetUnionReader(readerCloser)
if err != nil {
return nil, nil, err
}
@@ -61,30 +59,3 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []arti
return pkgs, nil, nil
}
func getUnionReader(readerCloser io.ReadCloser) (unionReader, error) {
reader, ok := readerCloser.(unionReader)
if ok {
return reader, nil
}
log.Debugf("golang cataloger: unable to use stereoscope file, reading entire contents")
b, err := ioutil.ReadAll(readerCloser)
if err != nil {
return nil, fmt.Errorf("unable to read contents from go binary: %w", err)
}
bytesReader := bytes.NewReader(b)
reader = struct {
io.ReadCloser
io.ReaderAt
io.Seeker
}{
ReadCloser: io.NopCloser(bytesReader),
ReaderAt: bytesReader,
Seeker: bytesReader,
}
return reader, nil
}
+21 -3
View File
@@ -10,11 +10,13 @@ import (
"io"
"runtime/debug"
"strings"
"time"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/golang/internal/xcoff"
"github.com/anchore/syft/syft/source"
"golang.org/x/mod/module"
)
const GOARCH = "GOARCH"
@@ -24,14 +26,30 @@ var (
// appear to be in a known format, or it breaks the rules of that format,
// or when there are I/O errors reading the file.
errUnrecognizedFormat = errors.New("unrecognized file format")
// devel is used to recognize the current default version when a golang main distribution is built
// https://github.com/golang/go/issues/29228 this issue has more details on the progress of being able to
// inject the correct version into the main module of the build process
)
const devel = "(devel)"
func makeGoMainPackage(mod *debug.BuildInfo, arch string, location source.Location) pkg.Package {
gbs := getBuildSettings(mod.Settings)
main := newGoBinaryPackage(&mod.Main, mod.Main.Path, mod.GoVersion, arch, location, gbs)
if v, ok := gbs["vcs.revision"]; ok {
main.Version = v
if main.Version == devel {
if version, ok := gbs["vcs.revision"]; ok {
if timestamp, ok := gbs["vcs.time"]; ok {
//NOTE: err is ignored, because if parsing fails
// we still use the empty Time{} struct to generate an empty date, like 00010101000000
// for consistency with the pseudo-version format: https://go.dev/ref/mod#pseudo-versions
ts, _ := time.Parse(time.RFC3339, timestamp)
if len(version) >= 12 {
version = version[:12]
}
version = module.PseudoVersion("", "", ts, version)
}
main.Version = version
}
}
return main
+3 -33
View File
@@ -2,27 +2,17 @@ package golang
import (
"debug/buildinfo"
"io"
"runtime/debug"
macho "github.com/anchore/go-macholibre"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
)
// unionReader is a single interface with all reading functions used by golang bin
// cataloger.
type unionReader interface {
io.Reader
io.ReaderAt
io.Seeker
io.Closer
}
// scanFile scans file to try to report the Go and module versions.
func scanFile(reader unionReader, filename string) ([]*debug.BuildInfo, []string) {
func scanFile(reader unionreader.UnionReader, filename string) ([]*debug.BuildInfo, []string) {
// NOTE: multiple readers are returned to cover universal binaries, which are files
// with more than one binary
readers, err := getReaders(reader)
readers, err := unionreader.GetReaders(reader)
if err != nil {
log.Warnf("golang cataloger: failed to open a binary: %v", err)
return nil, nil
@@ -56,23 +46,3 @@ func scanFile(reader unionReader, filename string) ([]*debug.BuildInfo, []string
return builds, archs
}
// getReaders extracts one or more io.ReaderAt objects representing binaries that can be processed (multiple binaries in the case for multi-architecture binaries).
func getReaders(f unionReader) ([]io.ReaderAt, error) {
if macho.IsUniversalMachoBinary(f) {
machoReaders, err := macho.ExtractReaders(f)
if err != nil {
log.Debugf("extracting readers: %v", err)
return nil, err
}
var readers []io.ReaderAt
for _, e := range machoReaders {
readers = append(readers, e.Reader)
}
return readers, nil
}
return []io.ReaderAt{f}, nil
}
+15
View File
@@ -0,0 +1,15 @@
package haskell
import (
"github.com/anchore/syft/syft/pkg/cataloger/common"
)
// NewHackageCataloger returns a new Haskell cataloger object.
func NewHackageCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/stack.yaml": parseStackYaml,
"**/stack.yaml.lock": parseStackLock,
"**/cabal.project.freeze": parseCabalFreeze,
}
return common.NewGenericCataloger(nil, globParsers, "hackage-cataloger")
}
@@ -0,0 +1,53 @@
package haskell
import (
"bufio"
"errors"
"fmt"
"io"
"strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
)
// integrity check
var _ common.ParserFn = parseCabalFreeze
// parseCabalFreeze is a parser function for cabal.project.freeze contents, returning all packages discovered.
func parseCabalFreeze(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
r := bufio.NewReader(reader)
pkgs := []*pkg.Package{}
for {
line, err := r.ReadString('\n')
switch {
case errors.Is(io.EOF, err):
return pkgs, nil, nil
case err != nil:
return nil, nil, fmt.Errorf("failed to parse cabal.project.freeze file: %w", err)
}
if !strings.Contains(line, "any.") {
continue
}
line = strings.TrimSpace(line)
startPkgEncoding, endPkgEncoding := strings.Index(line, "any.")+4, strings.Index(line, ",")
line = line[startPkgEncoding:endPkgEncoding]
splits := strings.Split(line, " ==")
pkgName, pkgVersion := splits[0], splits[1]
pkgs = append(pkgs, &pkg.Package{
Name: pkgName,
Version: pkgVersion,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: pkgName,
Version: pkgVersion,
},
})
}
}
@@ -0,0 +1,151 @@
package haskell
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
)
func TestParseCabalFreeze(t *testing.T) {
expected := []*pkg.Package{
{
Name: "Cabal",
Version: "3.2.1.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "Cabal",
Version: "3.2.1.0",
},
},
{
Name: "Diff",
Version: "0.4.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "Diff",
Version: "0.4.1",
},
},
{
Name: "HTTP",
Version: "4000.3.16",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "HTTP",
Version: "4000.3.16",
},
},
{
Name: "HUnit",
Version: "1.6.2.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "HUnit",
Version: "1.6.2.0",
},
},
{
Name: "OneTuple",
Version: "0.3.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "OneTuple",
Version: "0.3.1",
},
},
{
Name: "Only",
Version: "0.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "Only",
Version: "0.1",
},
},
{
Name: "PyF",
Version: "0.10.2.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "PyF",
Version: "0.10.2.0",
},
},
{
Name: "QuickCheck",
Version: "2.14.2",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "QuickCheck",
Version: "2.14.2",
},
},
{
Name: "RSA",
Version: "2.4.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "RSA",
Version: "2.4.1",
},
},
{
Name: "SHA",
Version: "1.6.4.4",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "SHA",
Version: "1.6.4.4",
},
},
{
Name: "Spock",
Version: "0.14.0.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "Spock",
Version: "0.14.0.0",
},
},
}
fixture, err := os.Open("test-fixtures/cabal.project.freeze")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
// TODO: no relationships are under test yet
actual, _, err := parseCabalFreeze(fixture.Name(), fixture)
if err != nil {
t.Error(err)
}
differences := deep.Equal(expected, actual)
if differences != nil {
t.Errorf("returned package list differed from expectation: %+v", differences)
}
}
@@ -0,0 +1,90 @@
package haskell
import (
"fmt"
"io"
"strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
"gopkg.in/yaml.v3"
)
// integrity check
var _ common.ParserFn = parseStackLock
type stackLock struct {
Packages []stackPackage `yaml:"packages"`
Snapshots []stackSnapshot `yaml:"snapshots"`
}
type stackPackage struct {
Completed completedPackage `yaml:"completed"`
}
type completedPackage struct {
Hackage string `yaml:"hackage"`
}
type stackSnapshot struct {
Completed completedSnapshot `yaml:"completed"`
}
type completedSnapshot struct {
URL string `yaml:"url"`
Sha string `yaml:"sha256"`
}
func parseStackPackageEncoding(pkgEncoding string) (name, version, hash string) {
lastDashIdx := strings.LastIndex(pkgEncoding, "-")
name = pkgEncoding[:lastDashIdx]
remainingEncoding := pkgEncoding[lastDashIdx+1:]
encodingSplits := strings.Split(remainingEncoding, "@")
version = encodingSplits[0]
startHash, endHash := strings.Index(encodingSplits[1], ":")+1, strings.Index(encodingSplits[1], ",")
hash = encodingSplits[1][startHash:endHash]
return
}
// parseStackLock is a parser function for stack.yaml.lock contents, returning all packages discovered.
func parseStackLock(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
bytes, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to load stack.yaml.lock file: %w", err)
}
var lockFile stackLock
if err := yaml.Unmarshal(bytes, &lockFile); err != nil {
return nil, nil, fmt.Errorf("failed to parse stack.yaml.lock file: %w", err)
}
var (
pkgs []*pkg.Package
snapshotURL string
)
for _, snap := range lockFile.Snapshots {
snapshotURL = snap.Completed.URL
}
for _, pack := range lockFile.Packages {
pkgName, pkgVersion, pkgHash := parseStackPackageEncoding(pack.Completed.Hackage)
pkgs = append(pkgs, &pkg.Package{
Name: pkgName,
Version: pkgVersion,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: pkgName,
Version: pkgVersion,
PkgHash: &pkgHash,
SnapshotURL: &snapshotURL,
},
})
}
return pkgs, nil, nil
}
@@ -0,0 +1,152 @@
package haskell
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
)
func fixtureP(str string) *string {
return &str
}
func TestParseStackLock(t *testing.T) {
url := "https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/14.yaml"
expected := []*pkg.Package{
{
Name: "HTTP",
Version: "4000.3.16",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "HTTP",
Version: "4000.3.16",
PkgHash: fixtureP("6042643c15a0b43e522a6693f1e322f05000d519543a84149cb80aeffee34f71"),
SnapshotURL: &url,
},
},
{
Name: "configurator-pg",
Version: "0.2.6",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "configurator-pg",
Version: "0.2.6",
PkgHash: fixtureP("cd9b06a458428e493a4d6def725af7ab1ab0fef678fbd871f9586fc7f9aa70be"),
SnapshotURL: &url,
},
},
{
Name: "hasql-dynamic-statements",
Version: "0.3.1.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "hasql-dynamic-statements",
Version: "0.3.1.1",
PkgHash: fixtureP("2cfe6e75990e690f595a87cbe553f2e90fcd738610f6c66749c81cc4396b2cc4"),
SnapshotURL: &url,
},
},
{
Name: "hasql-implicits",
Version: "0.1.0.4",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "hasql-implicits",
Version: "0.1.0.4",
PkgHash: fixtureP("0848d3cbc9d94e1e539948fa0be4d0326b26335034161bf8076785293444ca6f"),
SnapshotURL: &url,
},
},
{
Name: "hasql-pool",
Version: "0.5.2.2",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "hasql-pool",
Version: "0.5.2.2",
PkgHash: fixtureP("b56d4dea112d97a2ef4b2749508c0ca646828cb2d77b827e8dc433d249bb2062"),
SnapshotURL: &url,
},
},
{
Name: "lens-aeson",
Version: "1.1.3",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "lens-aeson",
Version: "1.1.3",
PkgHash: fixtureP("52c8eaecd2d1c2a969c0762277c4a8ee72c339a686727d5785932e72ef9c3050"),
SnapshotURL: &url,
},
},
{
Name: "optparse-applicative",
Version: "0.16.1.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "optparse-applicative",
Version: "0.16.1.0",
PkgHash: fixtureP("418c22ed6a19124d457d96bc66bd22c93ac22fad0c7100fe4972bbb4ac989731"),
SnapshotURL: &url,
},
},
{
Name: "protolude",
Version: "0.3.2",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "protolude",
Version: "0.3.2",
PkgHash: fixtureP("2a38b3dad40d238ab644e234b692c8911423f9d3ed0e36b62287c4a698d92cd1"),
SnapshotURL: &url,
},
},
{
Name: "ptr",
Version: "0.16.8.2",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "ptr",
Version: "0.16.8.2",
PkgHash: fixtureP("708ebb95117f2872d2c5a554eb6804cf1126e86abe793b2673f913f14e5eb1ac"),
SnapshotURL: &url,
},
},
}
fixture, err := os.Open("test-fixtures/stack.yaml.lock")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
// TODO: no relationships are under test yet
actual, _, err := parseStackLock(fixture.Name(), fixture)
if err != nil {
t.Error(err)
}
differences := deep.Equal(expected, actual)
if differences != nil {
t.Errorf("returned package list differed from expectation: %+v", differences)
}
}
@@ -0,0 +1,54 @@
package haskell
import (
"fmt"
"io"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
"gopkg.in/yaml.v3"
)
// integrity check
var _ common.ParserFn = parseStackYaml
type stackYaml struct {
ExtraDeps []string `yaml:"extra-deps"`
}
// parseStackYaml is a parser function for stack.yaml contents, returning all packages discovered.
func parseStackYaml(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
bytes, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to load stack.yaml file: %w", err)
}
var stackFile stackYaml
if err := yaml.Unmarshal(bytes, &stackFile); err != nil {
return nil, nil, fmt.Errorf("failed to parse stack.yaml file: %w", err)
}
var (
pkgs []*pkg.Package
)
for _, dep := range stackFile.ExtraDeps {
pkgName, pkgVersion, pkgHash := parseStackPackageEncoding(dep)
pkgs = append(pkgs, &pkg.Package{
Name: pkgName,
Version: pkgVersion,
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: pkgName,
Version: pkgVersion,
PkgHash: &pkgHash,
},
})
}
return pkgs, nil, nil
}
@@ -0,0 +1,126 @@
package haskell
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
)
func TestParseStackYaml(t *testing.T) {
expected := []*pkg.Package{
{
Name: "ShellCheck",
Version: "0.8.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "ShellCheck",
Version: "0.8.0",
PkgHash: fixtureP("353c9322847b661e4c6f7c83c2acf8e5c08b682fbe516c7d46c29605937543df"),
},
},
{
Name: "colourista",
Version: "0.1.0.1",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "colourista",
Version: "0.1.0.1",
PkgHash: fixtureP("98353ee0e2f5d97d2148513f084c1cd37dfda03e48aa9dd7a017c9d9c0ba710e"),
},
},
{
Name: "language-docker",
Version: "11.0.0",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "language-docker",
Version: "11.0.0",
PkgHash: fixtureP("3406ff0c1d592490f53ead8cf2cd22bdf3d79fd125ccaf3add683f6d71c24d55"),
},
},
{
Name: "spdx",
Version: "1.0.0.2",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "spdx",
Version: "1.0.0.2",
PkgHash: fixtureP("7dfac9b454ff2da0abb7560f0ffbe00ae442dd5cb76e8be469f77e6988a70fed"),
},
},
{
Name: "hspec",
Version: "2.9.4",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "hspec",
Version: "2.9.4",
PkgHash: fixtureP("658a6a74d5a70c040edd6df2a12228c6d9e63082adaad1ed4d0438ad082a0ef3"),
},
},
{
Name: "hspec-core",
Version: "2.9.4",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "hspec-core",
Version: "2.9.4",
PkgHash: fixtureP("a126e9087409fef8dcafcd2f8656456527ac7bb163ed4d9cb3a57589042a5fe8"),
},
},
{
Name: "hspec-discover",
Version: "2.9.4",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "hspec-discover",
Version: "2.9.4",
PkgHash: fixtureP("fbcf49ecfc3d4da53e797fd0275264cba776ffa324ee223e2a3f4ec2d2c9c4a6"),
},
},
{
Name: "stm",
Version: "2.5.0.2",
Language: pkg.Haskell,
Type: pkg.HackagePkg,
MetadataType: pkg.HackageMetadataType,
Metadata: pkg.HackageMetadata{
Name: "stm",
Version: "2.5.0.2",
PkgHash: fixtureP("e4dc6473faaa75fbd7eccab4e3ee1d651d75bb0e49946ef0b8b751ccde771a55"),
},
},
}
fixture, err := os.Open("test-fixtures/stack.yaml")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
// TODO: no relationships are under test yet
actual, _, err := parseStackYaml(fixture.Name(), fixture)
if err != nil {
t.Error(err)
}
differences := deep.Equal(expected, actual)
if differences != nil {
t.Errorf("returned package list differed from expectation: %+v", differences)
}
}
@@ -0,0 +1,17 @@
active-repositories: hackage.haskell.org:merge
constraints: any.Cabal ==3.2.1.0,
any.Diff ==0.4.1,
any.HTTP ==4000.3.16,
any.HUnit ==1.6.2.0,
any.OneTuple ==0.3.1,
tls +compat -hans +network,
any.Only ==0.1,
any.PyF ==0.10.2.0,
any.QuickCheck ==2.14.2,
semigroups +binary +bytestring -bytestring-builder +containers +deepseq +hashable +tagged +template-haskell +text +transformers +unordered-containers,
any.RSA ==2.4.1,
any.SHA ==1.6.4.4,
void -safe,
any.Spock ==0.14.0.0,
index-state: hackage.haskell.org 2022-07-07T01:01:53Z
@@ -0,0 +1,16 @@
flags: {}
extra-package-dbs: []
packages:
- .
resolver: lts-18.28
extra-deps:
- ShellCheck-0.8.0@sha256:353c9322847b661e4c6f7c83c2acf8e5c08b682fbe516c7d46c29605937543df,3297
- colourista-0.1.0.1@sha256:98353ee0e2f5d97d2148513f084c1cd37dfda03e48aa9dd7a017c9d9c0ba710e,3307
- language-docker-11.0.0@sha256:3406ff0c1d592490f53ead8cf2cd22bdf3d79fd125ccaf3add683f6d71c24d55,3883
- spdx-1.0.0.2@sha256:7dfac9b454ff2da0abb7560f0ffbe00ae442dd5cb76e8be469f77e6988a70fed,2008
- hspec-2.9.4@sha256:658a6a74d5a70c040edd6df2a12228c6d9e63082adaad1ed4d0438ad082a0ef3,1709
- hspec-core-2.9.4@sha256:a126e9087409fef8dcafcd2f8656456527ac7bb163ed4d9cb3a57589042a5fe8,6498
- hspec-discover-2.9.4@sha256:fbcf49ecfc3d4da53e797fd0275264cba776ffa324ee223e2a3f4ec2d2c9c4a6,2165
- stm-2.5.0.2@sha256:e4dc6473faaa75fbd7eccab4e3ee1d651d75bb0e49946ef0b8b751ccde771a55,2314
ghc-options:
"$everything": -haddock
@@ -0,0 +1,75 @@
# This file was autogenerated by Stack.
# You should not edit this file by hand.
# For more information, please see the documentation at:
# https://docs.haskellstack.org/en/stable/lock_files
packages:
- completed:
hackage: HTTP-4000.3.16@sha256:6042643c15a0b43e522a6693f1e322f05000d519543a84149cb80aeffee34f71,5947
pantry-tree:
size: 1428
sha256: b73a7f6d21cf20bbf819e19039409c9010efb5000d2b72cdd8fd67a9027c14e8
original:
hackage: HTTP-4000.3.16@sha256:6042643c15a0b43e522a6693f1e322f05000d519543a84149cb80aeffee34f71,5947
- completed:
hackage: configurator-pg-0.2.6@sha256:cd9b06a458428e493a4d6def725af7ab1ab0fef678fbd871f9586fc7f9aa70be,2849
pantry-tree:
size: 2463
sha256: 97efe7a22afc93033bda5adcffdabc0f1c30dc32b2c3ba02114ce7cd74c942fd
original:
hackage: configurator-pg-0.2.6@sha256:cd9b06a458428e493a4d6def725af7ab1ab0fef678fbd871f9586fc7f9aa70be,2849
- completed:
hackage: hasql-dynamic-statements-0.3.1.1@sha256:2cfe6e75990e690f595a87cbe553f2e90fcd738610f6c66749c81cc4396b2cc4,2675
pantry-tree:
size: 595
sha256: b84ae10a5c776f88f546df73bc957a35e61056400b7e805dad0b254612907e97
original:
hackage: hasql-dynamic-statements-0.3.1.1@sha256:2cfe6e75990e690f595a87cbe553f2e90fcd738610f6c66749c81cc4396b2cc4,2675
- completed:
hackage: hasql-implicits-0.1.0.4@sha256:0848d3cbc9d94e1e539948fa0be4d0326b26335034161bf8076785293444ca6f,1361
pantry-tree:
size: 264
sha256: d49af8f8749ab7039fa668af4b78f997f7fa2928b4aded6798f573a3d08e76a0
original:
hackage: hasql-implicits-0.1.0.4@sha256:0848d3cbc9d94e1e539948fa0be4d0326b26335034161bf8076785293444ca6f,1361
- completed:
hackage: hasql-pool-0.5.2.2@sha256:b56d4dea112d97a2ef4b2749508c0ca646828cb2d77b827e8dc433d249bb2062,2438
pantry-tree:
size: 412
sha256: 2741a33f947d28b4076c798c20c1f646beecd21f5eaf522c8256cbeb34d4d6d0
original:
hackage: hasql-pool-0.5.2.2@sha256:b56d4dea112d97a2ef4b2749508c0ca646828cb2d77b827e8dc433d249bb2062,2438
- completed:
hackage: lens-aeson-1.1.3@sha256:52c8eaecd2d1c2a969c0762277c4a8ee72c339a686727d5785932e72ef9c3050,1764
pantry-tree:
size: 541
sha256: b31392b78f2a03111c805f4400007778eb93b49f998ab41dfbebaaf9b5526bad
original:
hackage: lens-aeson-1.1.3@sha256:52c8eaecd2d1c2a969c0762277c4a8ee72c339a686727d5785932e72ef9c3050,1764
- completed:
hackage: optparse-applicative-0.16.1.0@sha256:418c22ed6a19124d457d96bc66bd22c93ac22fad0c7100fe4972bbb4ac989731,4982
pantry-tree:
size: 2979
sha256: dd092d843091c08691485d68a1908517079b1bc6f3d73928f37635a19dc27fc1
original:
hackage: optparse-applicative-0.16.1.0@sha256:418c22ed6a19124d457d96bc66bd22c93ac22fad0c7100fe4972bbb4ac989731,4982
- completed:
hackage: protolude-0.3.2@sha256:2a38b3dad40d238ab644e234b692c8911423f9d3ed0e36b62287c4a698d92cd1,2240
pantry-tree:
size: 1594
sha256: a36d2912ac552d950ba4476de7d950b56b82dd28e48b9f4d0efee938f10bc525
original:
hackage: protolude-0.3.2@sha256:2a38b3dad40d238ab644e234b692c8911423f9d3ed0e36b62287c4a698d92cd1,2240
- completed:
hackage: ptr-0.16.8.2@sha256:708ebb95117f2872d2c5a554eb6804cf1126e86abe793b2673f913f14e5eb1ac,3959
pantry-tree:
size: 1303
sha256: 557c438345de19f82bf01d676100da2a191ef06f624e7a4b90b09ac17cbb52a5
original:
hackage: ptr-0.16.8.2@sha256:708ebb95117f2872d2c5a554eb6804cf1126e86abe793b2673f913f14e5eb1ac,3959
snapshots:
- completed:
size: 618951
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/14.yaml
sha256: 4c31d4ef975b0211078862566aedf3b82b6cea569fc2cde4c72a51e5a8d236ce
original: lts-19.14
@@ -0,0 +1,66 @@
package unionreader
import (
"bytes"
"fmt"
"io"
"io/ioutil"
macho "github.com/anchore/go-macholibre"
"github.com/anchore/syft/internal/log"
)
// unionReader is a single interface with all reading functions needed by multi-arch binary catalogers
// cataloger.
type UnionReader interface {
io.Reader
io.ReaderAt
io.Seeker
io.Closer
}
// getReaders extracts one or more io.ReaderAt objects representing binaries that can be processed (multiple binaries in the case for multi-architecture binaries).
func GetReaders(f UnionReader) ([]io.ReaderAt, error) {
if macho.IsUniversalMachoBinary(f) {
machoReaders, err := macho.ExtractReaders(f)
if err != nil {
log.Debugf("extracting readers: %v", err)
return nil, err
}
var readers []io.ReaderAt
for _, e := range machoReaders {
readers = append(readers, e.Reader)
}
return readers, nil
}
return []io.ReaderAt{f}, nil
}
func GetUnionReader(readerCloser io.ReadCloser) (UnionReader, error) {
reader, ok := readerCloser.(UnionReader)
if ok {
return reader, nil
}
b, err := ioutil.ReadAll(readerCloser)
if err != nil {
return nil, fmt.Errorf("unable to read contents from binary: %w", err)
}
bytesReader := bytes.NewReader(b)
reader = struct {
io.ReadCloser
io.ReaderAt
io.Seeker
}{
ReadCloser: io.NopCloser(bytesReader),
ReaderAt: bytesReader,
Seeker: bytesReader,
}
return reader, nil
}
@@ -1,4 +1,4 @@
package golang
package unionreader
import (
"io"
@@ -14,13 +14,13 @@ func Test_getUnionReader_notUnionReader(t *testing.T) {
reader := io.NopCloser(strings.NewReader(expectedContents))
// make certain that the test fixture does not implement the union reader
_, ok := reader.(unionReader)
_, ok := reader.(UnionReader)
require.False(t, ok)
actual, err := getUnionReader(reader)
actual, err := GetUnionReader(reader)
require.NoError(t, err)
_, ok = actual.(unionReader)
_, ok = actual.(UnionReader)
require.True(t, ok)
b, err := io.ReadAll(actual)
+1 -1
View File
@@ -335,7 +335,7 @@ func pomProjectByParentPath(archivePath, virtualPath string, extractPaths []stri
projectByParentPath := make(map[string]pkg.PomProject)
for filePath, fileContents := range contentsOfMavenProjectFiles {
pomProject, err := parsePomXML(filePath, strings.NewReader(fileContents))
pomProject, err := parsePomXMLProject(filePath, strings.NewReader(fileContents))
if err != nil {
log.Warnf("failed to parse pom.xml virtualPath=%q path=%q: %+v", virtualPath, filePath, err)
continue
+67 -17
View File
@@ -6,34 +6,84 @@ import (
"io"
"strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/vifraa/gopom"
"golang.org/x/net/html/charset"
)
const pomXMLGlob = "*pom.xml"
const pomXMLDirGlob = "**/pom.xml"
func parsePomXML(path string, reader io.Reader) (*pkg.PomProject, error) {
var project gopom.Project
decoder := xml.NewDecoder(reader)
// prevent against warnings for "xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil"
decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&project); err != nil {
return nil, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
func parserPomXML(path string, content io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
pom, err := decodePomXML(content)
if err != nil {
return nil, nil, err
}
var pkgs []*pkg.Package
for _, dep := range pom.Dependencies {
p := newPackageFromPom(dep)
if p.Name == "" {
continue
}
pkgs = append(pkgs, p)
}
return pkgs, nil, nil
}
func parsePomXMLProject(path string, reader io.Reader) (*pkg.PomProject, error) {
project, err := decodePomXML(reader)
if err != nil {
return nil, err
}
return newPomProject(path, project), nil
}
func newPomProject(path string, p gopom.Project) *pkg.PomProject {
return &pkg.PomProject{
Path: path,
Parent: pomParent(project.Parent),
GroupID: project.GroupID,
ArtifactID: project.ArtifactID,
Version: project.Version,
Name: project.Name,
Description: cleanDescription(project.Description),
URL: project.URL,
}, nil
Parent: pomParent(p.Parent),
GroupID: p.GroupID,
ArtifactID: p.ArtifactID,
Version: p.Version,
Name: p.Name,
Description: cleanDescription(p.Description),
URL: p.URL,
}
}
func newPackageFromPom(dep gopom.Dependency) *pkg.Package {
p := &pkg.Package{
Name: dep.ArtifactID,
Version: dep.Version,
Language: pkg.Java,
Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet?
MetadataType: pkg.JavaMetadataType,
FoundBy: javaPomCataloger,
Metadata: pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{
GroupID: dep.GroupID,
},
},
}
p.Metadata = pkg.JavaMetadata{PURL: packageURL(*p)}
return p
}
func decodePomXML(content io.Reader) (project gopom.Project, err error) {
decoder := xml.NewDecoder(content)
// prevent against warnings for "xml: encoding "iso-8859-1" declared but Decoder.CharsetReader is nil"
decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&project); err != nil {
return project, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
}
return project, nil
}
func pomParent(parent gopom.Parent) (result *pkg.PomParent) {
+49 -2
View File
@@ -10,7 +10,54 @@ import (
"github.com/stretchr/testify/assert"
)
func Test_parsePomXML(t *testing.T) {
func Test_parserPomXML(t *testing.T) {
tests := []struct {
input string
expected []*pkg.Package
}{
{
input: "test-fixtures/pom/pom.xml",
expected: []*pkg.Package{
{
Name: "joda-time",
Version: "2.9.2",
FoundBy: javaPomCataloger,
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/com.joda/joda-time@2.9.2",
},
},
{
Name: "junit",
Version: "4.12",
FoundBy: "java-pom-cataloger",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
PURL: "pkg:maven/junit/junit@4.12",
},
},
},
},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
fixture, err := os.Open(test.input)
assert.NoError(t, err)
actual, relationships, err := parserPomXML(fixture.Name(), fixture)
assert.NoError(t, err)
assert.Nil(t, relationships)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_parsePomXMLProject(t *testing.T) {
tests := []struct {
expected pkg.PomProject
}{
@@ -37,7 +84,7 @@ func Test_parsePomXML(t *testing.T) {
fixture, err := os.Open(test.expected.Path)
assert.NoError(t, err)
actual, err := parsePomXML(fixture.Name(), fixture)
actual, err := parsePomXMLProject(fixture.Name(), fixture)
assert.NoError(t, err)
assert.Equal(t, &test.expected, actual)
+17
View File
@@ -0,0 +1,17 @@
package java
import "github.com/anchore/syft/syft/pkg/cataloger/common"
const javaPomCataloger = "java-pom-cataloger"
// NewJavaPomCataloger returns a cataloger capable of parsing
// dependencies from a pom.xml file.
// Pom files list dependencies that maybe not be locally installed yet.
func NewJavaPomCataloger() *common.GenericCataloger {
globParsers := make(map[string]common.ParserFn)
// java project files
globParsers[pomXMLDirGlob] = parserPomXML
return common.NewGenericCataloger(nil, globParsers, javaPomCataloger)
}
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.anchore</groupId>
<artifactId>example-java-app-maven</artifactId>
<packaging>jar</packaging>
<version>0.1.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- tag::joda[] -->
<dependency>
<groupId>com.joda</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.2</version>
</dependency>
<!-- end::joda[] -->
<!-- tag::junit[] -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- end::junit[] -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>hello.HelloWorld</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -25,6 +25,16 @@ var (
// versionExp matches the "version" line of a yarn.lock entry and captures the version value.
// For example: version "4.10.1" (...and the value "4.10.1" is captured)
versionExp = regexp.MustCompile(`^\W+version(?:\W+"|:\W+)([\w-_.]+)"?`)
// packageURLExp matches the name and version of the dependency in yarn.lock
// from the resolved URL, including scope/namespace prefix if any.
// For example:
// `resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"`
// would return "async" and "3.2.3"
//
// `resolved "https://registry.yarnpkg.com/@4lolo/resize-observer-polyfill/-/resize-observer-polyfill-1.5.2.tgz#58868fc7224506236b5550d0c68357f0a874b84b"`
// would return "@4lolo/resize-observer-polyfill" and "1.5.2"
packageURLExp = regexp.MustCompile(`^\s+resolved\s+"https://registry\.(?:yarnpkg\.com|npmjs\.org)/(.+?)/-/(?:.+?)-(\d+\..+?)\.tgz`)
)
const (
@@ -43,39 +53,37 @@ func parseYarnLock(path string, reader io.Reader) ([]*pkg.Package, []artifact.Re
scanner := bufio.NewScanner(reader)
parsedPackages := internal.NewStringSet()
currentPackage := noPackage
currentVersion := noVersion
for scanner.Scan() {
line := scanner.Text()
if currentPackage == noPackage {
// Scan until we find the next package
packageName := findPackageName(line)
if packageName == noPackage {
continue
}
if parsedPackages.Contains(packageName) {
// We don't parse repeated package declarations.
continue
if packageName := findPackageName(line); packageName != noPackage {
// When we find a new package, check if we have unsaved identifiers
if currentPackage != noPackage && currentVersion != noVersion && !parsedPackages.Contains(currentPackage+"@"+currentVersion) {
packages = append(packages, newYarnLockPackage(currentPackage, currentVersion))
parsedPackages.Add(currentPackage + "@" + currentVersion)
}
currentPackage = packageName
parsedPackages.Add(currentPackage)
} else if version := findPackageVersion(line); version != noVersion {
currentVersion = version
} else if packageName, version := findPackageAndVersion(line); packageName != noPackage && version != noVersion && !parsedPackages.Contains(packageName+"@"+version) {
packages = append(packages, newYarnLockPackage(packageName, version))
parsedPackages.Add(packageName + "@" + version)
continue
}
// We've found the package entry, now we just need the version
if version := findPackageVersion(line); version != noVersion {
packages = append(packages, newYarnLockPackage(currentPackage, version))
// Cleanup to indicate no unsaved identifiers
currentPackage = noPackage
continue
currentVersion = noVersion
}
}
// check if we have valid unsaved data after end-of-file has reached
if currentPackage != noPackage && currentVersion != noVersion && !parsedPackages.Contains(currentPackage+"@"+currentVersion) {
packages = append(packages, newYarnLockPackage(currentPackage, currentVersion))
parsedPackages.Add(currentPackage + "@" + currentVersion)
}
if err := scanner.Err(); err != nil {
return nil, nil, fmt.Errorf("failed to parse yarn.lock file: %w", err)
}
@@ -99,6 +107,14 @@ func findPackageVersion(line string) string {
return noVersion
}
func findPackageAndVersion(line string) (string, string) {
if matches := packageURLExp.FindStringSubmatch(line); len(matches) >= 2 {
return matches[1], matches[2]
}
return noPackage, noVersion
}
func newYarnLockPackage(name, version string) *pkg.Package {
return &pkg.Package{
Name: name,
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestParseYarnLock(t *testing.T) {
func TestParseYarnBerry(t *testing.T) {
expected := map[string]pkg.Package{
"@babel/code-frame": {
Name: "@babel/code-frame",
@@ -66,10 +66,87 @@ func TestParseYarnLock(t *testing.T) {
Type: pkg.NpmPkg,
},
}
testFixtures := []string{
"test-fixtures/yarn-berry/yarn.lock",
}
for _, file := range testFixtures {
file := file
t.Run(file, func(t *testing.T) {
t.Parallel()
fixture, err := os.Open(file)
require.NoError(t, err)
// TODO: no relationships are under test yet
actual, _, err := parseYarnLock(fixture.Name(), fixture)
require.NoError(t, err)
assertPkgsEqual(t, actual, expected)
})
}
}
func TestParseYarnLock(t *testing.T) {
expected := map[string]pkg.Package{
"@babel/code-frame": {
Name: "@babel/code-frame",
Version: "7.10.4",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"@types/minimatch": {
Name: "@types/minimatch",
Version: "3.0.3",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"@types/qs": {
Name: "@types/qs",
Version: "6.9.4",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"ajv": {
Name: "ajv",
Version: "6.12.3",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"atob": {
Name: "atob",
Version: "2.1.2",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"aws-sdk": {
Name: "aws-sdk",
Version: "2.706.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"jhipster-core": {
Name: "jhipster-core",
Version: "7.3.4",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"asn1.js": {
Name: "asn1.js",
Version: "4.10.1",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"something-i-made-up": {
Name: "something-i-made-up",
Version: "7.7.7",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
}
testFixtures := []string{
"test-fixtures/yarn/yarn.lock",
"test-fixtures/yarn-berry/yarn.lock",
}
for _, file := range testFixtures {
+151
View File
@@ -0,0 +1,151 @@
/*
Package portage provides a concrete Cataloger implementation for Gentoo Portage.
*/
package portage
import (
"bufio"
"fmt"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
var (
cpvRe = regexp.MustCompile(`/([^/]*/[\w+][\w+-]*)-((\d+)((\.\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\d*)*)(-r\d+)?)/CONTENTS$`)
)
type Cataloger struct{}
// NewPortageCataloger returns a new Portage package cataloger object.
func NewPortageCataloger() *Cataloger {
return &Cataloger{}
}
// Name returns a string that uniquely describes a cataloger
func (c *Cataloger) Name() string {
return "portage-cataloger"
}
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing portage support files.
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
dbFileMatches, err := resolver.FilesByGlob(pkg.PortageDBGlob)
if err != nil {
return nil, nil, fmt.Errorf("failed to find portage files by glob: %w", err)
}
var allPackages []pkg.Package
for _, dbLocation := range dbFileMatches {
cpvMatch := cpvRe.FindStringSubmatch(dbLocation.RealPath)
if cpvMatch == nil {
return nil, nil, fmt.Errorf("failed to match package and version in %s", dbLocation.RealPath)
}
entry := pkg.PortageMetadata{
// ensure the default value for a collection is never nil since this may be shown as JSON
Files: make([]pkg.PortageFileRecord, 0),
Package: cpvMatch[1],
Version: cpvMatch[2],
}
err = addFiles(resolver, dbLocation, &entry)
if err != nil {
return nil, nil, err
}
addSize(resolver, dbLocation, &entry)
p := pkg.Package{
Name: entry.Package,
Version: entry.Version,
Type: pkg.PortagePkg,
MetadataType: pkg.PortageMetadataType,
Metadata: entry,
}
addLicenses(resolver, dbLocation, &p)
p.FoundBy = c.Name()
p.Locations.Add(dbLocation)
p.SetID()
allPackages = append(allPackages, p)
}
return allPackages, nil, nil
}
func addFiles(resolver source.FileResolver, dbLocation source.Location, entry *pkg.PortageMetadata) error {
contentsReader, err := resolver.FileContentsByLocation(dbLocation)
if err != nil {
return err
}
scanner := bufio.NewScanner(contentsReader)
for scanner.Scan() {
line := strings.Trim(scanner.Text(), "\n")
fields := strings.Split(line, " ")
if fields[0] == "obj" {
record := pkg.PortageFileRecord{
Path: fields[1],
}
record.Digest = &file.Digest{
Algorithm: "md5",
Value: fields[2],
}
entry.Files = append(entry.Files, record)
}
}
return nil
}
func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) {
parentPath := filepath.Dir(dbLocation.RealPath)
location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "LICENSE"))
if location != nil {
licenseReader, err := resolver.FileContentsByLocation(*location)
if err == nil {
findings := internal.NewStringSet()
scanner := bufio.NewScanner(licenseReader)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
token := scanner.Text()
if token != "||" && token != "(" && token != ")" {
findings.Add(token)
}
}
p.Licenses = findings.ToSlice()
sort.Strings(p.Licenses)
}
}
}
func addSize(resolver source.FileResolver, dbLocation source.Location, entry *pkg.PortageMetadata) {
parentPath := filepath.Dir(dbLocation.RealPath)
location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "SIZE"))
if location != nil {
sizeReader, err := resolver.FileContentsByLocation(*location)
if err != nil {
log.Warnf("failed to fetch portage SIZE (package=%s): %+v", entry.Package, err)
} else {
scanner := bufio.NewScanner(sizeReader)
for scanner.Scan() {
line := strings.Trim(scanner.Text(), "\n")
size, err := strconv.Atoi(line)
if err == nil {
entry.InstalledSize = size
}
}
}
}
}
@@ -0,0 +1,106 @@
package portage
import (
"testing"
"github.com/anchore/syft/syft/file"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/go-test/deep"
)
func TestPortageCataloger(t *testing.T) {
tests := []struct {
name string
expected []pkg.Package
}{
{
name: "go-case",
expected: []pkg.Package{
{
Name: "app-containers/skopeo",
Version: "1.5.1",
FoundBy: "portage-cataloger",
Licenses: []string{"Apache-2.0", "BSD", "BSD-2", "CC-BY-SA-4.0", "ISC", "MIT"},
Type: pkg.PortagePkg,
MetadataType: pkg.PortageMetadataType,
Metadata: pkg.PortageMetadata{
Package: "app-containers/skopeo",
Version: "1.5.1",
InstalledSize: 27937835,
Files: []pkg.PortageFileRecord{
{
Path: "/usr/bin/skopeo",
Digest: &file.Digest{
Algorithm: "md5",
Value: "376c02bd3b22804df8fdfdc895e7dbfb",
},
},
{
Path: "/etc/containers/policy.json",
Digest: &file.Digest{
Algorithm: "md5",
Value: "c01eb6950f03419e09d4fc88cb42ff6f",
},
},
{
Path: "/etc/containers/registries.d/default.yaml",
Digest: &file.Digest{
Algorithm: "md5",
Value: "e6e66cd3c24623e0667f26542e0e08f6",
},
},
{
Path: "/var/lib/atomic/sigstore/.keep_app-containers_skopeo-0",
Digest: &file.Digest{
Algorithm: "md5",
Value: "d41d8cd98f00b204e9800998ecf8427e",
},
},
},
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
img := imagetest.GetFixtureImage(t, "docker-archive", "image-portage")
s, err := source.NewFromImage(img, "")
if err != nil {
t.Fatal(err)
}
c := NewPortageCataloger()
resolver, err := s.FileResolver(source.SquashedScope)
if err != nil {
t.Errorf("could not get resolver error: %+v", err)
}
actual, _, err := c.Catalog(resolver)
if err != nil {
t.Fatalf("failed to catalog: %+v", err)
}
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Logf(" %+v", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
}
// test remaining fields...
for _, d := range deep.Equal(actual, test.expected) {
t.Errorf("diff: %+v", d)
}
})
}
}
@@ -0,0 +1,2 @@
FROM scratch
COPY . .
@@ -0,0 +1,13 @@
dir /usr
dir /usr/bin
obj /usr/bin/skopeo 376c02bd3b22804df8fdfdc895e7dbfb 1649284374
dir /etc
dir /etc/containers
obj /etc/containers/policy.json c01eb6950f03419e09d4fc88cb42ff6f 1649284375
dir /etc/containers/registries.d
obj /etc/containers/registries.d/default.yaml e6e66cd3c24623e0667f26542e0e08f6 1649284375
dir /var
dir /var/lib
dir /var/lib/atomic
dir /var/lib/atomic/sigstore
obj /var/lib/atomic/sigstore/.keep_app-containers_skopeo-0 d41d8cd98f00b204e9800998ecf8427e 1649284375
@@ -0,0 +1 @@
Apache-2.0 BSD BSD-2 CC-BY-SA-4.0 ISC MIT
+22
View File
@@ -49,5 +49,27 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []arti
pkgs = append(pkgs, discoveredPkgs...)
}
// Additionally look for RPM manifest files to detect packages in CBL-Mariner distroless images
manifestFileMatches, err := resolver.FilesByGlob(pkg.RpmManifestGlob)
if err != nil {
return nil, nil, fmt.Errorf("failed to find rpm manifests by glob: %w", err)
}
for _, location := range manifestFileMatches {
reader, err := resolver.FileContentsByLocation(location)
if err != nil {
return nil, nil, err
}
discoveredPkgs, err := parseRpmManifest(location, reader)
internal.CloseAndLogError(reader, location.VirtualPath)
if err != nil {
return nil, nil, fmt.Errorf("unable to catalog rpm manifest=%+v: %w", location.RealPath, err)
}
pkgs = append(pkgs, discoveredPkgs...)
}
return pkgs, nil, nil
}
+1 -1
View File
@@ -14,7 +14,7 @@ import (
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
)
// parseApkDb parses an "Packages" RPM DB and returns the Packages listed within it.
// parseRpmDb parses an "Packages" RPM DB and returns the Packages listed within it.
func parseRpmDB(resolver source.FilePathResolver, dbLocation source.Location, reader io.Reader) ([]pkg.Package, error) {
f, err := ioutil.TempFile("", internal.ApplicationName+"-rpmdb")
if err != nil {
@@ -0,0 +1,104 @@
package rpmdb
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
// Parses an entry in an RPM manifest file as used in Mariner distroless containers
// Each line is the output of :
// rpm --query --all --query-format "%{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{BUILDTIME}\t%{VENDOR}\t%{EPOCH}\t%{SIZE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}\n"
// https://github.com/microsoft/CBL-Mariner/blob/3df18fac373aba13a54bd02466e64969574f13af/toolkit/docs/how_it_works/5_misc.md?plain=1#L150
func parseRpmManifestEntry(entry string, location source.Location) (*pkg.Package, error) {
parts := strings.Split(entry, "\t")
if len(parts) < 10 {
return nil, fmt.Errorf("unexpected number of fields in line: %s", entry)
}
versionParts := strings.Split(parts[1], "-")
if len(versionParts) != 2 {
return nil, fmt.Errorf("unexpected version field: %s", parts[1])
}
version := versionParts[0]
release := versionParts[1]
converted, err := strconv.Atoi(parts[8])
var epoch *int
if err != nil || parts[5] == "(none)" {
epoch = nil
} else {
epoch = &converted
}
converted, err = strconv.Atoi(parts[6])
var size int
if err == nil {
size = converted
}
metadata := pkg.RpmdbMetadata{
Name: parts[0],
Version: version,
Epoch: epoch,
Arch: parts[7],
Release: release,
SourceRpm: parts[9],
Vendor: parts[4],
Size: size,
}
p := pkg.Package{
Name: parts[0],
Version: toELVersion(metadata),
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: metadata,
}
p.SetID()
return &p, nil
}
// Parses an RPM manifest file, as used in Mariner distroless containers, and returns the Packages listed
func parseRpmManifest(dbLocation source.Location, reader io.Reader) ([]pkg.Package, error) {
r := bufio.NewReader(reader)
allPkgs := make([]pkg.Package, 0)
for {
line, err := r.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
if line == "" {
continue
}
p, err := parseRpmManifestEntry(strings.TrimSuffix(line, "\n"), dbLocation)
if err != nil {
log.Warnf("unable to parse RPM manifest entry: %w", err)
continue
}
if !pkg.IsValid(p) {
continue
}
p.SetID()
allPkgs = append(allPkgs, *p)
}
return allPkgs, nil
}
@@ -0,0 +1,117 @@
package rpmdb
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/go-test/deep"
)
func TestParseRpmManifest(t *testing.T) {
location := source.NewLocation("test-path")
fixture_path := "test-fixtures/container-manifest-2"
expected := map[string]pkg.Package{
"mariner-release": {
Name: "mariner-release",
Version: "2.0-12.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
Name: "mariner-release",
Epoch: nil,
Arch: "noarch",
Release: "12.cm2",
Version: "2.0",
SourceRpm: "mariner-release-2.0-12.cm2.src.rpm",
Size: 580,
Vendor: "Microsoft Corporation",
},
},
"filesystem": {
Name: "filesystem",
Version: "1.1-9.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
Name: "filesystem",
Epoch: nil,
Arch: "x86_64",
Release: "9.cm2",
Version: "1.1",
SourceRpm: "filesystem-1.1-9.cm2.src.rpm",
Size: 7596,
Vendor: "Microsoft Corporation",
},
},
"glibc": {
Name: "glibc",
Version: "2.35-2.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
Name: "glibc",
Epoch: nil,
Arch: "x86_64",
Release: "2.cm2",
Version: "2.35",
SourceRpm: "glibc-2.35-2.cm2.src.rpm",
Size: 10855265,
Vendor: "Microsoft Corporation",
},
},
"openssl-libs": {
Name: "openssl-libs",
Version: "1.1.1k-15.cm2",
Locations: source.NewLocationSet(location),
FoundBy: catalogerName,
Type: pkg.RpmPkg,
MetadataType: pkg.RpmdbMetadataType,
Metadata: pkg.RpmdbMetadata{
Name: "openssl-libs",
Epoch: nil,
Arch: "x86_64",
Release: "15.cm2",
Version: "1.1.1k",
SourceRpm: "openssl-1.1.1k-15.cm2.src.rpm",
Size: 4365048,
Vendor: "Microsoft Corporation",
},
},
}
fixture, err := os.Open(fixture_path)
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
actual, err := parseRpmManifest(location, fixture)
if err != nil {
t.Fatalf("failed to parse rpm manifest: %+v", err)
}
if len(actual) != 12 {
for _, a := range actual {
t.Log(" ", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expected))
}
for _, a := range actual[0:4] {
e := expected[a.Name]
diffs := deep.Equal(a, e)
if len(diffs) > 0 {
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
}
}
}
@@ -0,0 +1,12 @@
mariner-release 2.0-12.cm2 1653816591 1653753130 Microsoft Corporation (none) 580 noarch 0 mariner-release-2.0-12.cm2.src.rpm
filesystem 1.1-9.cm2 1653816591 1653628924 Microsoft Corporation (none) 7596 x86_64 0 filesystem-1.1-9.cm2.src.rpm
glibc 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 10855265 x86_64 0 glibc-2.35-2.cm2.src.rpm
openssl-libs 1.1.1k-15.cm2 1653816591 1653631609 Microsoft Corporation (none) 4365048 x86_64 0 openssl-1.1.1k-15.cm2.src.rpm
libgcc 11.2.0-2.cm2 1653816591 1650702349 Microsoft Corporation (none) 103960 x86_64 0 gcc-11.2.0-2.cm2.src.rpm
openssl 1.1.1k-15.cm2 1653816591 1653631609 Microsoft Corporation (none) 1286337 x86_64 0 openssl-1.1.1k-15.cm2.src.rpm
glibc-iconv 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 8397230 x86_64 0 glibc-2.35-2.cm2.src.rpm
iana-etc 20211115-1.cm2 1653816591 1650711959 Microsoft Corporation (none) 4380680 noarch 0 iana-etc-20211115-1.cm2.src.rpm
tzdata 2022a-1.cm2 1653816591 1653752882 Microsoft Corporation (none) 1535764 noarch 0 tzdata-2022a-1.cm2.src.rpm
prebuilt-ca-certificates-base 2.0.0-3.cm2 1653816591 1653771776 Microsoft Corporation (none) 65684 noarch 1 prebuilt-ca-certificates-base-2.0.0-3.cm2.src.rpm
distroless-packages-minimal 0.1-2.cm2 1653816591 1650712132 Microsoft Corporation (none) 0 x86_64 0 distroless-packages-0.1-2.cm2.src.rpm
distroless-packages-base 0.1-2.cm2 1653816591 1650712132 Microsoft Corporation (none) 0 x86_64 0 distroless-packages-0.1-2.cm2.src.rpm
@@ -21,3 +21,9 @@ docker exec -i --tty=false generate-rpmdb-fixture bash <<-EOF
EOF
docker cp generate-rpmdb-fixture:/scratch/Packages .
docker build -o . - <<EOF
FROM mcr.microsoft.com/cbl-mariner/distroless/base:2.0 as base
FROM scratch
COPY --from=base /var/lib/rpmmanifest/container-manifest-2 .
EOF
@@ -0,0 +1,126 @@
package rust
import (
"fmt"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
"github.com/anchore/syft/syft/source"
rustaudit "github.com/microsoft/go-rustaudit"
)
const catalogerName = "rust-audit-binary-cataloger"
type Cataloger struct{}
// NewRustAuditBinaryCataloger returns a new Rust auditable binary cataloger object that can detect dependencies
// in binaries produced with https://github.com/Shnatsel/rust-audit
func NewRustAuditBinaryCataloger() *Cataloger {
return &Cataloger{}
}
// Name returns a string that uniquely describes a cataloger
func (c *Cataloger) Name() string {
return catalogerName
}
// Catalog identifies executables then attempts to read Rust dependency information from them
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
fileMatches, err := resolver.FilesByMIMEType(internal.ExecutableMIMETypeSet.List()...)
if err != nil {
return pkgs, nil, fmt.Errorf("failed to find bin by mime types: %w", err)
}
for _, location := range fileMatches {
readerCloser, err := resolver.FileContentsByLocation(location)
if err != nil {
log.Warnf("rust cataloger: opening file: %v", err)
continue
}
reader, err := unionreader.GetUnionReader(readerCloser)
if err != nil {
return nil, nil, err
}
versionInfos := scanFile(reader, location.RealPath)
internal.CloseAndLogError(readerCloser, location.RealPath)
for _, versionInfo := range versionInfos {
pkgs = append(pkgs, buildRustPkgInfo(location, versionInfo)...)
}
}
return pkgs, nil, nil
}
// scanFile scans file to try to report the Rust crate dependencies
func scanFile(reader unionreader.UnionReader, filename string) []rustaudit.VersionInfo {
// NOTE: multiple readers are returned to cover universal binaries, which are files
// with more than one binary
readers, err := unionreader.GetReaders(reader)
if err != nil {
log.Warnf("rust cataloger: failed to open a binary: %v", err)
return nil
}
var versionInfos []rustaudit.VersionInfo
for _, r := range readers {
versionInfo, err := rustaudit.GetDependencyInfo(r)
if err != nil {
if err == rustaudit.ErrNoRustDepInfo {
// since the cataloger can only select executables and not distinguish if they are a Rust-compiled
// binary, we should not show warnings/logs in this case.
return nil
}
// Use an Info level log here like golang/scan_bin.go
log.Infof("rust cataloger: unable to read dependency information (file=%q): %v", filename, err)
return nil
}
versionInfos = append(versionInfos, versionInfo)
}
return versionInfos
}
func buildRustPkgInfo(location source.Location, versionInfo rustaudit.VersionInfo) []pkg.Package {
var pkgs []pkg.Package
for _, dep := range versionInfo.Packages {
dep := dep
p := newRustPackage(&dep, location)
if pkg.IsValid(&p) && dep.Kind == rustaudit.Runtime {
pkgs = append(pkgs, p)
}
}
return pkgs
}
func newRustPackage(dep *rustaudit.Package, location source.Location) pkg.Package {
p := pkg.Package{
FoundBy: catalogerName,
Name: dep.Name,
Version: dep.Version,
Language: pkg.Rust,
Type: pkg.RustPkg,
Locations: source.NewLocationSet(location),
MetadataType: pkg.RustCargoPackageMetadataType,
Metadata: pkg.CargoPackageMetadata{
Name: dep.Name,
Version: dep.Version,
Source: dep.Source,
},
}
p.SetID()
return p
}
+17
View File
@@ -0,0 +1,17 @@
/*
Package swift provides a concrete Cataloger implementation for Podfile.lock files.
*/
package swift
import (
"github.com/anchore/syft/syft/pkg/cataloger/common"
)
// NewCocoapodsCataloger returns a new Swift Cocoapods lock file cataloger object.
func NewCocoapodsCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/Podfile.lock": parsePodfileLock,
}
return common.NewGenericCataloger(nil, globParsers, "cocoapods-cataloger")
}
@@ -0,0 +1,76 @@
package swift
import (
"fmt"
"io"
"io/ioutil"
"strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
"gopkg.in/yaml.v3"
)
// integrity check
var _ common.ParserFn = parsePodfileLock
// parsePodfileLock is a parser function for Podfile.lock contents, returning all cocoapods pods discovered.
func parsePodfileLock(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
bytes, err := ioutil.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to read file: %w", err)
}
var podfile map[string]interface{}
if err = yaml.Unmarshal(bytes, &podfile); err != nil {
return nil, nil, fmt.Errorf("unable to parse yaml: %w", err)
}
c, exists := podfile["SPEC CHECKSUMS"]
if !exists {
return nil, nil, fmt.Errorf("malformed podfile.lock: missing checksums")
}
checksums := c.(map[string]interface{})
p, exists := podfile["PODS"]
if !exists {
return nil, nil, fmt.Errorf("malformed podfile.lock: missing checksums")
}
pods := p.([]interface{})
pkgs := []*pkg.Package{}
for _, podInterface := range pods {
var podBlob string
switch v := podInterface.(type) {
case map[string]interface{}:
for k := range v {
podBlob = k
}
case string:
podBlob = v
default:
return nil, nil, fmt.Errorf("malformed podfile.lock")
}
splits := strings.Split(podBlob, " ")
podName := splits[0]
podVersion := strings.TrimSuffix(strings.TrimPrefix(splits[1], "("), ")")
podRootPkg := strings.Split(podName, "/")[0]
pkgHash, exists := checksums[podRootPkg]
if !exists {
return nil, nil, fmt.Errorf("malformed podfile.lock: incomplete checksums")
}
pkgs = append(pkgs, &pkg.Package{
Name: podName,
Version: podVersion,
Type: pkg.CocoapodsPkg,
Language: pkg.Swift,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: podName,
Version: podVersion,
PkgHash: pkgHash.(string),
},
})
}
return pkgs, nil, nil
}
@@ -0,0 +1,306 @@
package swift
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
)
func TestParsePodfileLock(t *testing.T) {
expected := []*pkg.Package{
{
Name: "GlossButtonNode",
Version: "3.1.2",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "GlossButtonNode",
Version: "3.1.2",
PkgHash: "4ea1197a744f2fb5fb875fe31caf17ded4762e8f",
},
},
{
Name: "PINCache",
Version: "3.0.3",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "PINCache",
Version: "3.0.3",
PkgHash: "7a8fc1a691173d21dbddbf86cd515de6efa55086",
},
},
{
Name: "PINCache/Arc-exception-safe",
Version: "3.0.3",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "PINCache/Arc-exception-safe",
Version: "3.0.3",
PkgHash: "7a8fc1a691173d21dbddbf86cd515de6efa55086",
},
},
{
Name: "PINCache/Core",
Version: "3.0.3",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "PINCache/Core",
Version: "3.0.3",
PkgHash: "7a8fc1a691173d21dbddbf86cd515de6efa55086",
},
},
{
Name: "PINOperation",
Version: "1.2.1",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "PINOperation",
Version: "1.2.1",
PkgHash: "00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20",
},
},
{
Name: "PINRemoteImage/Core",
Version: "3.0.3",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "PINRemoteImage/Core",
Version: "3.0.3",
PkgHash: "f1295b29f8c5e640e25335a1b2bd9d805171bd01",
},
},
{
Name: "PINRemoteImage/iOS",
Version: "3.0.3",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "PINRemoteImage/iOS",
Version: "3.0.3",
PkgHash: "f1295b29f8c5e640e25335a1b2bd9d805171bd01",
},
},
{
Name: "PINRemoteImage/PINCache",
Version: "3.0.3",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "PINRemoteImage/PINCache",
Version: "3.0.3",
PkgHash: "f1295b29f8c5e640e25335a1b2bd9d805171bd01",
},
},
{
Name: "Reveal-SDK",
Version: "33",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "Reveal-SDK",
Version: "33",
PkgHash: "effba1c940b8337195563c425a6b5862ec875caa",
},
},
{
Name: "SwiftGen",
Version: "6.5.1",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "SwiftGen",
Version: "6.5.1",
PkgHash: "a6d22010845f08fe18fbdf3a07a8e380fd22e0ea",
},
},
{
Name: "Texture",
Version: "3.1.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "Texture",
Version: "3.1.0",
PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3",
},
},
{
Name: "Texture/AssetsLibrary",
Version: "3.1.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "Texture/AssetsLibrary",
Version: "3.1.0",
PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3",
},
},
{
Name: "Texture/Core",
Version: "3.1.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "Texture/Core",
Version: "3.1.0",
PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3",
},
},
{
Name: "Texture/MapKit",
Version: "3.1.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "Texture/MapKit",
Version: "3.1.0",
PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3",
},
},
{
Name: "Texture/Photos",
Version: "3.1.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "Texture/Photos",
Version: "3.1.0",
PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3",
},
},
{
Name: "Texture/PINRemoteImage",
Version: "3.1.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "Texture/PINRemoteImage",
Version: "3.1.0",
PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3",
},
},
{
Name: "Texture/Video",
Version: "3.1.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "Texture/Video",
Version: "3.1.0",
PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3",
},
},
{
Name: "TextureSwiftSupport",
Version: "3.13.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "TextureSwiftSupport",
Version: "3.13.0",
PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb",
},
},
{
Name: "TextureSwiftSupport/Components",
Version: "3.13.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "TextureSwiftSupport/Components",
Version: "3.13.0",
PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb",
},
},
{
Name: "TextureSwiftSupport/Experiments",
Version: "3.13.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "TextureSwiftSupport/Experiments",
Version: "3.13.0",
PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb",
},
},
{
Name: "TextureSwiftSupport/Extensions",
Version: "3.13.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "TextureSwiftSupport/Extensions",
Version: "3.13.0",
PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb",
},
},
{
Name: "TextureSwiftSupport/LayoutSpecBuilders",
Version: "3.13.0",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "TextureSwiftSupport/LayoutSpecBuilders",
Version: "3.13.0",
PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb",
},
},
{
Name: "TinyConstraints",
Version: "4.0.2",
Language: pkg.Swift,
Type: pkg.CocoapodsPkg,
MetadataType: pkg.CocoapodsMetadataType,
Metadata: pkg.CocoapodsMetadata{
Name: "TinyConstraints",
Version: "4.0.2",
PkgHash: "7b7ccc0c485bb3bb47082138ff28bc33cd49897f",
},
},
}
fixture, err := os.Open("test-fixtures/Podfile.lock")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
// TODO: no relationships are under test yet
actual, _, err := parsePodfileLock(fixture.Name(), fixture)
if err != nil {
t.Error(err)
}
differences := deep.Equal(expected, actual)
if differences != nil {
t.Errorf("returned package list differed from expectation: %+v", differences)
}
}
@@ -0,0 +1,92 @@
PODS:
- GlossButtonNode (3.1.2):
- Texture/Core (~> 3)
- TextureSwiftSupport (>= 3.10.0)
- PINCache (3.0.3):
- PINCache/Arc-exception-safe (= 3.0.3)
- PINCache/Core (= 3.0.3)
- PINCache/Arc-exception-safe (3.0.3):
- PINCache/Core
- PINCache/Core (3.0.3):
- PINOperation (~> 1.2.1)
- PINOperation (1.2.1)
- PINRemoteImage/Core (3.0.3):
- PINOperation
- PINRemoteImage/iOS (3.0.3):
- PINRemoteImage/Core
- PINRemoteImage/PINCache (3.0.3):
- PINCache (~> 3.0.3)
- PINRemoteImage/Core
- Reveal-SDK (33)
- SwiftGen (6.5.1)
- Texture (3.1.0):
- Texture/AssetsLibrary (= 3.1.0)
- Texture/Core (= 3.1.0)
- Texture/MapKit (= 3.1.0)
- Texture/Photos (= 3.1.0)
- Texture/PINRemoteImage (= 3.1.0)
- Texture/Video (= 3.1.0)
- Texture/AssetsLibrary (3.1.0):
- Texture/Core
- Texture/Core (3.1.0)
- Texture/MapKit (3.1.0):
- Texture/Core
- Texture/Photos (3.1.0):
- Texture/Core
- Texture/PINRemoteImage (3.1.0):
- PINRemoteImage/iOS (~> 3.0.0)
- PINRemoteImage/PINCache
- Texture/Core
- Texture/Video (3.1.0):
- Texture/Core
- TextureSwiftSupport (3.13.0):
- Texture/Core (>= 3)
- TextureSwiftSupport/Components (= 3.13.0)
- TextureSwiftSupport/Experiments (= 3.13.0)
- TextureSwiftSupport/Extensions (= 3.13.0)
- TextureSwiftSupport/LayoutSpecBuilders (= 3.13.0)
- TextureSwiftSupport/Components (3.13.0):
- Texture/Core (>= 3)
- TextureSwiftSupport/LayoutSpecBuilders
- TextureSwiftSupport/Experiments (3.13.0):
- Texture/Core (>= 3)
- TextureSwiftSupport/Extensions (3.13.0):
- Texture/Core (>= 3)
- TextureSwiftSupport/LayoutSpecBuilders (3.13.0):
- Texture/Core (>= 3)
- TinyConstraints (4.0.2)
DEPENDENCIES:
- GlossButtonNode
- Reveal-SDK
- SwiftGen
- Texture
- TextureSwiftSupport
- TinyConstraints
SPEC REPOS:
trunk:
- GlossButtonNode
- PINCache
- PINOperation
- PINRemoteImage
- Reveal-SDK
- SwiftGen
- Texture
- TextureSwiftSupport
- TinyConstraints
SPEC CHECKSUMS:
GlossButtonNode: 4ea1197a744f2fb5fb875fe31caf17ded4762e8f
PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086
PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20
PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01
Reveal-SDK: effba1c940b8337195563c425a6b5862ec875caa
SwiftGen: a6d22010845f08fe18fbdf3a07a8e380fd22e0ea
Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3
TextureSwiftSupport: c515c7927fab92d0d9485f49b885b8c5de34fbfb
TinyConstraints: 7b7ccc0c485bb3bb47082138ff28bc33cd49897f
PODFILE CHECKSUM: 07aa55f54421f6e6d3a920c11716a89fc9243d1b
COCOAPODS: 1.11.2
+27
View File
@@ -0,0 +1,27 @@
package pkg
import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/linux"
)
var _ urlIdentifier = (*CocoapodsMetadata)(nil)
type CocoapodsMetadata struct {
Name string `mapstructure:"name" json:"name"`
Version string `mapstructure:"version" json:"version"`
PkgHash string `mapstructure:"pkgHash" json:"pkgHash"`
}
func (m CocoapodsMetadata) PackageURL(_ *linux.Release) string {
var qualifiers packageurl.Qualifiers
return packageurl.NewPackageURL(
packageurl.TypeCocoapods,
"",
m.Name,
m.Version,
qualifiers,
"",
).ToString()
}

Some files were not shown because too many files have changed in this diff Show More