mirror of
https://github.com/anchore/syft.git
synced 2026-10-14 07:07:18 +02:00
Compare commits
58
Commits
plugin-system
...
v0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bf1e643eb | ||
|
|
3ba67a6bcc | ||
|
|
4a8ee15e87 | ||
|
|
dcb5d6d08e | ||
|
|
b3387807c1 | ||
|
|
24c4c025e1 | ||
|
|
5d0909f31e | ||
|
|
1f64b3cd49 | ||
|
|
8d25d44ea9 | ||
|
|
1fc46291a6 | ||
|
|
2b932f8d65 | ||
|
|
0d3e153959 | ||
|
|
587589bfe3 | ||
|
|
4ff330e396 | ||
|
|
949bf7bc91 | ||
|
|
956af57d3b | ||
|
|
262a0888a8 | ||
|
|
335a6b022f | ||
|
|
da0eb6f20f | ||
|
|
1be5f5756a | ||
|
|
9edbc65bce | ||
|
|
398d8903e7 | ||
|
|
46c74865e5 | ||
|
|
1f0f6fa3e5 | ||
|
|
abdd00cd24 | ||
|
|
10b44f5311 | ||
|
|
1c320a8382 | ||
|
|
7a8a5419b8 | ||
|
|
16b23e7994 | ||
|
|
e6b71486b0 | ||
|
|
5adfce19e0 | ||
|
|
b6dfdf16b6 | ||
|
|
b5ce187ba7 | ||
|
|
26855a2a9e | ||
|
|
4b78d9a1c0 | ||
|
|
2844b9878f | ||
|
|
8b81c87d18 | ||
|
|
9bd9dad76c | ||
|
|
6d9f9a9b3b | ||
|
|
50cb5612d0 | ||
|
|
080f010746 | ||
|
|
f4502fc824 | ||
|
|
03378e976c | ||
|
|
576fbc898b | ||
|
|
49800b6747 | ||
|
|
c46d004a3b | ||
|
|
3d91a66536 | ||
|
|
45b5cab49a | ||
|
|
1866949eff | ||
|
|
0567206b38 | ||
|
|
f3076c3905 | ||
|
|
b467964044 | ||
|
|
e26627d8b6 | ||
|
|
80ee18d20b | ||
|
|
ef64a6f206 | ||
|
|
22dfcb2d0b | ||
|
|
329b080a37 | ||
|
|
11334b632e |
@@ -1,129 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
run-static-analysis:
|
||||
parameters:
|
||||
version:
|
||||
type: string
|
||||
docker:
|
||||
- image: circleci/golang:<< parameters.version >>
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
# work around for recent circle CI breaking change
|
||||
# Error: "Error response from daemon: client version 1.39 is too new. Maximum supported API version is 1.38"
|
||||
DOCKER_API_VERSION: "1.38"
|
||||
# 2CPU / 4GB RAM
|
||||
resource_class: medium
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-<< parameters.version >>-{{ checksum "go.sum" }}-{{ checksum "Makefile" }}
|
||||
|
||||
- run: make ci-bootstrap
|
||||
|
||||
- save_cache:
|
||||
key: go-<< parameters.version >>-{{ checksum "go.sum" }}-{{ checksum "Makefile" }}
|
||||
paths:
|
||||
- "/go/pkg/mod"
|
||||
- ".tmp"
|
||||
|
||||
- run:
|
||||
name: run static analysis
|
||||
command: make static-analysis
|
||||
|
||||
run-tests:
|
||||
parameters:
|
||||
version:
|
||||
type: string
|
||||
docker:
|
||||
- image: circleci/golang:<< parameters.version >>
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
# work around for recent circle CI breaking change
|
||||
# Error: "Error response from daemon: client version 1.39 is too new. Maximum supported API version is 1.38"
|
||||
DOCKER_API_VERSION: "1.38"
|
||||
# 2CPU / 4GB RAM
|
||||
resource_class: medium
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-<< parameters.version >>-{{ checksum "go.sum" }}-{{ checksum "Makefile" }}
|
||||
|
||||
- run: make ci-bootstrap
|
||||
|
||||
- save_cache:
|
||||
key: go-<< parameters.version >>-{{ checksum "go.sum" }}-{{ checksum "Makefile" }}
|
||||
paths:
|
||||
- "/go/pkg/mod"
|
||||
- ".tmp"
|
||||
|
||||
- setup_remote_docker:
|
||||
version: 18.06.0-ce
|
||||
|
||||
- run:
|
||||
name: enable docker client
|
||||
command: |
|
||||
# all of this to enable "circleci local execute ..." cli commands for /var/run/docker.sock
|
||||
mkdir -p ${HOME}/.local/bin
|
||||
cat \<< EOF > ${HOME}/.local/bin/docker
|
||||
#!/bin/bash
|
||||
set -xue
|
||||
sudo -E ${HOME}/.local/bin/docker.bin \$@
|
||||
EOF
|
||||
sudo mv /usr/bin/docker ${HOME}/.local/bin/docker.bin
|
||||
chmod 755 ${HOME}/.local/bin/docker
|
||||
|
||||
- run:
|
||||
name: build cache key for java test-fixture blobs
|
||||
command: make java-packages-fingerprint
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- syft-unit-test-java-cache-{{ checksum "syft/cataloger/java/test-fixtures/java-builds/packages.fingerprint" }}
|
||||
|
||||
- run:
|
||||
name: run unit tests
|
||||
command: make unit
|
||||
|
||||
- save_cache:
|
||||
key: syft-unit-test-java-cache-{{ checksum "syft/cataloger/java/test-fixtures/java-builds/packages.fingerprint" }}
|
||||
paths:
|
||||
- "syft/cataloger/java/test-fixtures/java-builds/packages"
|
||||
|
||||
- run:
|
||||
name: validate syft output against the CycloneDX schema
|
||||
command: make validate-cyclonedx-schema
|
||||
|
||||
- run:
|
||||
name: build hash key for integration test-fixtures blobs
|
||||
command: make integration-fingerprint
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- syft-integration-test-tar-cache-{{ checksum "test/integration/test-fixtures/tar-cache.fingerprint" }}
|
||||
- run:
|
||||
name: run integration tests
|
||||
command: make integration
|
||||
|
||||
- save_cache:
|
||||
key: syft-integration-test-tar-cache-{{ checksum "test/integration/test-fixtures/tar-cache.fingerprint" }}
|
||||
paths:
|
||||
- "test/integration/test-fixtures/tar-cache"
|
||||
|
||||
workflows:
|
||||
# Note: changing this workflow name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
"Static Analysis + Unit + Integration":
|
||||
jobs:
|
||||
- run-static-analysis:
|
||||
name: "Static Analysis"
|
||||
version: "1.14"
|
||||
- run-tests:
|
||||
name: "Unit & Integration Tests (go-1.13)"
|
||||
version: "1.13"
|
||||
- run-tests:
|
||||
name: "Unit & Integration Tests (go-latest)"
|
||||
version: "latest"
|
||||
@@ -16,5 +16,5 @@ assignees: ''
|
||||
**Anything else we need to know?**:
|
||||
|
||||
**Environment**:
|
||||
- syft version (use `syft version -v`):
|
||||
- Output of `syft version`:
|
||||
- OS (e.g: `cat /etc/os-release` or similar):
|
||||
|
||||
@@ -64,6 +64,15 @@ jobs:
|
||||
name: artifacts
|
||||
path: snapshot/**/*
|
||||
|
||||
- uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
fields: repo,workflow,job,commit,message,author
|
||||
text: The syft acceptance tests have failed tragically!
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
if: ${{ failure() }}
|
||||
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
Acceptance-Linux:
|
||||
needs: [ Build-Snapshot-Artifacts ]
|
||||
@@ -80,6 +89,15 @@ jobs:
|
||||
- name: Run Acceptance Tests (Linux)
|
||||
run: make acceptance-linux
|
||||
|
||||
- uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
fields: repo,workflow,job,commit,message,author
|
||||
text: The syft acceptance tests have failed tragically!
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
if: ${{ failure() }}
|
||||
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
Acceptance-Mac:
|
||||
needs: [ Build-Snapshot-Artifacts ]
|
||||
@@ -96,6 +114,15 @@ jobs:
|
||||
- name: Run Acceptance Tests (Mac)
|
||||
run: make acceptance-mac
|
||||
|
||||
- uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
fields: repo,workflow,job,commit,message,author
|
||||
text: The syft acceptance tests have failed tragically!
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
if: ${{ failure() }}
|
||||
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
Inline-Compare:
|
||||
needs: [ Build-Snapshot-Artifacts ]
|
||||
@@ -120,4 +147,13 @@ jobs:
|
||||
path: snapshot
|
||||
|
||||
- name: Compare Anchore inline-scan results against snapshot build output
|
||||
run: make compare-snapshot
|
||||
run: make compare-snapshot
|
||||
|
||||
- uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
fields: repo,workflow,job,commit,message,author
|
||||
text: The syft acceptance tests have failed tragically!
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
if: ${{ failure() }}
|
||||
@@ -25,13 +25,22 @@ jobs:
|
||||
git fetch origin main
|
||||
git merge-base --is-ancestor ${GITHUB_REF##*/} origin/main && echo "${GITHUB_REF##*/} is a commit on main!"
|
||||
|
||||
- name: Check static anaylysis, unit, and integration test results
|
||||
- name: Check static analysis results
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
id: sa-unit-int
|
||||
id: static-analysis
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This check name is defined as the circle-ci workflow name (in .circleci/config.yaml)
|
||||
checkName: "Static Analysis + Unit + Integration"
|
||||
checkName: "Static-Analysis (1.x, ubuntu-latest)"
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check unit + integration results (latest go version)
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
id: unit-integration
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# This check name is defined as the circle-ci workflow name (in .circleci/config.yaml)
|
||||
checkName: "Tests (1.x, ubuntu-latest)"
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check acceptance test results (linux)
|
||||
@@ -62,9 +71,10 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Quality gate
|
||||
if: steps.sa-unit-int.outputs.conclusion != 'success' || steps.inline-compare.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success'
|
||||
if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit-integration.outputs.conclusion != 'success' || steps.inline-compare.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success'
|
||||
run: |
|
||||
echo "Static/Unit/Integration Status: ${{ steps.sa-unit-int.outputs.conclusion }}"
|
||||
echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}"
|
||||
echo "Unit & Integration Test Status: ${{ steps.unit-integration.outputs.conclusion }}"
|
||||
echo "Acceptance Test (Linux) Status: ${{ steps.acceptance-linux.outputs.conclusion }}"
|
||||
echo "Acceptance Test (Mac) Status: ${{ steps.acceptance-mac.outputs.conclusion }}"
|
||||
echo "Inline Compare Status: ${{ steps.inline-compare.outputs.conclusion }}"
|
||||
@@ -123,6 +133,15 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
- uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
fields: repo,workflow,action,eventName
|
||||
text: "A new Syft release is ready to be manually published: https://github.com/anchore/syft/releases"
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
if: ${{ success() }}
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
name: 'Static Analysis + Unit + Integration'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
pull_request:
|
||||
jobs:
|
||||
|
||||
Static-Analysis:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.x]
|
||||
platform: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore bootstrap cache
|
||||
id: bootstrap-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-${{ hashFiles('Makefile') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-
|
||||
${{ runner.os }}-go-${{ matrix.go-version }}-
|
||||
|
||||
- name: Bootstrap project dependencies
|
||||
if: steps.bootstrap-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
|
||||
- name: Bootstrap CI dependencies
|
||||
run: make ci-bootstrap
|
||||
|
||||
- name: Run static analysis
|
||||
run: make static-analysis
|
||||
|
||||
Tests:
|
||||
strategy:
|
||||
matrix:
|
||||
# test the lower bounds of support, and the latest available
|
||||
go-version: [1.13.x, 1.x]
|
||||
platform: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore bootstrap cache
|
||||
id: bootstrap-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-${{ hashFiles('Makefile') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-
|
||||
${{ runner.os }}-go-${{ matrix.go-version }}-
|
||||
|
||||
- name: Bootstrap project dependencies
|
||||
if: steps.bootstrap-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
|
||||
- name: Bootstrap CI dependencies
|
||||
run: make ci-bootstrap
|
||||
|
||||
- name: Build cache key for java test-fixture blobs (for unit tests)
|
||||
run: make java-packages-fingerprint
|
||||
|
||||
- name: Restore Java test-fixture cache
|
||||
id: unit-java-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: syft/cataloger/java/test-fixtures/java-builds/packages
|
||||
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }}
|
||||
|
||||
- name: Run unit tests
|
||||
run: make unit
|
||||
|
||||
- name: Validate syft output against the CycloneDX schema
|
||||
run: make validate-cyclonedx-schema
|
||||
|
||||
- name: Build key for tar cache
|
||||
run: make integration-fingerprint
|
||||
|
||||
- name: Restore integration test cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ github.workspace }}/integration/test-fixtures/cache
|
||||
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('integration/test-fixtures/cache.fingerprint') }}
|
||||
|
||||
- name: Run integration tests
|
||||
run: make integration
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
release:
|
||||
# If set to auto, will mark the release as not ready for production in case there is an indicator for this in the
|
||||
# tag e.g. v1.0.0-rc1 .If set to true, will mark the release as not ready for production.
|
||||
prerelease: false
|
||||
prerelease: auto
|
||||
|
||||
# If set to true, will not auto-publish the release. This is done to allow us to review the changelog before publishing.
|
||||
draft: true
|
||||
|
||||
@@ -27,6 +27,10 @@ ifeq "$(strip $(VERSION))" ""
|
||||
override VERSION = $(shell git describe --always --tags --dirty)
|
||||
endif
|
||||
|
||||
# used to generate the changelog from the second to last tag to the current tag (used in the release pipeline when the release tag is in place)
|
||||
LAST_TAG := $(shell git describe --abbrev=0 --tags $(shell git rev-list --tags --max-count=1))
|
||||
SECOND_TO_LAST_TAG := $(shell git describe --abbrev=0 --tags $(shell git rev-list --tags --skip=1 --max-count=1))
|
||||
|
||||
## Variable assertions
|
||||
|
||||
ifndef TEMPDIR
|
||||
@@ -67,7 +71,7 @@ help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: ci-bootstrap
|
||||
ci-bootstrap: bootstrap
|
||||
ci-bootstrap:
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y bc jq libxml2-utils
|
||||
|
||||
.PHONY: bootstrap
|
||||
@@ -126,11 +130,11 @@ unit: fixtures ## Run unit tests (with coverage)
|
||||
.PHONY: integration
|
||||
integration: ## Run integration tests
|
||||
$(call title,Running integration tests)
|
||||
go test -v -tags=integration ./test/integration
|
||||
go test -tags=integration -v ./test/integration
|
||||
|
||||
# note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted
|
||||
integration-fingerprint:
|
||||
find test/integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee test/integration/test-fixtures/tar-cache.fingerprint
|
||||
find test/integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee test/integration/test-fixtures/cache.fingerprint
|
||||
|
||||
.PHONY: java-packages-fingerprint
|
||||
java-packages-fingerprint:
|
||||
@@ -157,17 +161,7 @@ generate-json-schema: clean-json-schema-examples integration ## Generate a new j
|
||||
|
||||
.PHONY: clear-test-cache
|
||||
clear-test-cache: ## Delete all test cache (built docker image tars)
|
||||
find . -type f -wholename "**/test-fixtures/tar-cache/*.tar" -delete
|
||||
|
||||
.PHONY: check-pipeline
|
||||
check-pipeline: ## Run local CircleCI pipeline locally (sanity check)
|
||||
$(call title,Check pipeline)
|
||||
# note: this is meant for local development & testing of the pipeline, NOT to be run in CI
|
||||
mkdir -p $(TEMPDIR)
|
||||
circleci config process .circleci/config.yml > .tmp/circleci.yml
|
||||
circleci local execute -c .tmp/circleci.yml --job "Static Analysis"
|
||||
circleci local execute -c .tmp/circleci.yml --job "Unit & Integration Tests (go-latest)"
|
||||
@printf '$(SUCCESS)Pipeline checks pass!$(RESET)\n'
|
||||
find . -type f -wholename "**/test-fixtures/cache/*.tar" -delete
|
||||
|
||||
.PHONY: build
|
||||
build: $(SNAPSHOTDIR) ## Build release snapshot binaries and packages
|
||||
@@ -226,32 +220,53 @@ acceptance-test-rpm-package-install: $(SNAPSHOTDIR)
|
||||
$(ACC_TEST_IMAGE) \
|
||||
$(RESULTSDIR)
|
||||
|
||||
.PHONY: changlog
|
||||
changelog:
|
||||
mkdir -p $(DISTDIR)
|
||||
.PHONY: changlog-release
|
||||
changelog-release:
|
||||
@echo "Last tag: $(SECOND_TO_LAST_TAG)"
|
||||
@echo "Current tag: $(VERSION)"
|
||||
@docker run -i --rm \
|
||||
-v "$(shell pwd)":/usr/local/src/your-app ferrarimarco/github-changelog-generator \
|
||||
--user anchore \
|
||||
--project $(BIN) \
|
||||
-t ${GITHUB_TOKEN} \
|
||||
--exclude-labels 'duplicate,question,invalid,wontfix,size:small,size:medium,size:large,size:x-large' \
|
||||
--no-pr-wo-labels \
|
||||
--no-issues-wo-labels \
|
||||
--since-tag $(SECOND_TO_LAST_TAG)
|
||||
|
||||
@printf '\n$(BOLD)$(CYAN)Release $(VERSION) Changelog$(RESET)\n\n'
|
||||
@cat CHANGELOG.md
|
||||
|
||||
.PHONY: changelog-unreleased
|
||||
changelog-unreleased: ## show the current changelog that will be produced on the next release (note: requires GITHUB_TOKEN set)
|
||||
@docker run -it --rm \
|
||||
-v "$(shell pwd)":/usr/local/src/your-app ferrarimarco/github-changelog-generator \
|
||||
--user anchore \
|
||||
--project $(BIN) \
|
||||
-t ${GITHUB_TOKEN} \
|
||||
--no-pr-wo-labels \
|
||||
--no-issues-wo-labels \
|
||||
--unreleased-only \
|
||||
--future-release $(VERSION)
|
||||
--exclude-labels 'duplicate,question,invalid,wontfix,size:small,size:medium,size:large,size:x-large' \
|
||||
--since-tag $(LAST_TAG)
|
||||
|
||||
@printf '\n$(BOLD)$(CYAN)Unreleased Changes (closed PRs and issues will not be in the final changelog)$(RESET)\n'
|
||||
|
||||
@docker run -it --rm \
|
||||
-v $(shell pwd)/CHANGELOG.md:/CHANGELOG.md \
|
||||
rawkode/mdv \
|
||||
-t 748.5989 \
|
||||
/CHANGELOG.md
|
||||
|
||||
.PHONY: release
|
||||
release: clean-dist changelog ## Build and publish final binaries and packages
|
||||
release: clean-dist changelog-release ## Build and publish final binaries and packages
|
||||
$(call title,Publishing release artifacts)
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
# release
|
||||
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
|
||||
$(TEMPDIR)/goreleaser \
|
||||
bash -c "BUILD_GIT_TREE_STATE=$(GITTREESTATE) $(TEMPDIR)/goreleaser \
|
||||
--rm-dist \
|
||||
--config $(TEMPDIR)/goreleaser.yaml \
|
||||
--release-notes <(cat CHANGELOG.md)
|
||||
--release-notes <(cat CHANGELOG.md)"
|
||||
|
||||
# verify checksum signatures
|
||||
.github/scripts/verify-signature.sh "$(DISTDIR)"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# syft
|
||||
|
||||
[](https://circleci.com/gh/anchore/syft)
|
||||
[](https://github.com/anchore/syft/actions?query=workflow%3A%22Static+Analysis+%2B+Unit+%2B+Integration%22)
|
||||
[](https://github.com/anchore/syft/actions?query=workflow%3AAcceptance)
|
||||
[](https://goreportcard.com/report/github.com/anchore/syft)
|
||||
[](https://github.com/anchore/syft/releases/latest)
|
||||
[](https://github.com/anchore/syft/blob/main/LICENSE)
|
||||
@@ -13,12 +14,13 @@ A CLI tool and go library for generating a Software Bill of Materials (SBOM) fro
|
||||
- Catalog container images and filesystems to discover packages and libraries.
|
||||
- Supports packages and libraries from various ecosystems (APK, DEB, RPM, Ruby Bundles, Python Wheel/Egg/requirements.txt, JavaScript NPM/Yarn, Java JAR/EAR/WAR, Jenkins plugins JPI/HPI, Go modules)
|
||||
- Linux distribution identification (supports Alpine, BusyBox, CentOS/RedHat, Debian/Ubuntu flavored distributions)
|
||||
- Supports Docker and OCI image formats
|
||||
|
||||
> :warning: **This is pre-release software** and it may not work as expected. If you encounter an issue, please [let us know using the issue tracker](https://github.com/anchore/syft/issues).
|
||||
If you encounter an issue, please [let us know using the issue tracker](https://github.com/anchore/syft/issues).
|
||||
|
||||
## Getting started
|
||||
|
||||
To generate an SBOM for an image:
|
||||
To generate an SBOM for a Docker or OCI image:
|
||||
```
|
||||
syft <image>
|
||||
```
|
||||
@@ -32,19 +34,24 @@ syft <image> --scope all-layers
|
||||
|
||||
Syft can generate a SBOM from a variety of sources:
|
||||
```
|
||||
# catalog a docker image tar (from the result of "docker image save ... -o image.tar" command)
|
||||
syft docker-archive://path/to/image.tar
|
||||
# catalog a container image archive (from the result of `docker image save ...`, `podman save ...`, or `skopeo copy` commands)
|
||||
syft path/to/image.tar
|
||||
|
||||
# catalog a directory
|
||||
syft dir://path/to/dir
|
||||
syft path/to/dir
|
||||
```
|
||||
|
||||
By default Syft shows a summary table, however, more detailed `text` and `json` formats are also available.
|
||||
The output format for Syft is configurable as well:
|
||||
```
|
||||
syft <image> -o json
|
||||
syft <image> -o text
|
||||
syft <image> -o <format>
|
||||
```
|
||||
|
||||
Where the `format`s available are:
|
||||
- `json`: Use this to get as much information out of Syft as possible!
|
||||
- `text`: A row-oriented, human-and-machine-friendly output.
|
||||
- `cyclonedx`: A XML report conforming to the [CycloneDX 1.2](https://cyclonedx.org/) specification.
|
||||
- `table`: A columnar summary (default).
|
||||
|
||||
## Installation
|
||||
|
||||
**Recommended**
|
||||
@@ -53,7 +60,7 @@ syft <image> -o text
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
# install a specific version into a specific dir
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s <RELEASE_VERSION> -b <SOME_BIN_PATH>
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <SOME_BIN_PATH> <RELEASE_VERSION>
|
||||
```
|
||||
|
||||
**macOS**
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// completionCmd represents the completion command
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish]",
|
||||
Short: "Generate a shell completion for Syft (listing local docker images)",
|
||||
Long: `To load completions (docker image list):
|
||||
|
||||
Bash:
|
||||
|
||||
$ source <(syft completion bash)
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
Linux:
|
||||
$ syft completion bash > /etc/bash_completion.d/syft
|
||||
MacOS:
|
||||
$ syft completion bash > /usr/local/etc/bash_completion.d/syft
|
||||
|
||||
Zsh:
|
||||
|
||||
# If shell completion is not already enabled in your environment you will need
|
||||
# to enable it. You can execute the following once:
|
||||
|
||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ syft completion zsh > "${fpath[1]}/_syft"
|
||||
|
||||
# You will need to start a new shell for this setup to take effect.
|
||||
|
||||
Fish:
|
||||
|
||||
$ syft completion fish | source
|
||||
|
||||
# To load completions for each session, execute once:
|
||||
$ syft completion fish > ~/.config/fish/completions/syft.fish
|
||||
`,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish"},
|
||||
Args: cobra.ExactValidArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var err error
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
err = cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
err = cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
err = cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(completionCmd)
|
||||
}
|
||||
+55
-4
@@ -1,8 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
@@ -12,6 +14,9 @@ import (
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/presenter"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
@@ -21,10 +26,15 @@ var rootCmd = &cobra.Command{
|
||||
Short: "A tool for generating a Software Bill Of Materials (SBOM) from container images and filesystems",
|
||||
Long: internal.Tprintf(`
|
||||
Supports the following image sources:
|
||||
{{.appName}} yourrepo/yourimage:tag defaults to using images from a docker daemon
|
||||
{{.appName}} docker://yourrepo/yourimage:tag explicitly use the docker daemon
|
||||
{{.appName}} tar://path/to/yourimage.tar use a tarball from disk
|
||||
{{.appName}} dir://path/to/yourproject read directly from a path in disk
|
||||
{{.appName}} yourrepo/yourimage:tag defaults to using images from a Docker daemon
|
||||
{{.appName}} path/to/yourproject a Docker tar, OCI tar, OCI directory, or generic filesystem directory
|
||||
|
||||
You can also explicitly specify the scheme to use:
|
||||
{{.appName}} docker:yourrepo/yourimage:tag explicitly use the Docker daemon
|
||||
{{.appName}} docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
|
||||
{{.appName}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Podman or otherwise)
|
||||
{{.appName}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||
{{.appName}} dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||
`, map[string]interface{}{
|
||||
"appName": internal.ApplicationName,
|
||||
}),
|
||||
@@ -44,6 +54,20 @@ Supports the following image sources:
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
// Since we use ValidArgsFunction, Cobra will call this AFTER having parsed all flags and arguments provided
|
||||
dockerImageRepoTags, err := ListLocalDockerImages(toComplete)
|
||||
if err != nil {
|
||||
// Indicates that an error occurred and completions should be ignored
|
||||
return []string{"completion failed"}, cobra.ShellCompDirectiveError
|
||||
}
|
||||
if len(dockerImageRepoTags) == 0 {
|
||||
return []string{"no docker images found"}, cobra.ShellCompDirectiveError
|
||||
}
|
||||
// ShellCompDirectiveDefault indicates that the shell will perform its default behavior after completions have
|
||||
// been provided (without implying other possible directives)
|
||||
return dockerImageRepoTags, cobra.ShellCompDirectiveDefault
|
||||
},
|
||||
}
|
||||
|
||||
func startWorker(userInput string) <-chan error {
|
||||
@@ -88,3 +112,30 @@ func doRunCmd(_ *cobra.Command, args []string) error {
|
||||
ux := ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet)
|
||||
return ux(errs, eventSubscription)
|
||||
}
|
||||
|
||||
func ListLocalDockerImages(prefix string) ([]string, error) {
|
||||
var repoTags = make([]string, 0)
|
||||
ctx := context.Background()
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
return repoTags, err
|
||||
}
|
||||
|
||||
// Only want to return tagged images
|
||||
imageListArgs := filters.NewArgs()
|
||||
imageListArgs.Add("dangling", "false")
|
||||
images, err := cli.ImageList(ctx, types.ImageListOptions{All: false, Filters: imageListArgs})
|
||||
if err != nil {
|
||||
return repoTags, err
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
// image may have multiple tags
|
||||
for _, tag := range image.RepoTags {
|
||||
if strings.HasPrefix(tag, prefix) {
|
||||
repoTags = append(repoTags, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
return repoTags, nil
|
||||
}
|
||||
|
||||
+28
-6
@@ -1,14 +1,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/presenter"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var showVerboseVersionInfo bool
|
||||
var outputFormat string
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
@@ -17,14 +21,15 @@ var versionCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionCmd.Flags().BoolVarP(&showVerboseVersionInfo, "verbose", "v", false, "show additional version information")
|
||||
|
||||
versionCmd.Flags().StringVarP(&outputFormat, "output", "o", string(presenter.TextPresenter), "format to show version information (available=[text, json])")
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
func printVersion(_ *cobra.Command, _ []string) {
|
||||
versionInfo := version.FromBuild()
|
||||
if showVerboseVersionInfo {
|
||||
|
||||
switch outputFormat {
|
||||
case "text":
|
||||
fmt.Println("Application: ", internal.ApplicationName)
|
||||
fmt.Println("Version: ", versionInfo.Version)
|
||||
fmt.Println("BuildDate: ", versionInfo.BuildDate)
|
||||
@@ -33,7 +38,24 @@ func printVersion(_ *cobra.Command, _ []string) {
|
||||
fmt.Println("Platform: ", versionInfo.Platform)
|
||||
fmt.Println("GoVersion: ", versionInfo.GoVersion)
|
||||
fmt.Println("Compiler: ", versionInfo.Compiler)
|
||||
} else {
|
||||
fmt.Printf("%s %s\n", internal.ApplicationName, versionInfo.Version)
|
||||
|
||||
case "json":
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
err := enc.Encode(&struct {
|
||||
version.Version
|
||||
Application string `json:"application"`
|
||||
}{
|
||||
Version: versionInfo,
|
||||
Application: internal.ApplicationName,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("failed to show version information: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
fmt.Printf("unsupported output format: %s\n", outputFormat)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,15 @@ go 1.14
|
||||
require (
|
||||
github.com/adrg/xdg v0.2.1
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e
|
||||
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/stereoscope v0.0.0-20200813152757-548b22c8a0b3
|
||||
github.com/anchore/stereoscope v0.0.0-20200925184903-c82da54e98fe
|
||||
github.com/bmatcuk/doublestar v1.3.1
|
||||
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/go-test/deep v1.0.6
|
||||
github.com/google/go-containerregistry v0.1.1 // indirect
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gookit/color v1.2.7
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
@@ -23,10 +21,10 @@ require (
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/package-url/packageurl-go v0.1.0
|
||||
github.com/pelletier/go-toml v1.8.0
|
||||
github.com/rogpeppe/go-internal v1.5.2
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/afero v1.2.2
|
||||
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d
|
||||
github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240
|
||||
@@ -34,10 +32,6 @@ require (
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
|
||||
golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect
|
||||
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7 // indirect
|
||||
gopkg.in/ini.v1 v1.57.0 // indirect
|
||||
golang.org/x/mod v0.3.0
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
||||
@@ -104,10 +104,10 @@ github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvo
|
||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk=
|
||||
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab h1:9pygWVFqbY9lPxM0peffumuVDyMuIMzNLyO9uFjJuQo=
|
||||
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/Microsoft/hcsshim v0.8.10 h1:k5wTrpnVU2/xv8ZuzGkbXVd3js5zJ8RnumPo5RxiIxU=
|
||||
github.com/Microsoft/hcsshim v0.8.10/go.mod h1:g5uw8EV2mAlzqe94tfNBNdr89fnbD/n3HV0OhsddkmM=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
|
||||
@@ -126,13 +126,12 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e h1:kty6r0R2JeaNPeWKSYDC+HW3hkqwFh4PP5TQ8pUPYFw=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e/go.mod h1:iYuIG0Nai8dR0ri3LhZQKUyO1loxUWAGvoWhXDmjy1A=
|
||||
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db h1:LWKezJnFTFxNkZ4MzajVf+YWvJS0+7hwFr59u6SS7cw=
|
||||
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db/go.mod h1:D3rc2L/q4Hcp9eeX6AIJH4Q+kPjOtJCFhG9za90j+nU=
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8=
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g=
|
||||
github.com/anchore/stereoscope v0.0.0-20200813152757-548b22c8a0b3 h1:pl+txuYlhK8Mmio4d+4zQI/1xg8X6BtNErTASrx23Wk=
|
||||
github.com/anchore/stereoscope v0.0.0-20200813152757-548b22c8a0b3/go.mod h1:WntReQTI/I27FOQ87UgLVVzWgku6+ZsqfOTLxpIZFCs=
|
||||
github.com/anchore/stereoscope v0.0.0-20200925184903-c82da54e98fe h1:m4NSyTo2fVUoUHAV/ZVqE/PFMr/y8oz9HRrhWLk9It0=
|
||||
github.com/anchore/stereoscope v0.0.0-20200925184903-c82da54e98fe/go.mod h1:2Jja/4l0zYggW52og+nn0rut4i+OYjCf9vTyrM8RT4E=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ=
|
||||
github.com/apex/log v1.3.0 h1:1fyfbPvUwD10nMoh3hY6MXzvZShJQn9/ck7ATgAt5pA=
|
||||
@@ -181,10 +180,11 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
|
||||
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
|
||||
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||
github.com/containerd/containerd v1.3.0 h1:xjvXQWABwS2uiv3TWgQt5Uth60Gu86LTGZXMJkjc7rY=
|
||||
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
@@ -193,7 +193,6 @@ github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
|
||||
github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI=
|
||||
github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
|
||||
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe h1:PEmIrUvwG9Yyv+0WKZqjXfSFDeZjs/q15g0m08BYS9k=
|
||||
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
|
||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||
@@ -209,10 +208,12 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -321,7 +322,7 @@ github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw=
|
||||
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -399,11 +400,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-containerregistry v0.0.0-20200430153450-5cbd060f5c92/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA=
|
||||
github.com/google/go-containerregistry v0.1.0 h1:hL5mVw7cTX3SBr64Arpv+cJH93L+Z9Q6WjckImYLB3g=
|
||||
github.com/google/go-containerregistry v0.1.0/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
|
||||
github.com/google/go-containerregistry v0.1.1 h1:AG8FSAfXglim2l5qSrqp5VK2Xl03PiBf25NiTGGamws=
|
||||
github.com/google/go-containerregistry v0.1.1/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
|
||||
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
|
||||
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
@@ -444,9 +442,8 @@ github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1K
|
||||
github.com/gookit/color v1.2.7 h1:4qePMNWZhrmbfYJDix+J4V2l0iVW+6jQGjicELlN14E=
|
||||
github.com/gookit/color v1.2.7/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/goreleaser/goreleaser v0.136.0 h1:Z+7XPrfGK11s/Sp+a06sx2FzGuCjTBdxN2ubpGvQbjY=
|
||||
github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904=
|
||||
github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w=
|
||||
@@ -667,7 +664,7 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM
|
||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/package-url/packageurl-go v0.1.0 h1:efWBc98O/dBZRg1pw2xiDzovnlMjCa9NPnfaiBduh8I=
|
||||
github.com/package-url/packageurl-go v0.1.0/go.mod h1:C/ApiuWpmbpni4DIOECf6WCjFUZV7O1Fx7VAzrZHgBw=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -740,7 +737,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@@ -766,6 +762,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905 h1:Xn3y6St5vlEgvxekK6JuArarc79GKAj/jbcsMRCaRH4=
|
||||
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905/go.mod h1:jCpPr/pv3OLY1D1nS9lN5HIdIpmOO/WTYJcAmnPq1nU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
@@ -788,6 +786,7 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
|
||||
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -820,8 +819,8 @@ github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbd
|
||||
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg=
|
||||
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs=
|
||||
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
@@ -959,11 +958,10 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -980,8 +978,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1000,7 +996,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1012,7 +1007,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1020,6 +1015,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1028,14 +1024,11 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
|
||||
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1098,7 +1091,6 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
@@ -1174,20 +1166,17 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200519141106-08726f379972/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0=
|
||||
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb h1:ek2py5bOqzR7MR/6obzk0rXUgYCLmjyLnaO9ssT+l6w=
|
||||
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7 h1:1N7l1PuXZwEK7OhHdmKQROOM75PnUjABGwvVRbLBgFk=
|
||||
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
@@ -1223,8 +1212,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
|
||||
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
|
||||
@@ -47,7 +47,7 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
|
||||
defer func() {
|
||||
err = zipReader.Close()
|
||||
if err != nil {
|
||||
log.Errorf("unable to close zip archive (%s): %w", archivePath, err)
|
||||
log.Errorf("unable to close zip archive (%s): %+v", archivePath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths
|
||||
defer func() {
|
||||
err = zipReader.Close()
|
||||
if err != nil {
|
||||
log.Errorf("unable to close zip archive (%s): %w", archivePath, err)
|
||||
log.Errorf("unable to close zip archive (%s): %+v", archivePath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ var platform = fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
// Version defines the application version details (generally from build information)
|
||||
type Version struct {
|
||||
Version string // application semantic version
|
||||
GitCommit string // git SHA at build-time
|
||||
GitTreeState string // indication of git tree (either "clean" or "dirty") at build-time
|
||||
BuildDate string // date of the build
|
||||
GoVersion string // go runtime version at build-time
|
||||
Compiler string // compiler used at build-time
|
||||
Platform string // GOOS and GOARCH at build-time
|
||||
Version string `json:"version"` // application semantic version
|
||||
GitCommit string `json:"gitCommit"` // git SHA at build-time
|
||||
GitTreeState string `json:"gitTreeState"` // indication of git tree (either "clean" or "dirty") at build-time
|
||||
BuildDate string `json:"buildDate"` // date of the build
|
||||
GoVersion string `json:"goVersion"` // go runtime version at build-time
|
||||
Compiler string `json:"compiler"` // compiler used at build-time
|
||||
Platform string `json:"platform"` // GOOS and GOARCH at build-time
|
||||
}
|
||||
|
||||
// FromBuild provides all version details
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Source this file in your shell sessions in order to use this shell completion.
|
||||
|
||||
complete -W "$(docker images --format '{{.Repository}}:{{.Tag}}')" syft
|
||||
+168
-126
@@ -4,7 +4,7 @@
|
||||
"artifacts": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"found-by": {
|
||||
"foundBy": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"layer-index": {
|
||||
"layerIndex": {
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
@@ -26,7 +26,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"layer-index",
|
||||
"layerIndex",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
@@ -48,43 +48,59 @@
|
||||
},
|
||||
"files": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"checksum": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
"owner-gid": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner-uid": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "string"
|
||||
{
|
||||
"properties": {
|
||||
"checksum": {
|
||||
"type": "string"
|
||||
},
|
||||
"ownerGid": {
|
||||
"type": "string"
|
||||
},
|
||||
"ownerUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"checksum",
|
||||
"ownerGid",
|
||||
"ownerUid",
|
||||
"path",
|
||||
"permissions"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"checksum",
|
||||
"owner-gid",
|
||||
"owner-uid",
|
||||
"path",
|
||||
"permissions"
|
||||
],
|
||||
"type": "object"
|
||||
]
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"git-commit-of-apk-port": {
|
||||
"gitCommitOfApkPort": {
|
||||
"type": "string"
|
||||
},
|
||||
"installed-size": {
|
||||
"homepage": {
|
||||
"type": "string"
|
||||
},
|
||||
"installedSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
"license": {
|
||||
"type": "string"
|
||||
},
|
||||
"licenses": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"maintainer": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -95,7 +111,7 @@
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"extra-fields": {
|
||||
"extraFields": {
|
||||
"properties": {
|
||||
"Archiver-Version": {
|
||||
"type": "string"
|
||||
@@ -160,60 +176,63 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"implementation-title": {
|
||||
"implementationTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"implementation-vendor": {
|
||||
"implementationVendor": {
|
||||
"type": "string"
|
||||
},
|
||||
"implementation-version": {
|
||||
"implementationVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"manifest-version": {
|
||||
"manifestVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"specification-title": {
|
||||
"specificationTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"specification-vendor": {
|
||||
"specificationVendor": {
|
||||
"type": "string"
|
||||
},
|
||||
"specification-version": {
|
||||
"specificationVersion": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"extra-fields",
|
||||
"implementation-title",
|
||||
"implementation-vendor",
|
||||
"implementation-version",
|
||||
"manifest-version",
|
||||
"extraFields",
|
||||
"implementationTitle",
|
||||
"implementationVendor",
|
||||
"implementationVersion",
|
||||
"manifestVersion",
|
||||
"name",
|
||||
"specification-title",
|
||||
"specification-vendor",
|
||||
"specification-version"
|
||||
"specificationTitle",
|
||||
"specificationVendor",
|
||||
"specificationVersion"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"origin-package": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"originPackage": {
|
||||
"type": "string"
|
||||
},
|
||||
"package": {
|
||||
"type": "string"
|
||||
},
|
||||
"parent-package": {
|
||||
"parentPackage": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"found-by": {
|
||||
"foundBy": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
@@ -229,7 +248,7 @@
|
||||
"properties": {
|
||||
"manifest": {
|
||||
"properties": {
|
||||
"extra-fields": {
|
||||
"extraFields": {
|
||||
"properties": {
|
||||
"Archiver-Version": {
|
||||
"type": "string"
|
||||
@@ -294,59 +313,59 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"implementation-title": {
|
||||
"implementationTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"implementation-vendor": {
|
||||
"implementationVendor": {
|
||||
"type": "string"
|
||||
},
|
||||
"implementation-version": {
|
||||
"implementationVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"manifest-version": {
|
||||
"manifestVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"specification-title": {
|
||||
"specificationTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"specification-vendor": {
|
||||
"specificationVendor": {
|
||||
"type": "string"
|
||||
},
|
||||
"specification-version": {
|
||||
"specificationVersion": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"extra-fields",
|
||||
"implementation-title",
|
||||
"implementation-vendor",
|
||||
"implementation-version",
|
||||
"manifest-version",
|
||||
"extraFields",
|
||||
"implementationTitle",
|
||||
"implementationVendor",
|
||||
"implementationVersion",
|
||||
"manifestVersion",
|
||||
"name",
|
||||
"specification-title",
|
||||
"specification-vendor",
|
||||
"specification-version"
|
||||
"specificationTitle",
|
||||
"specificationVendor",
|
||||
"specificationVersion"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"parent-package": {
|
||||
"parentPackage": {
|
||||
"type": "null"
|
||||
},
|
||||
"pom-properties": {
|
||||
"pomProperties": {
|
||||
"properties": {
|
||||
"Path": {
|
||||
"type": "string"
|
||||
},
|
||||
"artifact-id": {
|
||||
"artifactId": {
|
||||
"type": "string"
|
||||
},
|
||||
"extra-fields": {
|
||||
"extraFields": {
|
||||
"type": "null"
|
||||
},
|
||||
"group-id": {
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -358,9 +377,9 @@
|
||||
},
|
||||
"required": [
|
||||
"Path",
|
||||
"artifact-id",
|
||||
"extra-fields",
|
||||
"group-id",
|
||||
"artifactId",
|
||||
"extraFields",
|
||||
"groupId",
|
||||
"name",
|
||||
"version"
|
||||
],
|
||||
@@ -369,8 +388,8 @@
|
||||
},
|
||||
"required": [
|
||||
"manifest",
|
||||
"parent-package",
|
||||
"pom-properties"
|
||||
"parentPackage",
|
||||
"pomProperties"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -385,7 +404,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"found-by",
|
||||
"foundBy",
|
||||
"language",
|
||||
"licenses",
|
||||
"manifest",
|
||||
@@ -398,18 +417,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"pom-properties": {
|
||||
"pomProperties": {
|
||||
"properties": {
|
||||
"Path": {
|
||||
"type": "string"
|
||||
},
|
||||
"artifact-id": {
|
||||
"artifactId": {
|
||||
"type": "string"
|
||||
},
|
||||
"extra-fields": {
|
||||
"extraFields": {
|
||||
"type": "null"
|
||||
},
|
||||
"group-id": {
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -421,18 +440,18 @@
|
||||
},
|
||||
"required": [
|
||||
"Path",
|
||||
"artifact-id",
|
||||
"extra-fields",
|
||||
"group-id",
|
||||
"artifactId",
|
||||
"extraFields",
|
||||
"groupId",
|
||||
"name",
|
||||
"version"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"pull-checksum": {
|
||||
"pullChecksum": {
|
||||
"type": "string"
|
||||
},
|
||||
"pull-dependencies": {
|
||||
"pullDependencies": {
|
||||
"type": "string"
|
||||
},
|
||||
"release": {
|
||||
@@ -444,9 +463,15 @@
|
||||
"source": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceRpm": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -464,7 +489,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"found-by",
|
||||
"foundBy",
|
||||
"locations",
|
||||
"name",
|
||||
"type",
|
||||
@@ -474,61 +499,78 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"source": {
|
||||
"properties": {
|
||||
"digest": {
|
||||
"type": "string"
|
||||
},
|
||||
"layers": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"digest": {
|
||||
"type": "string"
|
||||
},
|
||||
"media-type": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
}
|
||||
"target": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
"required": [
|
||||
"digest",
|
||||
"media-type",
|
||||
"size"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
{
|
||||
"properties": {
|
||||
"digest": {
|
||||
"type": "string"
|
||||
},
|
||||
"layers": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"digest": {
|
||||
"type": "string"
|
||||
},
|
||||
"mediaType": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"digest",
|
||||
"mediaType",
|
||||
"size"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"mediaType": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"tags": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"digest",
|
||||
"layers",
|
||||
"mediaType",
|
||||
"size",
|
||||
"tags"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"media-type": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"tags": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"digest",
|
||||
"layers",
|
||||
"media-type",
|
||||
"size",
|
||||
"tags"
|
||||
"target",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"artifacts"
|
||||
"artifacts",
|
||||
"source"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
@@ -4,39 +4,14 @@ Package apkdb provides a concrete Cataloger implementation for Alpine DB files.
|
||||
package apkdb
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
// Cataloger catalogs pkg.ApkPkg Package Types defined in Alpine DB files.
|
||||
type Cataloger struct {
|
||||
cataloger common.GenericCataloger
|
||||
}
|
||||
|
||||
// New returns a new Alpine DB cataloger object.
|
||||
func New() *Cataloger {
|
||||
// NewApkdbCataloger returns a new Alpine DB cataloger object.
|
||||
func NewApkdbCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/lib/apk/db/installed": parseApkDB,
|
||||
}
|
||||
|
||||
return &Cataloger{
|
||||
cataloger: common.NewGenericCataloger(nil, globParsers),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes this cataloger.
|
||||
func (a *Cataloger) Name() string {
|
||||
return "apkdb-cataloger"
|
||||
}
|
||||
|
||||
// SelectFiles returns a set of discovered Alpine DB files from the user content source.
|
||||
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
|
||||
return a.cataloger.SelectFiles(resolver)
|
||||
}
|
||||
|
||||
// Catalog returns the Packages indexed from all Alpine DB files discovered.
|
||||
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
return a.cataloger.Catalog(contents, a.Name())
|
||||
return common.NewGenericCataloger(nil, globParsers, "apkdb-cataloger")
|
||||
}
|
||||
|
||||
@@ -20,9 +20,14 @@ var _ common.ParserFn = parseApkDB
|
||||
// parseApkDb parses individual packages from a given Alpine DB file. For more information on specific fields
|
||||
// see https://wiki.alpinelinux.org/wiki/Apk_spec .
|
||||
func parseApkDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
// larger capacity for the scanner.
|
||||
const maxScannerCapacity = 1024 * 1024
|
||||
// a new larger buffer for the scanner
|
||||
bufScan := make([]byte, maxScannerCapacity)
|
||||
packages := make([]pkg.Package, 0)
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Buffer(bufScan, maxScannerCapacity)
|
||||
onDoubleLF := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
for i := 0; i < len(data); i++ {
|
||||
if i > 0 && data[i-1] == '\n' && data[i] == '\n' {
|
||||
@@ -93,7 +98,7 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
|
||||
fileRecord = &files[len(files)-1]
|
||||
case "a":
|
||||
ownershipFields := strings.Split(value, ":")
|
||||
if len(ownershipFields) != 3 {
|
||||
if len(ownershipFields) < 3 {
|
||||
log.Errorf("unexpected APK ownership field: %q", value)
|
||||
continue
|
||||
}
|
||||
@@ -104,6 +109,8 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
|
||||
fileRecord.OwnerUID = ownershipFields[0]
|
||||
fileRecord.OwnerGUI = ownershipFields[1]
|
||||
fileRecord.Permissions = ownershipFields[2]
|
||||
// note: there are more optional fields available that we are not capturing, e.g.:
|
||||
// "0:0:755:Q1JaDEHQHBbizhEzoWK1YxuraNU/4="
|
||||
case "Z":
|
||||
if fileRecord == nil {
|
||||
log.Errorf("checksum field with no parent record: %q", value)
|
||||
|
||||
@@ -10,6 +10,56 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestExtraFileAttributes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expected pkg.ApkMetadata
|
||||
}{
|
||||
{
|
||||
name: "test extra file attributes (checksum) are ignored",
|
||||
expected: pkg.ApkMetadata{
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{
|
||||
Path: "/usr/lib/jvm/java-1.8-openjdk/bin/policytool",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1M0C9qfC/+kdRiOodeihG2GMRtkE=",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
file, err := os.Open("test-fixtures/extra-file-attributes")
|
||||
if err != nil {
|
||||
t.Fatal("Unable to read test-fixtures/extra-file-attributes: ", err)
|
||||
}
|
||||
defer func() {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
t.Fatal("closing file failed:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
reader := bufio.NewReader(file)
|
||||
|
||||
entry, err := parseApkDBEntry(reader)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to read file contents: ", err)
|
||||
}
|
||||
|
||||
if diff := deep.Equal(entry.Files, test.expected.Files); diff != nil {
|
||||
for _, d := range diff {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSinglePackage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
P:openjdk8-jre
|
||||
V:8.212.04-r0
|
||||
A:x86_64
|
||||
S:359029
|
||||
I:970752
|
||||
T:OpenJDK 8 Java Runtime
|
||||
U:https://icedtea.classpath.org/
|
||||
L:custom
|
||||
o:openjdk8
|
||||
m:Timo Teras <timo.teras@iki.fi>
|
||||
t:1556993127
|
||||
c:46badd1aabd3375ae4b41c7194bc448f74b2ea4a
|
||||
D:java-cacerts nss so:libX11.so.6 so:libXcomposite.so.1 so:libXext.so.6 so:libXi.so.6 so:libXrender.so.1 so:libXtst.so.6 so:libasound.so.2 so:libc.musl-x86_64.so.1 so:libfreetype.so.6 so:libgcc_s.so.1 so:libgif.so.7 so:libjpeg.so.8 so:libpng16.so.16 so:libstdc++.so.6 so:openjdk8:libawt.so so:openjdk8:libjava.so so:openjdk8:libjli.so so:openjdk8:libjvm.so
|
||||
p:so:openjdk8:libawt_xawt.so=0 so:openjdk8:libfontmanager.so=0 so:openjdk8:libjawt.so=0 so:openjdk8:libjsoundalsa.so=0 so:openjdk8:libsplashscreen.so=0
|
||||
F:usr
|
||||
F:usr/lib
|
||||
F:usr/lib/jvm
|
||||
F:usr/lib/jvm/java-1.8-openjdk
|
||||
F:usr/lib/jvm/java-1.8-openjdk/bin
|
||||
R:policytool
|
||||
a:0:0:755:Q1JaDEHQHBbizhEzoWK1YxuraNU/4=
|
||||
Z:Q1M0C9qfC/+kdRiOodeihG2GMRtkE=
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
Package bundler provides a concrete Cataloger implementation for Ruby Gemfile.lock bundler files.
|
||||
*/
|
||||
package bundler
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
// Cataloger catalogs pkg.GemPkg Package Types defined in Bundler Gemfile.lock files.
|
||||
type Cataloger struct {
|
||||
cataloger common.GenericCataloger
|
||||
}
|
||||
|
||||
// New returns a new Bundler cataloger object.
|
||||
func New() *Cataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/Gemfile.lock": parseGemfileLockEntries,
|
||||
}
|
||||
|
||||
return &Cataloger{
|
||||
cataloger: common.NewGenericCataloger(nil, globParsers),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes this cataloger.
|
||||
func (a *Cataloger) Name() string {
|
||||
return "bundler-cataloger"
|
||||
}
|
||||
|
||||
// SelectFiles returns a set of discovered Gemfile.lock files from the user content source.
|
||||
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
|
||||
return a.cataloger.SelectFiles(resolver)
|
||||
}
|
||||
|
||||
// Catalog returns the Packages indexed from all Gemfile.lock files discovered.
|
||||
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
return a.cataloger.Catalog(contents, a.Name())
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package bundler
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
var expected = map[string]string{
|
||||
"actionmailer": "4.1.1",
|
||||
"actionpack": "4.1.1",
|
||||
"actionview": "4.1.1",
|
||||
"activemodel": "4.1.1",
|
||||
"activerecord": "4.1.1",
|
||||
"activesupport": "4.1.1",
|
||||
"arel": "5.0.1.20140414130214",
|
||||
"bootstrap-sass": "3.1.1.1",
|
||||
"builder": "3.2.2",
|
||||
"coffee-rails": "4.0.1",
|
||||
"coffee-script": "2.2.0",
|
||||
"coffee-script-source": "1.7.0",
|
||||
"erubis": "2.7.0",
|
||||
"execjs": "2.0.2",
|
||||
"hike": "1.2.3",
|
||||
"i18n": "0.6.9",
|
||||
"jbuilder": "2.0.7",
|
||||
"jquery-rails": "3.1.0",
|
||||
"json": "1.8.1",
|
||||
"kgio": "2.9.2",
|
||||
"libv8": "3.16.14.3",
|
||||
"mail": "2.5.4",
|
||||
"mime-types": "1.25.1",
|
||||
"minitest": "5.3.4",
|
||||
"multi_json": "1.10.1",
|
||||
"mysql2": "0.3.16",
|
||||
"polyglot": "0.3.4",
|
||||
"rack": "1.5.2",
|
||||
"rack-test": "0.6.2",
|
||||
"rails": "4.1.1",
|
||||
"railties": "4.1.1",
|
||||
"raindrops": "0.13.0",
|
||||
"rake": "10.3.2",
|
||||
"rdoc": "4.1.1",
|
||||
"ref": "1.0.5",
|
||||
"sass": "3.2.19",
|
||||
"sass-rails": "4.0.3",
|
||||
"sdoc": "0.4.0",
|
||||
"spring": "1.1.3",
|
||||
"sprockets": "2.11.0",
|
||||
"sprockets-rails": "2.1.3",
|
||||
"sqlite3": "1.3.9",
|
||||
"therubyracer": "0.12.1",
|
||||
"thor": "0.19.1",
|
||||
"thread_safe": "0.3.3",
|
||||
"tilt": "1.4.1",
|
||||
"treetop": "1.4.15",
|
||||
"turbolinks": "2.2.2",
|
||||
"tzinfo": "1.2.0",
|
||||
"uglifier": "2.5.0",
|
||||
"unicorn": "4.8.3",
|
||||
}
|
||||
|
||||
func TestParseGemfileLockEntries(t *testing.T) {
|
||||
fixture, err := os.Open("test-fixtures/Gemfile.lock")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseGemfileLockEntries(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse gemfile lock: %+v", err)
|
||||
}
|
||||
|
||||
if len(actual) != len(expected) {
|
||||
for _, a := range actual {
|
||||
t.Log(" ", a)
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expected))
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
expectedVersion, ok := expected[a.Name]
|
||||
if !ok {
|
||||
t.Errorf("unexpected package found: %s", a.Name)
|
||||
}
|
||||
|
||||
if expectedVersion != a.Version {
|
||||
t.Errorf("unexpected package version (pkg=%s): %s", a.Name, a.Version)
|
||||
}
|
||||
|
||||
if a.Language != pkg.Ruby {
|
||||
t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
|
||||
}
|
||||
|
||||
if a.Type != pkg.BundlerPkg {
|
||||
t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-12
@@ -8,13 +8,13 @@ package cataloger
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/apkdb"
|
||||
"github.com/anchore/syft/syft/cataloger/bundler"
|
||||
"github.com/anchore/syft/syft/cataloger/dpkg"
|
||||
"github.com/anchore/syft/syft/cataloger/deb"
|
||||
"github.com/anchore/syft/syft/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/cataloger/java"
|
||||
"github.com/anchore/syft/syft/cataloger/javascript"
|
||||
"github.com/anchore/syft/syft/cataloger/python"
|
||||
"github.com/anchore/syft/syft/cataloger/rpmdb"
|
||||
"github.com/anchore/syft/syft/cataloger/ruby"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
@@ -33,16 +33,30 @@ type Cataloger interface {
|
||||
// TODO: we should consider refactoring to return a set of io.Readers instead of the full contents themselves (allow for optional buffering).
|
||||
}
|
||||
|
||||
// All returns a slice of all locally defined catalogers (defined in child packages).
|
||||
func All() []Cataloger {
|
||||
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
|
||||
func ImageCatalogers() []Cataloger {
|
||||
return []Cataloger{
|
||||
dpkg.New(),
|
||||
bundler.New(),
|
||||
python.New(),
|
||||
rpmdb.New(),
|
||||
java.New(),
|
||||
apkdb.New(),
|
||||
golang.New(),
|
||||
javascript.New(),
|
||||
ruby.NewGemSpecCataloger(),
|
||||
python.NewPythonCataloger(), // TODO: split and replace me
|
||||
javascript.NewJavascriptCataloger(), // TODO: split and replace me
|
||||
deb.NewDpkgdbCataloger(),
|
||||
rpmdb.NewRpmdbCataloger(),
|
||||
java.NewJavaCataloger(),
|
||||
apkdb.NewApkdbCataloger(),
|
||||
golang.NewGoModCataloger(),
|
||||
}
|
||||
}
|
||||
|
||||
// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
|
||||
func DirectoryCatalogers() []Cataloger {
|
||||
return []Cataloger{
|
||||
ruby.NewGemFileLockCataloger(),
|
||||
python.NewPythonCataloger(), // TODO: split and replace me
|
||||
javascript.NewJavascriptCataloger(), // TODO: split and replace me
|
||||
deb.NewDpkgdbCataloger(),
|
||||
rpmdb.NewRpmdbCataloger(),
|
||||
java.NewJavaCataloger(),
|
||||
apkdb.NewApkdbCataloger(),
|
||||
golang.NewGoModCataloger(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,22 +15,29 @@ import (
|
||||
// GenericCataloger implements the Catalog interface and is responsible for dispatching the proper parser function for
|
||||
// a given path or glob pattern. This is intended to be reusable across many package cataloger types.
|
||||
type GenericCataloger struct {
|
||||
globParsers map[string]ParserFn
|
||||
pathParsers map[string]ParserFn
|
||||
selectedFiles []file.Reference
|
||||
parsers map[file.Reference]ParserFn
|
||||
globParsers map[string]ParserFn
|
||||
pathParsers map[string]ParserFn
|
||||
selectedFiles []file.Reference
|
||||
parsers map[file.Reference]ParserFn
|
||||
upstreamCataloger string
|
||||
}
|
||||
|
||||
// NewGenericCataloger if provided path-to-parser-function and glob-to-parser-function lookups creates a GenericCataloger
|
||||
func NewGenericCataloger(pathParsers map[string]ParserFn, globParsers map[string]ParserFn) GenericCataloger {
|
||||
return GenericCataloger{
|
||||
globParsers: globParsers,
|
||||
pathParsers: pathParsers,
|
||||
selectedFiles: make([]file.Reference, 0),
|
||||
parsers: make(map[file.Reference]ParserFn),
|
||||
func NewGenericCataloger(pathParsers map[string]ParserFn, globParsers map[string]ParserFn, upstreamCataloger string) *GenericCataloger {
|
||||
return &GenericCataloger{
|
||||
globParsers: globParsers,
|
||||
pathParsers: pathParsers,
|
||||
selectedFiles: make([]file.Reference, 0),
|
||||
parsers: make(map[file.Reference]ParserFn),
|
||||
upstreamCataloger: upstreamCataloger,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes the upstream cataloger that this Generic Cataloger represents.
|
||||
func (a *GenericCataloger) Name() string {
|
||||
return a.upstreamCataloger
|
||||
}
|
||||
|
||||
// register pairs a set of file references with a parser function for future cataloging (when the file contents are resolved)
|
||||
func (a *GenericCataloger) register(files []file.Reference, parser ParserFn) {
|
||||
a.selectedFiles = append(a.selectedFiles, files...)
|
||||
@@ -51,7 +58,7 @@ func (a *GenericCataloger) SelectFiles(resolver scope.FileResolver) []file.Refer
|
||||
for path, parser := range a.pathParsers {
|
||||
files, err := resolver.FilesByPath(file.Path(path))
|
||||
if err != nil {
|
||||
log.Errorf("cataloger failed to select files by path: %w", err)
|
||||
log.Errorf("cataloger failed to select files by path: %+v", err)
|
||||
}
|
||||
if files != nil {
|
||||
a.register(files, parser)
|
||||
@@ -73,7 +80,7 @@ func (a *GenericCataloger) SelectFiles(resolver scope.FileResolver) []file.Refer
|
||||
}
|
||||
|
||||
// Catalog takes a set of file contents and uses any configured parser functions to resolve and return discovered packages
|
||||
func (a *GenericCataloger) Catalog(contents map[file.Reference]string, upstreamMatcher string) ([]pkg.Package, error) {
|
||||
func (a *GenericCataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
defer a.clear()
|
||||
|
||||
packages := make([]pkg.Package, 0)
|
||||
@@ -81,19 +88,19 @@ func (a *GenericCataloger) Catalog(contents map[file.Reference]string, upstreamM
|
||||
for reference, parser := range a.parsers {
|
||||
content, ok := contents[reference]
|
||||
if !ok {
|
||||
log.Errorf("cataloger '%s' missing file content: %+v", upstreamMatcher, reference)
|
||||
log.Errorf("cataloger '%s' missing file content: %+v", a.upstreamCataloger, reference)
|
||||
continue
|
||||
}
|
||||
|
||||
entries, err := parser(string(reference.Path), strings.NewReader(content))
|
||||
if err != nil {
|
||||
// TODO: should we fail? or only log?
|
||||
log.Errorf("cataloger '%s' failed to parse entries (reference=%+v): %w", upstreamMatcher, reference, err)
|
||||
log.Errorf("cataloger '%s' failed to parse entries (reference=%+v): %+v", a.upstreamCataloger, reference, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
entry.FoundBy = upstreamMatcher
|
||||
entry.FoundBy = a.upstreamCataloger
|
||||
entry.Source = []file.Reference{reference}
|
||||
|
||||
packages = append(packages, entry)
|
||||
|
||||
@@ -60,9 +60,9 @@ func TestGenericCataloger(t *testing.T) {
|
||||
"/another-path.txt": parser,
|
||||
"/last/path.txt": parser,
|
||||
}
|
||||
|
||||
upstream := "some-other-cataloger"
|
||||
resolver := newTestResolver()
|
||||
cataloger := NewGenericCataloger(pathParsers, globParsers)
|
||||
cataloger := NewGenericCataloger(pathParsers, globParsers, upstream)
|
||||
|
||||
selected := cataloger.SelectFiles(resolver)
|
||||
|
||||
@@ -79,7 +79,6 @@ func TestGenericCataloger(t *testing.T) {
|
||||
selectionByPath[string(s.Path)] = s
|
||||
}
|
||||
|
||||
upstream := "some-other-cataloger"
|
||||
expectedPkgs := make(map[file.Reference]pkg.Package)
|
||||
for path, ref := range selectionByPath {
|
||||
expectedPkgs[ref] = pkg.Package{
|
||||
@@ -89,7 +88,7 @@ func TestGenericCataloger(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
actualPkgs, err := cataloger.Catalog(resolver.contents, upstream)
|
||||
actualPkgs, err := cataloger.Catalog(resolver.contents)
|
||||
if err != nil {
|
||||
t.Fatalf("cataloger catalog action failed: %+v", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Package dpkg provides a concrete Cataloger implementation for Debian package DB status files.
|
||||
*/
|
||||
package deb
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
)
|
||||
|
||||
// NewDpkgdbCataloger returns a new Deb package cataloger object.
|
||||
func NewDpkgdbCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/var/lib/dpkg/status": parseDpkgStatus,
|
||||
}
|
||||
|
||||
return common.NewGenericCataloger(nil, globParsers, "dpkgdb-cataloger")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package dpkg
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package dpkg
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
Package dpkg provides a concrete Cataloger implementation for Debian package DB status files.
|
||||
*/
|
||||
package dpkg
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
// Cataloger catalogs pkg.DebPkg Package Types defined in DPKG status files.
|
||||
type Cataloger struct {
|
||||
cataloger common.GenericCataloger
|
||||
}
|
||||
|
||||
// New returns a new Deb package cataloger object.
|
||||
func New() *Cataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/var/lib/dpkg/status": parseDpkgStatus,
|
||||
}
|
||||
|
||||
return &Cataloger{
|
||||
cataloger: common.NewGenericCataloger(nil, globParsers),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes this cataloger.
|
||||
func (a *Cataloger) Name() string {
|
||||
return "dpkg-cataloger"
|
||||
}
|
||||
|
||||
// SelectFiles returns a set of discovered DPKG status files from the user content source.
|
||||
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
|
||||
return a.cataloger.SelectFiles(resolver)
|
||||
}
|
||||
|
||||
// Catalog returns the Packages indexed from all DPKG status files discovered.
|
||||
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
return a.cataloger.Catalog(contents, a.Name())
|
||||
}
|
||||
@@ -4,39 +4,14 @@ Package golang provides a concrete Cataloger implementation for go.mod files.
|
||||
package golang
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
// Cataloger catalogs pkg.GoModulePkg Package Types defined in go.mod files.
|
||||
type Cataloger struct {
|
||||
cataloger common.GenericCataloger
|
||||
}
|
||||
|
||||
// New returns a new Go module cataloger object.
|
||||
func New() *Cataloger {
|
||||
// NewGoModCataloger returns a new Go module cataloger object.
|
||||
func NewGoModCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/go.mod": parseGoMod,
|
||||
}
|
||||
|
||||
return &Cataloger{
|
||||
cataloger: common.NewGenericCataloger(nil, globParsers),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes this cataloger.
|
||||
func (a *Cataloger) Name() string {
|
||||
return "go-cataloger"
|
||||
}
|
||||
|
||||
// SelectFiles returns a set of discovered go.mod files from the user content source.
|
||||
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
|
||||
return a.cataloger.SelectFiles(resolver)
|
||||
}
|
||||
|
||||
// Catalog returns the Packages indexed from all go.mod files discovered.
|
||||
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
return a.cataloger.Catalog(contents, a.Name())
|
||||
return common.NewGenericCataloger(nil, globParsers, "go-cataloger")
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ import (
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
|
||||
"github.com/rogpeppe/go-internal/modfile"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"golang.org/x/mod/modfile"
|
||||
)
|
||||
|
||||
// parseGoMod takes a go.mod and lists all packages discovered.
|
||||
|
||||
@@ -4,40 +4,15 @@ Package java provides a concrete Cataloger implementation for Java archives (jar
|
||||
package java
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
// Cataloger catalogs pkg.JavaPkg and pkg.JenkinsPluginPkg Package Types defined in java archive files.
|
||||
type Cataloger struct {
|
||||
cataloger common.GenericCataloger
|
||||
}
|
||||
|
||||
// New returns a new Java archive cataloger object.
|
||||
func New() *Cataloger {
|
||||
// NewJavaCataloger returns a new Java archive cataloger object.
|
||||
func NewJavaCataloger() *common.GenericCataloger {
|
||||
globParsers := make(map[string]common.ParserFn)
|
||||
for _, pattern := range archiveFormatGlobs {
|
||||
globParsers[pattern] = parseJavaArchive
|
||||
}
|
||||
|
||||
return &Cataloger{
|
||||
cataloger: common.NewGenericCataloger(nil, globParsers),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes this cataloger.
|
||||
func (a *Cataloger) Name() string {
|
||||
return "java-cataloger"
|
||||
}
|
||||
|
||||
// SelectFiles returns a set of discovered Java archive files from the user content source.
|
||||
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
|
||||
return a.cataloger.SelectFiles(resolver)
|
||||
}
|
||||
|
||||
// Catalog returns the Packages indexed from all Java archive files discovered.
|
||||
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
return a.cataloger.Catalog(contents, a.Name())
|
||||
return common.NewGenericCataloger(nil, globParsers, "java-cataloger")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func saveArchiveToTmp(reader io.Reader) (string, string, func(), error) {
|
||||
cleanupFn := func() {
|
||||
err = os.RemoveAll(tempDir)
|
||||
if err != nil {
|
||||
log.Errorf("unable to cleanup jar tempdir: %w", err)
|
||||
log.Errorf("unable to cleanup jar tempdir: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
<?jelly escape-by-default='true'?>
|
||||
<div>
|
||||
TODO
|
||||
</div>
|
||||
|
||||
@@ -4,40 +4,15 @@ Package javascript provides a concrete Cataloger implementation for JavaScript e
|
||||
package javascript
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
// Cataloger catalogs pkg.YarnPkg and pkg.NpmPkg Package Types defined in package-lock.json and yarn.lock files.
|
||||
type Cataloger struct {
|
||||
cataloger common.GenericCataloger
|
||||
}
|
||||
|
||||
// New returns a new JavaScript cataloger object.
|
||||
func New() *Cataloger {
|
||||
// NewJavascriptCataloger returns a new JavaScript cataloger object.
|
||||
func NewJavascriptCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/package-lock.json": parsePackageLock,
|
||||
"**/yarn.lock": parseYarnLock,
|
||||
}
|
||||
|
||||
return &Cataloger{
|
||||
cataloger: common.NewGenericCataloger(nil, globParsers),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes this cataloger.
|
||||
func (a *Cataloger) Name() string {
|
||||
return "javascript-cataloger"
|
||||
}
|
||||
|
||||
// SelectFiles returns a set of discovered Javascript ecosystem files from the user content source.
|
||||
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
|
||||
return a.cataloger.SelectFiles(resolver)
|
||||
}
|
||||
|
||||
// Catalog returns the Packages indexed from all Javascript ecosystem files discovered.
|
||||
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
return a.cataloger.Catalog(contents, a.Name())
|
||||
return common.NewGenericCataloger(nil, globParsers, "javascript-cataloger")
|
||||
}
|
||||
|
||||
@@ -4,43 +4,18 @@ Package python provides a concrete Cataloger implementation for Python ecosystem
|
||||
package python
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
// Cataloger catalogs pkg.WheelPkg, pkg.EggPkg, and pkg.PythonRequirementsPkg Package Types defined in Python ecosystem files.
|
||||
type Cataloger struct {
|
||||
cataloger common.GenericCataloger
|
||||
}
|
||||
|
||||
// New returns a new Python cataloger object.
|
||||
func New() *Cataloger {
|
||||
// NewPythonCataloger returns a new Python cataloger object.
|
||||
func NewPythonCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/*egg-info/PKG-INFO": parseEggMetadata,
|
||||
"**/*dist-info/METADATA": parseWheelMetadata,
|
||||
"**/requirements.txt": parseRequirementsTxt,
|
||||
"**/*requirements*.txt": parseRequirementsTxt,
|
||||
"**/poetry.lock": parsePoetryLock,
|
||||
"**/setup.py": parseSetup,
|
||||
}
|
||||
|
||||
return &Cataloger{
|
||||
cataloger: common.NewGenericCataloger(nil, globParsers),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes this cataloger.
|
||||
func (a *Cataloger) Name() string {
|
||||
return "python-cataloger"
|
||||
}
|
||||
|
||||
// SelectFiles returns a set of discovered Python ecosystem files from the user content source.
|
||||
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
|
||||
return a.cataloger.SelectFiles(resolver)
|
||||
}
|
||||
|
||||
// Catalog returns the Packages indexed from all Python ecosystem files discovered.
|
||||
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
return a.cataloger.Catalog(contents, a.Name())
|
||||
return common.NewGenericCataloger(nil, globParsers, "python-cataloger")
|
||||
}
|
||||
|
||||
@@ -4,39 +4,13 @@ Package rpmdb provides a concrete Cataloger implementation for RPM "Package" DB
|
||||
package rpmdb
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
// Cataloger catalogs pkg.RpmPkg Package Types defined in RPM DB files.
|
||||
type Cataloger struct {
|
||||
cataloger common.GenericCataloger
|
||||
}
|
||||
|
||||
// New returns a new RPM DB cataloger object.
|
||||
func New() *Cataloger {
|
||||
// NewRpmdbCataloger returns a new RPM DB cataloger object.
|
||||
func NewRpmdbCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/var/lib/rpm/Packages": parseRpmDB,
|
||||
}
|
||||
|
||||
return &Cataloger{
|
||||
cataloger: common.NewGenericCataloger(nil, globParsers),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes this cataloger.
|
||||
func (a *Cataloger) Name() string {
|
||||
return "rpmdb-cataloger"
|
||||
}
|
||||
|
||||
// SelectFiles returns a set of discovered RPM DB files from the user content source.
|
||||
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
|
||||
return a.cataloger.SelectFiles(resolver)
|
||||
}
|
||||
|
||||
// Catalog returns the Packages indexed from all RPM DB files discovered.
|
||||
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
|
||||
return a.cataloger.Catalog(contents, a.Name())
|
||||
return common.NewGenericCataloger(nil, globParsers, "rpmdb-cataloger")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Package bundler provides a concrete Cataloger implementation for Ruby Gemfile.lock bundler files.
|
||||
*/
|
||||
package ruby
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
)
|
||||
|
||||
// NewGemFileLockCataloger returns a new Bundler cataloger object tailored for parsing index-oriented files (e.g. Gemfile.lock).
|
||||
func NewGemFileLockCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/Gemfile.lock": parseGemFileLockEntries,
|
||||
}
|
||||
|
||||
return common.NewGenericCataloger(nil, globParsers, "ruby-gemfile-cataloger")
|
||||
}
|
||||
|
||||
// NewGemSpecCataloger returns a new Bundler cataloger object tailored for detecting installations of gems (e.g. Gemspec).
|
||||
func NewGemSpecCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/specifications/*.gemspec": parseGemSpecEntries,
|
||||
}
|
||||
|
||||
return common.NewGenericCataloger(nil, globParsers, "ruby-gemspec-cataloger")
|
||||
}
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package bundler
|
||||
package ruby
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
)
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseGemfileLockEntries
|
||||
var _ common.ParserFn = parseGemFileLockEntries
|
||||
|
||||
var sectionsOfInterest = internal.NewStringSetFromSlice([]string{"GEM"})
|
||||
|
||||
// parseGemfileLockEntries is a parser function for Gemfile.lock contents, returning all Gems discovered.
|
||||
func parseGemfileLockEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
// parseGemFileLockEntries is a parser function for Gemfile.lock contents, returning all Gems discovered.
|
||||
func parseGemFileLockEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
pkgs := make([]pkg.Package, 0)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
@@ -44,7 +44,7 @@ func parseGemfileLockEntries(_ string, reader io.Reader) ([]pkg.Package, error)
|
||||
Name: candidate[0],
|
||||
Version: strings.Trim(candidate[1], "()"),
|
||||
Language: pkg.Ruby,
|
||||
Type: pkg.BundlerPkg,
|
||||
Type: pkg.GemPkg,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package ruby
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestParseGemfileLockEntries(t *testing.T) {
|
||||
|
||||
var expectedGems = map[string]string{
|
||||
"actionmailer": "4.1.1",
|
||||
"actionpack": "4.1.1",
|
||||
"actionview": "4.1.1",
|
||||
"activemodel": "4.1.1",
|
||||
"activerecord": "4.1.1",
|
||||
"activesupport": "4.1.1",
|
||||
"arel": "5.0.1.20140414130214",
|
||||
"bootstrap-sass": "3.1.1.1",
|
||||
"builder": "3.2.2",
|
||||
"coffee-rails": "4.0.1",
|
||||
"coffee-script": "2.2.0",
|
||||
"coffee-script-source": "1.7.0",
|
||||
"erubis": "2.7.0",
|
||||
"execjs": "2.0.2",
|
||||
"hike": "1.2.3",
|
||||
"i18n": "0.6.9",
|
||||
"jbuilder": "2.0.7",
|
||||
"jquery-rails": "3.1.0",
|
||||
"json": "1.8.1",
|
||||
"kgio": "2.9.2",
|
||||
"libv8": "3.16.14.3",
|
||||
"mail": "2.5.4",
|
||||
"mime-types": "1.25.1",
|
||||
"minitest": "5.3.4",
|
||||
"multi_json": "1.10.1",
|
||||
"mysql2": "0.3.16",
|
||||
"polyglot": "0.3.4",
|
||||
"rack": "1.5.2",
|
||||
"rack-test": "0.6.2",
|
||||
"rails": "4.1.1",
|
||||
"railties": "4.1.1",
|
||||
"raindrops": "0.13.0",
|
||||
"rake": "10.3.2",
|
||||
"rdoc": "4.1.1",
|
||||
"ref": "1.0.5",
|
||||
"sass": "3.2.19",
|
||||
"sass-rails": "4.0.3",
|
||||
"sdoc": "0.4.0",
|
||||
"spring": "1.1.3",
|
||||
"sprockets": "2.11.0",
|
||||
"sprockets-rails": "2.1.3",
|
||||
"sqlite3": "1.3.9",
|
||||
"therubyracer": "0.12.1",
|
||||
"thor": "0.19.1",
|
||||
"thread_safe": "0.3.3",
|
||||
"tilt": "1.4.1",
|
||||
"treetop": "1.4.15",
|
||||
"turbolinks": "2.2.2",
|
||||
"tzinfo": "1.2.0",
|
||||
"uglifier": "2.5.0",
|
||||
"unicorn": "4.8.3",
|
||||
}
|
||||
|
||||
fixture, err := os.Open("test-fixtures/Gemfile.lock")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseGemFileLockEntries(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse gemfile lock: %+v", err)
|
||||
}
|
||||
|
||||
if len(actual) != len(expectedGems) {
|
||||
for _, a := range actual {
|
||||
t.Log(" ", a)
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expectedGems))
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
expectedVersion, ok := expectedGems[a.Name]
|
||||
if !ok {
|
||||
t.Errorf("unexpected package found: %s", a.Name)
|
||||
}
|
||||
|
||||
if expectedVersion != a.Version {
|
||||
t.Errorf("unexpected package version (pkg=%s): %s", a.Name, a.Version)
|
||||
}
|
||||
|
||||
if a.Language != pkg.Ruby {
|
||||
t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
|
||||
}
|
||||
|
||||
if a.Type != pkg.GemPkg {
|
||||
t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package ruby
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseGemFileLockEntries
|
||||
|
||||
type postProcessor func(string) []string
|
||||
|
||||
// match example: Al\u003Ex ---> 003E
|
||||
var unicodePattern = regexp.MustCompile(`\\u(?P<unicode>[0-9A-F]{4})`)
|
||||
|
||||
var patterns = map[string]*regexp.Regexp{
|
||||
// match example: name = "railties".freeze ---> railties
|
||||
"name": regexp.MustCompile(`.*\.name\s*=\s*["']{1}(?P<name>.*)["']{1} *`),
|
||||
|
||||
// match example: version = "1.0.4".freeze ---> 1.0.4
|
||||
"version": regexp.MustCompile(`.*\.version\s*=\s*["']{1}(?P<version>.*)["']{1} *`),
|
||||
|
||||
// match example:
|
||||
// homepage = "https://github.com/anchore/syft".freeze ---> https://github.com/anchore/syft
|
||||
"homepage": regexp.MustCompile(`.*\.homepage\s*=\s*["']{1}(?P<homepage>.*)["']{1} *`),
|
||||
|
||||
// match example: files = ["exe/bundle".freeze, "exe/bundler".freeze] ---> "exe/bundle".freeze, "exe/bundler".freeze
|
||||
"files": regexp.MustCompile(`.*\.files\s*=\s*\[(?P<files>.*)\] *`),
|
||||
|
||||
// match example: authors = ["Andr\u00E9 Arko".freeze, "Samuel Giddins".freeze, "Colby Swandale".freeze,
|
||||
// "Hiroshi Shibata".freeze, "David Rodr\u00EDguez".freeze, "Grey Baker".freeze...]
|
||||
"authors": regexp.MustCompile(`.*\.authors\s*=\s*\[(?P<authors>.*)\] *`),
|
||||
|
||||
// match example: licenses = ["MIT".freeze] ----> "MIT".freeze
|
||||
"licenses": regexp.MustCompile(`.*\.licenses\s*=\s*\[(?P<licenses>.*)\] *`),
|
||||
}
|
||||
|
||||
var postProcessors = map[string]postProcessor{
|
||||
"files": processList,
|
||||
"authors": processList,
|
||||
"licenses": processList,
|
||||
}
|
||||
|
||||
func processList(s string) []string {
|
||||
// nolint:prealloc
|
||||
var results []string
|
||||
for _, item := range strings.Split(s, ",") {
|
||||
results = append(results, strings.Trim(item, "\" "))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func parseGemSpecEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
var pkgs []pkg.Package
|
||||
var fields = make(map[string]interface{})
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
sanitizedLine := strings.TrimSpace(line)
|
||||
sanitizedLine = strings.ReplaceAll(sanitizedLine, ".freeze", "")
|
||||
sanitizedLine = renderUtf8(sanitizedLine)
|
||||
|
||||
if sanitizedLine == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
for field, pattern := range patterns {
|
||||
matchMap := matchCaptureGroups(pattern, sanitizedLine)
|
||||
if value := matchMap[field]; value != "" {
|
||||
if postProcessor := postProcessors[field]; postProcessor != nil {
|
||||
fields[field] = postProcessor(value)
|
||||
} else {
|
||||
fields[field] = value
|
||||
}
|
||||
// TODO: know that a line could actually match on multiple patterns, this is unlikely though
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fields["name"] != "" && fields["version"] != "" {
|
||||
var metadata pkg.GemMetadata
|
||||
if err := mapstructure.Decode(fields, &metadata); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode gem metadata: %w", err)
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, pkg.Package{
|
||||
Name: metadata.Name,
|
||||
Version: metadata.Version,
|
||||
Licenses: metadata.Licenses,
|
||||
Language: pkg.Ruby,
|
||||
Type: pkg.GemPkg,
|
||||
Metadata: metadata,
|
||||
})
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
// renderUtf8 takes any string escaped string sub-sections from the ruby string and replaces those sections with the UTF8 runes.
|
||||
func renderUtf8(s string) string {
|
||||
fullReplacement := unicodePattern.ReplaceAllStringFunc(s, func(unicodeSection string) string {
|
||||
var replacement string
|
||||
// note: the json parser already has support for interpreting hex-representations of unicode escaped strings as unicode runes.
|
||||
// we can do this ourselves with strconv.Atoi, or leverage the existing json package.
|
||||
if err := json.Unmarshal([]byte(`"`+unicodeSection+`"`), &replacement); err != nil {
|
||||
return unicodeSection
|
||||
}
|
||||
return replacement
|
||||
})
|
||||
return fullReplacement
|
||||
}
|
||||
|
||||
// matchCaptureGroups takes a regular expression and string and returns all of the named capture group results in a map.
|
||||
func matchCaptureGroups(regEx *regexp.Regexp, str string) map[string]string {
|
||||
match := regEx.FindStringSubmatch(str)
|
||||
results := make(map[string]string)
|
||||
for i, name := range regEx.SubexpNames() {
|
||||
if i > 0 && i <= len(match) {
|
||||
results[name] = match[i]
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ruby
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
func TestParseGemspec(t *testing.T) {
|
||||
var expectedPkg = pkg.Package{
|
||||
Name: "bundler",
|
||||
Version: "2.1.4",
|
||||
Type: pkg.GemPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Language: pkg.Ruby,
|
||||
Metadata: pkg.GemMetadata{
|
||||
Name: "bundler",
|
||||
Version: "2.1.4",
|
||||
Files: []string{"exe/bundle", "exe/bundler"},
|
||||
Authors: []string{"André Arko", "Samuel Giddins", "Colby Swandale", "Hiroshi Shibata", "David Rodríguez", "Grey Baker", "Stephanie Morillo", "Chris Morris", "James Wen", "Tim Moore", "André Medeiros", "Jessica Lynn Suttles", "Terence Lee", "Carl Lerche", "Yehuda Katz"},
|
||||
Licenses: []string{"MIT"},
|
||||
Homepage: "https://bundler.io",
|
||||
},
|
||||
}
|
||||
|
||||
fixture, err := os.Open("test-fixtures/bundler.gemspec")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseGemSpecEntries(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse gemspec: %+v", err)
|
||||
}
|
||||
|
||||
if len(actual) != 1 {
|
||||
for _, a := range actual {
|
||||
t.Log(" ", a)
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=1", len(actual))
|
||||
}
|
||||
|
||||
for _, d := range deep.Equal(actual[0], expectedPkg) {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
# -*- encoding: utf-8 -*-
|
||||
# stub: bundler 2.1.4 ruby lib
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "bundler".freeze
|
||||
s.version = "2.1.4"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 2.5.2".freeze) if s.respond_to? :required_rubygems_version=
|
||||
s.metadata = { "bug_tracker_uri" => "https://github.com/bundler/bundler/issues", "changelog_uri" => "https://github.com/bundler/bundler/blob/master/CHANGELOG.md", "homepage_uri" => "https://bundler.io/", "source_code_uri" => "https://github.com/bundler/bundler/" } if s.respond_to? :metadata=
|
||||
s.require_paths = ["lib".freeze]
|
||||
s.authors = ["Andr\u00E9 Arko".freeze, "Samuel Giddins".freeze, "Colby Swandale".freeze, "Hiroshi Shibata".freeze, "David Rodr\u00EDguez".freeze, "Grey Baker".freeze, "Stephanie Morillo".freeze, "Chris Morris".freeze, "James Wen".freeze, "Tim Moore".freeze, "Andr\u00E9 Medeiros".freeze, "Jessica Lynn Suttles".freeze, "Terence Lee".freeze, "Carl Lerche".freeze, "Yehuda Katz".freeze]
|
||||
s.bindir = "exe".freeze
|
||||
s.date = "2020-01-05"
|
||||
s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably".freeze
|
||||
s.email = ["team@bundler.io".freeze]
|
||||
s.executables = ["bundle".freeze, "bundler".freeze]
|
||||
s.files = ["exe/bundle".freeze, "exe/bundler".freeze]
|
||||
s.homepage = "https://bundler.io".freeze
|
||||
s.licenses = ["MIT".freeze]
|
||||
s.required_ruby_version = Gem::Requirement.new(">= 2.3.0".freeze)
|
||||
s.rubygems_version = "3.1.2".freeze
|
||||
s.summary = "The best way to manage your application's dependencies".freeze
|
||||
|
||||
s.installed_by_version = "3.1.2" if s.respond_to? :installed_by_version
|
||||
end
|
||||
+11
-1
@@ -20,6 +20,9 @@ func NewUnknownDistro() Distro {
|
||||
}
|
||||
|
||||
func NewDistro(t Type, ver string) (Distro, error) {
|
||||
if ver == "" {
|
||||
return Distro{Type: t}, nil
|
||||
}
|
||||
verObj, err := hashiVer.NewVersion(ver)
|
||||
if err != nil {
|
||||
return Distro{}, fmt.Errorf("could not create distro version: %w", err)
|
||||
@@ -32,6 +35,9 @@ func NewDistro(t Type, ver string) (Distro, error) {
|
||||
}
|
||||
|
||||
func (d Distro) MajorVersion() string {
|
||||
if d.Version == nil {
|
||||
return fmt.Sprint("(version unknown)")
|
||||
}
|
||||
return fmt.Sprintf("%d", d.Version.Segments()[0])
|
||||
}
|
||||
|
||||
@@ -40,7 +46,11 @@ func (d Distro) FullVersion() string {
|
||||
}
|
||||
|
||||
func (d Distro) String() string {
|
||||
return fmt.Sprintf("%s %s", d.Type, d.RawVersion)
|
||||
versionStr := "(version unknown)"
|
||||
if d.RawVersion != "" {
|
||||
versionStr = d.RawVersion
|
||||
}
|
||||
return fmt.Sprintf("%s %s", d.Type, versionStr)
|
||||
}
|
||||
|
||||
// Name provides a string repr of the distro
|
||||
|
||||
@@ -48,6 +48,7 @@ identifyLoop:
|
||||
}
|
||||
|
||||
if len(refs) == 0 {
|
||||
log.Debugf("No Refs found from path: %s", entry.path)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ func assemble(name, version string) *Distro {
|
||||
distroType, ok := IDMapping[name]
|
||||
|
||||
// Both distro and version must be present
|
||||
if len(name) == 0 || len(version) == 0 {
|
||||
if len(name) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,15 @@ func TestIdentifyDistro(t *testing.T) {
|
||||
fixture: "test-fixtures/os/unmatchable",
|
||||
Type: UnknownDistroType,
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/opensuse-leap",
|
||||
Type: OpenSuseLeap,
|
||||
Version: "15.2.0",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/os/arch",
|
||||
Type: ArchLinux,
|
||||
},
|
||||
}
|
||||
|
||||
observedDistros := internal.NewStringSet()
|
||||
@@ -94,6 +103,11 @@ func TestIdentifyDistro(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if d.Version == nil {
|
||||
t.Log("Distro doesn't have a Version")
|
||||
return
|
||||
}
|
||||
|
||||
if d.Version.String() != test.Version {
|
||||
t.Errorf("expected distro version doesn't match: %v != %v", d.Version.String(), test.Version)
|
||||
}
|
||||
@@ -175,10 +189,6 @@ func TestParseOsReleaseFailures(t *testing.T) {
|
||||
fixture string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/bad-version",
|
||||
name: "No version",
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/bad-id",
|
||||
name: "No name ID",
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
NAME="Red Hat Enterprise Linux"
|
||||
VERSION="8.1 (Ootpa)"
|
||||
ID="rhel"
|
||||
ID_LIKE="fedora"
|
||||
PLATFORM_ID="platform:el8"
|
||||
PRETTY_NAME="Red Hat Enterprise Linux 8.1 (Ootpa)"
|
||||
ANSI_COLOR="0;31"
|
||||
CPE_NAME="cpe:/o:redhat:enterprise_linux:8.1:GA"
|
||||
HOME_URL="https://www.redhat.com/"
|
||||
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||
|
||||
REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=8.1
|
||||
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION="8.1"
|
||||
@@ -0,0 +1,10 @@
|
||||
NAME="Arch Linux"
|
||||
PRETTY_NAME="Arch Linux"
|
||||
ID=arch
|
||||
BUILD_ID=rolling
|
||||
ANSI_COLOR="38;2;23;147;209"
|
||||
HOME_URL="https://www.archlinux.org/"
|
||||
DOCUMENTATION_URL="https://wiki.archlinux.org/"
|
||||
SUPPORT_URL="https://bbs.archlinux.org/"
|
||||
BUG_REPORT_URL="https://bugs.archlinux.org/"
|
||||
LOGO=archlinux
|
||||
@@ -0,0 +1,10 @@
|
||||
NAME="openSUSE Leap"
|
||||
VERSION="15.2"
|
||||
ID="opensuse-leap"
|
||||
ID_LIKE="suse opensuse"
|
||||
VERSION_ID="15.2"
|
||||
PRETTY_NAME="openSUSE Leap 15.2"
|
||||
ANSI_COLOR="0;32"
|
||||
CPE_NAME="cpe:/o:opensuse:leap:15.2"
|
||||
BUG_REPORT_URL="https://bugs.opensuse.org"
|
||||
HOME_URL="https://www.opensuse.org/"
|
||||
+17
-13
@@ -11,7 +11,8 @@ const (
|
||||
Busybox
|
||||
AmazonLinux
|
||||
OracleLinux
|
||||
//ArchLinux
|
||||
ArchLinux
|
||||
OpenSuseLeap
|
||||
)
|
||||
|
||||
type Type int
|
||||
@@ -27,7 +28,8 @@ var distroStr = []string{
|
||||
"busybox",
|
||||
"amazn",
|
||||
"oraclelinux",
|
||||
//"archlinux",
|
||||
"archlinux",
|
||||
"opensuse-leap",
|
||||
}
|
||||
|
||||
var All = []Type{
|
||||
@@ -40,7 +42,8 @@ var All = []Type{
|
||||
Busybox,
|
||||
AmazonLinux,
|
||||
OracleLinux,
|
||||
//ArchLinux,
|
||||
ArchLinux,
|
||||
OpenSuseLeap,
|
||||
}
|
||||
|
||||
func (t Type) String() string {
|
||||
@@ -53,14 +56,15 @@ func (t Type) String() string {
|
||||
|
||||
// IDMapping connects a distro ID like "ubuntu" to a Distro type
|
||||
var IDMapping = map[string]Type{
|
||||
"debian": Debian,
|
||||
"ubuntu": Ubuntu,
|
||||
"rhel": RedHat,
|
||||
"centos": CentOS,
|
||||
"fedora": Fedora,
|
||||
"alpine": Alpine,
|
||||
"busybox": Busybox,
|
||||
"amzn": AmazonLinux,
|
||||
"ol": OracleLinux,
|
||||
//"arch": ArchLinux,
|
||||
"debian": Debian,
|
||||
"ubuntu": Ubuntu,
|
||||
"rhel": RedHat,
|
||||
"centos": CentOS,
|
||||
"fedora": Fedora,
|
||||
"alpine": Alpine,
|
||||
"busybox": Busybox,
|
||||
"amzn": AmazonLinux,
|
||||
"ol": OracleLinux,
|
||||
"arch": ArchLinux,
|
||||
"opensuse-leap": OpenSuseLeap,
|
||||
}
|
||||
|
||||
+28
-1
@@ -1,9 +1,24 @@
|
||||
/*
|
||||
A "one-stop-shop" for helper utilities for all major functionality provided by child packages of the syft library.
|
||||
|
||||
Here is what the main execution path for syft does:
|
||||
|
||||
1. Parse a user image string to get a stereoscope image.Source object
|
||||
2. Invoke all catalogers to catalog the image, adding discovered packages to a single catalog object
|
||||
3. Invoke a single presenter to show the contents of the catalog
|
||||
|
||||
A Scope object encapsulates the image object to be cataloged and the user options (catalog all layers vs. squashed layer),
|
||||
providing a way to inspect paths and file content within the image. The Scope object, not the image object, is used
|
||||
throughout the main execution path. This abstraction allows for decoupling of what is cataloged (a docker image, an OCI
|
||||
image, a filesystem, etc) and how it is cataloged (the individual catalogers).
|
||||
|
||||
Similar to the cataloging process, Linux distribution identification is also performed based on what is discovered within the image.
|
||||
*/
|
||||
package syft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cataloger"
|
||||
@@ -49,7 +64,19 @@ func IdentifyDistro(s scope.Scope) distro.Distro {
|
||||
// Catalog the given scope, which may represent a container image or filesystem. Returns the discovered set of packages.
|
||||
func CatalogFromScope(s scope.Scope) (*pkg.Catalog, error) {
|
||||
log.Info("building the catalog")
|
||||
return cataloger.Catalog(s.Resolver, cataloger.All()...)
|
||||
|
||||
// conditionally have two sets of catalogers
|
||||
var catalogers []cataloger.Cataloger
|
||||
switch s.Scheme {
|
||||
case scope.ImageScheme:
|
||||
catalogers = cataloger.ImageCatalogers()
|
||||
case scope.DirectoryScheme:
|
||||
catalogers = cataloger.DirectoryCatalogers()
|
||||
default:
|
||||
return nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", s.Scheme)
|
||||
}
|
||||
|
||||
return cataloger.Catalog(s.Resolver, catalogers...)
|
||||
}
|
||||
|
||||
// SetLogger sets the logger object used for all syft logging calls.
|
||||
|
||||
@@ -4,10 +4,14 @@ import (
|
||||
"github.com/package-url/packageurl-go"
|
||||
)
|
||||
|
||||
// ApkMetadata represents all captured data for a Alpine DB package entry. See https://wiki.alpinelinux.org/wiki/Apk_spec for more information.
|
||||
// ApkMetadata represents all captured data for a Alpine DB package entry.
|
||||
// See the following sources for more information:
|
||||
// - https://wiki.alpinelinux.org/wiki/Apk_spec
|
||||
// - https://git.alpinelinux.org/apk-tools/tree/src/package.c
|
||||
// - https://git.alpinelinux.org/apk-tools/tree/src/database.c
|
||||
type ApkMetadata struct {
|
||||
Package string `mapstructure:"P" json:"package"`
|
||||
OriginPackage string `mapstructure:"o" json:"origin-package"`
|
||||
OriginPackage string `mapstructure:"o" json:"originPackage"`
|
||||
Maintainer string `mapstructure:"m" json:"maintainer"`
|
||||
Version string `mapstructure:"V" json:"version"`
|
||||
License string `mapstructure:"L" json:"license"`
|
||||
@@ -15,18 +19,18 @@ type ApkMetadata struct {
|
||||
URL string `mapstructure:"U" json:"url"`
|
||||
Description string `mapstructure:"T" json:"description"`
|
||||
Size int `mapstructure:"S" json:"size"`
|
||||
InstalledSize int `mapstructure:"I" json:"installed-size"`
|
||||
PullDependencies string `mapstructure:"D" json:"pull-dependencies"`
|
||||
PullChecksum string `mapstructure:"C" json:"pull-checksum"`
|
||||
GitCommitOfAport string `mapstructure:"c" json:"git-commit-of-apk-port"`
|
||||
InstalledSize int `mapstructure:"I" json:"installedSize"`
|
||||
PullDependencies string `mapstructure:"D" json:"pullDependencies"`
|
||||
PullChecksum string `mapstructure:"C" json:"pullChecksum"`
|
||||
GitCommitOfAport string `mapstructure:"c" json:"gitCommitOfApkPort"`
|
||||
Files []ApkFileRecord `json:"files"`
|
||||
}
|
||||
|
||||
// ApkFileRecord represents a single file listing and metadata from a APK DB entry (which may have many of these file records).
|
||||
type ApkFileRecord struct {
|
||||
Path string `json:"path"`
|
||||
OwnerUID string `json:"owner-uid"`
|
||||
OwnerGUI string `json:"owner-gid"`
|
||||
OwnerUID string `json:"ownerUid"`
|
||||
OwnerGUI string `json:"ownerGid"`
|
||||
Permissions string `json:"permissions"`
|
||||
Checksum string `json:"checksum"`
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// TODO: add reader methods (by type, id, fuzzy search, etc)
|
||||
|
||||
var nextPackageID int64
|
||||
|
||||
// Catalog represents a collection of Packages.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package pkg
|
||||
|
||||
type GemMetadata struct {
|
||||
Name string `mapstructure:"name" json:"name"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
Files []string `mapstructure:"files" json:"files,omitempty"`
|
||||
Authors []string `mapstructure:"authors" json:"authors,omitempty"`
|
||||
Licenses []string `mapstructure:"licenses" json:"licenses,omitempty"`
|
||||
Homepage string `mapstructure:"homepage" json:"homepage,omitempty"`
|
||||
}
|
||||
+13
-13
@@ -5,31 +5,31 @@ import "github.com/package-url/packageurl-go"
|
||||
// JavaMetadata encapsulates all Java ecosystem metadata for a package as well as an (optional) parent relationship.
|
||||
type JavaMetadata struct {
|
||||
Manifest *JavaManifest `mapstructure:"Manifest" json:"manifest"`
|
||||
PomProperties *PomProperties `mapstructure:"PomProperties" json:"pom-properties"`
|
||||
Parent *Package `json:"parent-package"` // TODO: should this be included in the json output?
|
||||
PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties"`
|
||||
Parent *Package `json:"parentPackage"` // TODO: should this be included in the json output?
|
||||
}
|
||||
|
||||
// PomProperties represents the fields of interest extracted from a Java archive's pom.xml file.
|
||||
type PomProperties struct {
|
||||
Path string
|
||||
Name string `mapstructure:"name" json:"name"`
|
||||
GroupID string `mapstructure:"groupId" json:"group-id"`
|
||||
ArtifactID string `mapstructure:"artifactId" json:"artifact-id"`
|
||||
GroupID string `mapstructure:"groupId" json:"groupId"`
|
||||
ArtifactID string `mapstructure:"artifactId" json:"artifactId"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
Extra map[string]string `mapstructure:",remain" json:"extra-fields"`
|
||||
Extra map[string]string `mapstructure:",remain" json:"extraFields"`
|
||||
}
|
||||
|
||||
// JavaManifest represents the fields of interest extracted from a Java archive's META-INF/MANIFEST.MF file.
|
||||
type JavaManifest struct {
|
||||
Name string `mapstructure:"Name" json:"name"`
|
||||
ManifestVersion string `mapstructure:"Manifest-Version" json:"manifest-version"`
|
||||
SpecTitle string `mapstructure:"Specification-Title" json:"specification-title"`
|
||||
SpecVersion string `mapstructure:"Specification-Version" json:"specification-version"`
|
||||
SpecVendor string `mapstructure:"Specification-Vendor" json:"specification-vendor"`
|
||||
ImplTitle string `mapstructure:"Implementation-Title" json:"implementation-title"`
|
||||
ImplVersion string `mapstructure:"Implementation-Version" json:"implementation-version"`
|
||||
ImplVendor string `mapstructure:"Implementation-Vendor" json:"implementation-vendor"`
|
||||
Extra map[string]string `mapstructure:",remain" json:"extra-fields"`
|
||||
ManifestVersion string `mapstructure:"Manifest-Version" json:"manifestVersion"`
|
||||
SpecTitle string `mapstructure:"Specification-Title" json:"specificationTitle"`
|
||||
SpecVersion string `mapstructure:"Specification-Version" json:"specificationVersion"`
|
||||
SpecVendor string `mapstructure:"Specification-Vendor" json:"specificationVendor"`
|
||||
ImplTitle string `mapstructure:"Implementation-Title" json:"implementationTitle"`
|
||||
ImplVersion string `mapstructure:"Implementation-Version" json:"implementationVersion"`
|
||||
ImplVendor string `mapstructure:"Implementation-Vendor" json:"implementationVendor"`
|
||||
Extra map[string]string `mapstructure:",remain" json:"extraFields"`
|
||||
}
|
||||
|
||||
func (m JavaMetadata) PackageURL() string {
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ type Package struct {
|
||||
id ID // uniquely identifies a package, set by the cataloger
|
||||
Name string `json:"manifest"` // the package name
|
||||
Version string `json:"version"` // the version of the package
|
||||
FoundBy string `json:"found-by"` // the specific cataloger that discovered this package
|
||||
FoundBy string `json:"foundBy"` // the specific cataloger that discovered this package
|
||||
Source []file.Reference `json:"sources"` // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package)
|
||||
// TODO: should we move licenses into metadata?
|
||||
Licenses []string `json:"licenses"` // licenses discovered with the package metadata
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPackage_pURL(t *testing.T) {
|
||||
@@ -56,7 +57,7 @@ func TestPackage_pURL(t *testing.T) {
|
||||
pkg: Package{
|
||||
Name: "name",
|
||||
Version: "v0.1.0",
|
||||
Type: BundlerPkg,
|
||||
Type: GemPkg,
|
||||
},
|
||||
expected: "pkg:gem/name@v0.1.0",
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ type RpmMetadata struct {
|
||||
Epoch int `json:"epoch"`
|
||||
Arch string `json:"architecture"`
|
||||
Release string `json:"release"`
|
||||
SourceRpm string `json:"source-rpm"`
|
||||
SourceRpm string `json:"sourceRpm"`
|
||||
Size int `json:"size"`
|
||||
License string `json:"license"`
|
||||
Vendor string `json:"vendor"`
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ type Type string
|
||||
const (
|
||||
UnknownPkg Type = "UnknownPackage"
|
||||
ApkPkg Type = "apk"
|
||||
BundlerPkg Type = "bundle"
|
||||
GemPkg Type = "gem"
|
||||
DebPkg Type = "deb"
|
||||
EggPkg Type = "egg"
|
||||
// PacmanPkg Type = "pacman"
|
||||
@@ -26,7 +26,7 @@ const (
|
||||
|
||||
var AllPkgs = []Type{
|
||||
ApkPkg,
|
||||
BundlerPkg,
|
||||
GemPkg,
|
||||
DebPkg,
|
||||
EggPkg,
|
||||
// PacmanPkg,
|
||||
@@ -45,7 +45,7 @@ func (t Type) PackageURLType() string {
|
||||
switch t {
|
||||
case ApkPkg:
|
||||
return "alpine"
|
||||
case BundlerPkg:
|
||||
case GemPkg:
|
||||
return packageurl.TypeGem
|
||||
case DebPkg:
|
||||
return "deb"
|
||||
|
||||
@@ -34,9 +34,7 @@ func NewPresenter(catalog *pkg.Catalog, s scope.Scope, d distro.Distro) *Present
|
||||
func (pres *Presenter) Present(output io.Writer) error {
|
||||
bom := NewDocumentFromCatalog(pres.catalog, pres.distro)
|
||||
|
||||
srcObj := pres.scope.Source()
|
||||
|
||||
switch src := srcObj.(type) {
|
||||
switch src := pres.scope.Source.(type) {
|
||||
case scope.DirSource:
|
||||
bom.BomDescriptor.Component = &BdComponent{
|
||||
Component: Component{
|
||||
@@ -54,7 +52,6 @@ func (pres *Presenter) Present(output io.Writer) error {
|
||||
} else {
|
||||
imageID = src.Img.Metadata.Digest
|
||||
}
|
||||
src.Img.Metadata.Tags[0].TagStr()
|
||||
bom.BomDescriptor.Component = &BdComponent{
|
||||
Component: Component{
|
||||
Type: "container",
|
||||
|
||||
@@ -3,6 +3,7 @@ package cyclonedx
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"regexp"
|
||||
"testing"
|
||||
@@ -96,7 +97,7 @@ func TestCycloneDxImgsPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
defer cleanup()
|
||||
|
||||
// populate catalog with test data
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0" version="1" serialNumber="urn:uuid:b3b1786a-2de3-4501-b902-58b701b8ad0e">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0" version="1" serialNumber="urn:uuid:2bbada20-3e87-44ea-9a56-1aa0e4dd01a0">
|
||||
<components>
|
||||
<component type="library">
|
||||
<name>package1</name>
|
||||
@@ -21,7 +21,7 @@
|
||||
</component>
|
||||
</components>
|
||||
<bd:metadata>
|
||||
<bd:timestamp>2020-08-30T21:50:50-04:00</bd:timestamp>
|
||||
<bd:timestamp>2020-09-23T18:26:58-04:00</bd:timestamp>
|
||||
<bd:tool>
|
||||
<bd:vendor>anchore</bd:vendor>
|
||||
<bd:name>syft</bd:name>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0" version="1" serialNumber="urn:uuid:ab970429-e6a2-44a1-810c-f9ed2b7c3147">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0" version="1" serialNumber="urn:uuid:94dae829-4d5d-482f-afab-27f43f919e2c">
|
||||
<components>
|
||||
<component type="library">
|
||||
<name>package1</name>
|
||||
@@ -21,14 +21,14 @@
|
||||
</component>
|
||||
</components>
|
||||
<bd:metadata>
|
||||
<bd:timestamp>2020-08-30T21:50:50-04:00</bd:timestamp>
|
||||
<bd:timestamp>2020-09-23T18:26:58-04:00</bd:timestamp>
|
||||
<bd:tool>
|
||||
<bd:vendor>anchore</bd:vendor>
|
||||
<bd:name>syft</bd:name>
|
||||
<bd:version>[not provided]</bd:version>
|
||||
</bd:tool>
|
||||
<bd:component type="container">
|
||||
<name>index.docker.io/library/anchore-fixture-image-simple</name>
|
||||
<name>index.docker.io/library/stereoscope-fixture-image-simple</name>
|
||||
<version>04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7</version>
|
||||
</bd:component>
|
||||
</bd:metadata>
|
||||
|
||||
@@ -9,7 +9,7 @@ type Artifact struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type string `json:"type"`
|
||||
FoundBy []string `json:"found-by"`
|
||||
FoundBy []string `json:"foundBy"`
|
||||
Locations Locations `json:"locations,omitempty"`
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
type Document struct {
|
||||
Artifacts []Artifact `json:"artifacts"`
|
||||
Image *Image `json:"image,omitempty"`
|
||||
Directory *string `json:"directory,omitempty"`
|
||||
Source Source `json:"source"`
|
||||
}
|
||||
|
||||
func NewDocument(catalog *pkg.Catalog, s scope.Scope) (Document, error) {
|
||||
@@ -18,15 +15,11 @@ func NewDocument(catalog *pkg.Catalog, s scope.Scope) (Document, error) {
|
||||
Artifacts: make([]Artifact, 0),
|
||||
}
|
||||
|
||||
srcObj := s.Source()
|
||||
switch src := srcObj.(type) {
|
||||
case scope.ImageSource:
|
||||
doc.Image = NewImage(src)
|
||||
case scope.DirSource:
|
||||
doc.Directory = &s.DirSrc.Path
|
||||
default:
|
||||
return Document{}, fmt.Errorf("unsupported source: %T", src)
|
||||
src, err := NewSource(s)
|
||||
if err != nil {
|
||||
return Document{}, nil
|
||||
}
|
||||
doc.Source = src
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
art, err := NewArtifact(p, s)
|
||||
|
||||
@@ -6,12 +6,12 @@ type Image struct {
|
||||
Layers []Layer `json:"layers"`
|
||||
Size int64 `json:"size"`
|
||||
Digest string `json:"digest"`
|
||||
MediaType string `json:"media-type"`
|
||||
MediaType string `json:"mediaType"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type Layer struct {
|
||||
MediaType string `json:"media-type"`
|
||||
MediaType string `json:"mediaType"`
|
||||
Digest string `json:"digest"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
@@ -11,12 +11,11 @@ type Locations interface{}
|
||||
|
||||
type ImageLocation struct {
|
||||
Path string `json:"path"`
|
||||
LayerIndex uint `json:"layer-index"`
|
||||
LayerIndex uint `json:"layerIndex"`
|
||||
}
|
||||
|
||||
func NewLocations(p *pkg.Package, s scope.Scope) (Locations, error) {
|
||||
srcObj := s.Source()
|
||||
switch src := srcObj.(type) {
|
||||
switch src := s.Source.(type) {
|
||||
case scope.ImageSource:
|
||||
locations := make([]ImageLocation, len(p.Source))
|
||||
for idx := range p.Source {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
@@ -72,11 +73,11 @@ func TestJsonImgsPresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
|
||||
if *update {
|
||||
testutils.UpdateGoldenFixtureImage(t, testImage)
|
||||
imagetest.UpdateGoldenFixtureImage(t, testImage)
|
||||
}
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img := testutils.GetGoldenFixtureImage(t, testImage)
|
||||
img := imagetest.GetGoldenFixtureImage(t, testImage)
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
Type string `json:"type"`
|
||||
Target interface{} `json:"target"`
|
||||
}
|
||||
|
||||
func NewSource(s scope.Scope) (Source, error) {
|
||||
switch src := s.Source.(type) {
|
||||
case scope.ImageSource:
|
||||
return Source{
|
||||
Type: "image",
|
||||
Target: NewImage(src),
|
||||
}, nil
|
||||
case scope.DirSource:
|
||||
return Source{
|
||||
Type: "directory",
|
||||
Target: src.Path,
|
||||
}, nil
|
||||
default:
|
||||
return Source{}, fmt.Errorf("unsupported source: %T", src)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "deb",
|
||||
"found-by": [
|
||||
"foundBy": [
|
||||
"the-cataloger-1"
|
||||
],
|
||||
"locations": [
|
||||
@@ -15,7 +15,7 @@
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
"found-by": [
|
||||
"foundBy": [
|
||||
"the-cataloger-2"
|
||||
],
|
||||
"locations": [
|
||||
@@ -23,5 +23,8 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"directory": "/some/path"
|
||||
"source": {
|
||||
"type": "directory",
|
||||
"target": "/some/path"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "deb",
|
||||
"found-by": [
|
||||
"foundBy": [
|
||||
"the-cataloger-1"
|
||||
],
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layer-index": 0
|
||||
"layerIndex": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -18,40 +18,43 @@
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
"found-by": [
|
||||
"foundBy": [
|
||||
"the-cataloger-2"
|
||||
],
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layer-index": 1
|
||||
"layerIndex": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"image": {
|
||||
"layers": [
|
||||
{
|
||||
"media-type": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:056c0789fa9ad629ceae6d09713fb035f84115af3c4a88a43aa60f13bc683053",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"media-type": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:b461c48116592c570a66fed71d5b09662a8172e168b7938cf317af47872cdc9b",
|
||||
"size": 16
|
||||
},
|
||||
{
|
||||
"media-type": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:00b80053e05c01da485015610d288ce3185fac00d251e2ada02b45a7a7c5f589",
|
||||
"size": 27
|
||||
}
|
||||
],
|
||||
"size": 65,
|
||||
"digest": "sha256:3c53d2d891940f8d8e95acb77b58752f54dc5de9d91d19dd90ced2db76256cea",
|
||||
"media-type": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
"anchore-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"
|
||||
]
|
||||
"source": {
|
||||
"type": "image",
|
||||
"target": {
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:e158b57d6f5a96ef5fd22f2fe76c70b5ba6ff5b2619f9d83125b2aad0492ac7b",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:da21056e7bf4308ecea0c0836848a7fe92f38fdcf35bc09ee6d98e7ab7beeebf",
|
||||
"size": 16
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:f0e18aa6032c24659a9c741fc36ca56f589782ea132061ccf6f52b952403da94",
|
||||
"size": 27
|
||||
}
|
||||
],
|
||||
"size": 65,
|
||||
"digest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
"stereoscope-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
|
||||
@@ -50,6 +51,7 @@ func (pres *Presenter) Present(output io.Writer) error {
|
||||
}
|
||||
return false
|
||||
})
|
||||
rows = removeDuplicateRows(rows)
|
||||
|
||||
table := tablewriter.NewWriter(output)
|
||||
|
||||
@@ -71,3 +73,21 @@ func (pres *Presenter) Present(output io.Writer) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeDuplicateRows(items [][]string) [][]string {
|
||||
seen := map[string][]string{}
|
||||
// nolint:prealloc
|
||||
var result [][]string
|
||||
|
||||
for _, v := range items {
|
||||
key := strings.Join(v, "|")
|
||||
if seen[key] != nil {
|
||||
// dup!
|
||||
continue
|
||||
}
|
||||
|
||||
seen[key] = v
|
||||
result = append(result, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ package table
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"github.com/go-test/deep"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
@@ -20,7 +22,7 @@ func TestTablePresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", testImage)
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", testImage)
|
||||
defer cleanup()
|
||||
|
||||
// populate catalog with test data
|
||||
@@ -63,3 +65,32 @@ func TestTablePresenter(t *testing.T) {
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveDuplicateRows(t *testing.T) {
|
||||
data := [][]string{
|
||||
{"1", "2", "3"},
|
||||
{"a", "b", "c"},
|
||||
{"1", "2", "3"},
|
||||
{"a", "b", "c"},
|
||||
{"1", "2", "3"},
|
||||
{"4", "5", "6"},
|
||||
{"1", "2", "1"},
|
||||
}
|
||||
|
||||
expected := [][]string{
|
||||
{"1", "2", "3"},
|
||||
{"a", "b", "c"},
|
||||
{"4", "5", "6"},
|
||||
{"1", "2", "1"},
|
||||
}
|
||||
|
||||
actual := removeDuplicateRows(data)
|
||||
|
||||
if diffs := deep.Equal(expected, actual); len(diffs) > 0 {
|
||||
t.Errorf("found diffs!")
|
||||
for _, d := range diffs {
|
||||
t.Errorf(" diff: %+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,9 +27,8 @@ func (pres *Presenter) Present(output io.Writer) error {
|
||||
// init the tabular writer
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)
|
||||
srcObj := pres.scope.Source()
|
||||
|
||||
switch src := srcObj.(type) {
|
||||
switch src := pres.scope.Source.(type) {
|
||||
case scope.DirSource:
|
||||
fmt.Fprintln(w, fmt.Sprintf("[Path: %s]", src.Path))
|
||||
case scope.ImageSource:
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
@@ -67,7 +68,7 @@ func TestTextImgPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
catalog := pkg.NewCatalog()
|
||||
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-simple")
|
||||
defer cleanup()
|
||||
|
||||
// populate catalog with test data
|
||||
@@ -92,7 +93,7 @@ func TestTextImgPresenter(t *testing.T) {
|
||||
})
|
||||
|
||||
// stub out all the digests so that they don't affect tests comparisons
|
||||
// TODO: update with go-testutils feature when issue #1 is resolved
|
||||
// TODO: update with stereoscope test utils feature when this issue is resolved: https://github.com/anchore/stereoscope/issues/43
|
||||
for _, l := range img.Layers {
|
||||
l.Metadata.Digest = "sha256:ad8ecdc058976c07e7e347cb89fa9ad86a294b5ceaae6d09713fb035f84115abf3c4a2388a4af3aa60f13b94f4c6846930bdf53"
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package scope
|
||||
|
||||
import "strings"
|
||||
|
||||
// Potentially consider moving this out into a generic package that parses user input.
|
||||
// Aside from scope, this is the 2nd package that looks at a string to parse the input
|
||||
// and return an Option type.
|
||||
|
||||
const (
|
||||
// nolint:varcheck,deadcode
|
||||
unknownProtocol protocolType = iota
|
||||
imageProtocol
|
||||
directoryProtocol
|
||||
)
|
||||
|
||||
var protocolStr = []string{
|
||||
"UnknownProtocol",
|
||||
"Image",
|
||||
"Directory",
|
||||
}
|
||||
|
||||
type protocolType int
|
||||
|
||||
type protocol struct {
|
||||
Type protocolType
|
||||
Value string
|
||||
}
|
||||
|
||||
func newProtocol(userStr string) protocol {
|
||||
candidates := strings.Split(userStr, "://")
|
||||
|
||||
switch len(candidates) {
|
||||
case 2:
|
||||
if strings.HasPrefix(userStr, "dir://") {
|
||||
return protocol{
|
||||
Type: directoryProtocol,
|
||||
Value: strings.TrimPrefix(userStr, "dir://"),
|
||||
}
|
||||
}
|
||||
// default to an Image for anything else since stereoscope can handle this
|
||||
return protocol{
|
||||
Type: imageProtocol,
|
||||
Value: userStr,
|
||||
}
|
||||
default:
|
||||
return protocol{
|
||||
Type: imageProtocol,
|
||||
Value: userStr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o protocolType) String() string {
|
||||
if int(o) >= len(protocolStr) || o < 0 {
|
||||
return protocolStr[0]
|
||||
}
|
||||
|
||||
return protocolStr[o]
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package scope
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewProtocol(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
expType protocolType
|
||||
expValue string
|
||||
}{
|
||||
{
|
||||
desc: "directory protocol",
|
||||
input: "dir:///opt/",
|
||||
expType: directoryProtocol,
|
||||
expValue: "/opt/",
|
||||
},
|
||||
{
|
||||
desc: "unknown protocol",
|
||||
input: "s4:///opt/",
|
||||
expType: imageProtocol,
|
||||
expValue: "s4:///opt/",
|
||||
},
|
||||
{
|
||||
desc: "docker protocol",
|
||||
input: "docker://ubuntu:20.04",
|
||||
expType: imageProtocol,
|
||||
expValue: "docker://ubuntu:20.04",
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
p := newProtocol(test.input)
|
||||
if p.Type != test.expType {
|
||||
t.Errorf("mismatched type in protocol: '%v' != '%v'", p.Type, test.expType)
|
||||
}
|
||||
if p.Value != test.expValue {
|
||||
t.Errorf("mismatched protocol value: '%s' != '%s'", p.Value, test.expValue)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
)
|
||||
|
||||
@@ -83,7 +83,7 @@ func TestAllLayersResolver_FilesByPath(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-symlinks")
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
|
||||
defer cleanup()
|
||||
|
||||
resolver, err := NewAllLayersResolver(img)
|
||||
@@ -191,7 +191,7 @@ func TestAllLayersResolver_FilesByGlob(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-symlinks")
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
|
||||
defer cleanup()
|
||||
|
||||
resolver, err := NewAllLayersResolver(img)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestImageSquashResolver_FilesByPath(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-symlinks")
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
|
||||
defer cleanup()
|
||||
|
||||
resolver, err := NewImageSquashResolver(img)
|
||||
@@ -122,7 +122,7 @@ func TestImageSquashResolver_FilesByGlob(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
img, cleanup := testutils.GetFixtureImage(t, "docker-archive", "image-symlinks")
|
||||
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
|
||||
defer cleanup()
|
||||
|
||||
resolver, err := NewImageSquashResolver(img)
|
||||
|
||||
+70
-36
@@ -7,7 +7,11 @@ package scope
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/anchore/stereoscope"
|
||||
|
||||
@@ -15,6 +19,14 @@ import (
|
||||
"github.com/anchore/syft/syft/scope/resolvers"
|
||||
)
|
||||
|
||||
const (
|
||||
UnknownScheme Scheme = "unknown-scheme"
|
||||
DirectoryScheme Scheme = "directory-scheme"
|
||||
ImageScheme Scheme = "image-scheme"
|
||||
)
|
||||
|
||||
type Scheme string
|
||||
|
||||
// ImageSource represents a data source that is a container image
|
||||
type ImageSource struct {
|
||||
Img *image.Image // the image object to be cataloged
|
||||
@@ -30,35 +42,43 @@ type DirSource struct {
|
||||
type Scope struct {
|
||||
Option Option // specific perspective to catalog
|
||||
Resolver Resolver // a Resolver object to use in file path/glob resolution and file contents resolution
|
||||
ImgSrc ImageSource // the specific image to be cataloged
|
||||
DirSrc DirSource // the specific directory to be cataloged
|
||||
Source interface{} // the specific source object to be cataloged
|
||||
Scheme Scheme // the source data scheme type (directory or image)
|
||||
}
|
||||
|
||||
// NewScope produces a Scope based on userInput like dir:// or image:tag
|
||||
// NewScope produces a Scope based on userInput like dir: or image:tag
|
||||
func NewScope(userInput string, o Option) (Scope, func(), error) {
|
||||
protocol := newProtocol(userInput)
|
||||
fs := afero.NewOsFs()
|
||||
parsedScheme, location, err := detectScheme(fs, image.DetectSource, userInput)
|
||||
if err != nil {
|
||||
return Scope{}, func() {}, fmt.Errorf("unable to parse input=%q: %w", userInput, err)
|
||||
}
|
||||
|
||||
switch protocol.Type {
|
||||
case directoryProtocol:
|
||||
err := isValidPath(protocol.Value)
|
||||
switch parsedScheme {
|
||||
case DirectoryScheme:
|
||||
fileMeta, err := fs.Stat(location)
|
||||
if err != nil {
|
||||
return Scope{}, func() {}, fmt.Errorf("unable to process path, must exist and be a directory: %w", err)
|
||||
return Scope{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err)
|
||||
}
|
||||
|
||||
s, err := NewScopeFromDir(protocol.Value)
|
||||
if !fileMeta.IsDir() {
|
||||
return Scope{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err)
|
||||
}
|
||||
|
||||
s, err := NewScopeFromDir(location)
|
||||
if err != nil {
|
||||
return Scope{}, func() {}, fmt.Errorf("could not populate scope from path (%s): %w", protocol.Value, err)
|
||||
return Scope{}, func() {}, fmt.Errorf("could not populate scope from path=%q: %w", location, err)
|
||||
}
|
||||
return s, func() {}, nil
|
||||
|
||||
case imageProtocol:
|
||||
img, err := stereoscope.GetImage(userInput)
|
||||
case ImageScheme:
|
||||
img, err := stereoscope.GetImage(location)
|
||||
cleanup := func() {
|
||||
stereoscope.Cleanup()
|
||||
}
|
||||
|
||||
if err != nil || img == nil {
|
||||
return Scope{}, cleanup, fmt.Errorf("could not fetch image '%s': %w", userInput, err)
|
||||
return Scope{}, cleanup, fmt.Errorf("could not fetch image '%s': %w", location, err)
|
||||
}
|
||||
|
||||
s, err := NewScopeFromImage(img, o)
|
||||
@@ -66,10 +86,9 @@ func NewScope(userInput string, o Option) (Scope, func(), error) {
|
||||
return Scope{}, cleanup, fmt.Errorf("could not populate scope with image: %w", err)
|
||||
}
|
||||
return s, cleanup, nil
|
||||
|
||||
default:
|
||||
return Scope{}, func() {}, fmt.Errorf("unable to process input for scanning: '%s'", userInput)
|
||||
}
|
||||
|
||||
return Scope{}, func() {}, fmt.Errorf("unable to process input for scanning: '%s'", userInput)
|
||||
}
|
||||
|
||||
// NewScopeFromDir creates a new scope object tailored to catalog a given filesystem directory recursively.
|
||||
@@ -78,9 +97,10 @@ func NewScopeFromDir(path string) (Scope, error) {
|
||||
Resolver: &resolvers.DirectoryResolver{
|
||||
Path: path,
|
||||
},
|
||||
DirSrc: DirSource{
|
||||
Source: DirSource{
|
||||
Path: path,
|
||||
},
|
||||
Scheme: DirectoryScheme,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -99,34 +119,48 @@ func NewScopeFromImage(img *image.Image, option Option) (Scope, error) {
|
||||
return Scope{
|
||||
Option: option,
|
||||
Resolver: resolver,
|
||||
ImgSrc: ImageSource{
|
||||
Source: ImageSource{
|
||||
Img: img,
|
||||
},
|
||||
Scheme: ImageScheme,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Source returns the configured data source (either a dir source or container image source)
|
||||
func (s Scope) Source() interface{} {
|
||||
if s.ImgSrc != (ImageSource{}) {
|
||||
return s.ImgSrc
|
||||
}
|
||||
if s.DirSrc != (DirSource{}) {
|
||||
return s.DirSrc
|
||||
type sourceDetector func(string) (image.Source, string, error)
|
||||
|
||||
func detectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (Scheme, string, error) {
|
||||
if strings.HasPrefix(userInput, "dir:") {
|
||||
// blindly trust the user's scheme
|
||||
dirLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "dir:"))
|
||||
if err != nil {
|
||||
return UnknownScheme, "", fmt.Errorf("unable to expand directory path: %w", err)
|
||||
}
|
||||
return DirectoryScheme, dirLocation, nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isValidPath ensures that the user-provided input will correspond to a path that exists and is a directory
|
||||
func isValidPath(userInput string) error {
|
||||
fileMeta, err := os.Stat(userInput)
|
||||
// we should attempt to let stereoscope determine what the source is first --just because the source is a valid directory
|
||||
// doesn't mean we yet know if it is an OCI layout directory (to be treated as an image) or if it is a generic filesystem directory.
|
||||
source, imageSpec, err := imageDetector(userInput)
|
||||
if err != nil {
|
||||
return err
|
||||
return UnknownScheme, "", fmt.Errorf("unable to detect the scheme from %q: %w", userInput, err)
|
||||
}
|
||||
|
||||
if fileMeta.IsDir() {
|
||||
return nil
|
||||
if source == image.UnknownSource {
|
||||
dirLocation, err := homedir.Expand(userInput)
|
||||
if err != nil {
|
||||
return UnknownScheme, "", fmt.Errorf("unable to expand potential directory path: %w", err)
|
||||
}
|
||||
|
||||
fileMeta, err := fs.Stat(dirLocation)
|
||||
if err != nil {
|
||||
return UnknownScheme, "", nil
|
||||
}
|
||||
|
||||
if fileMeta.IsDir() {
|
||||
return DirectoryScheme, dirLocation, nil
|
||||
}
|
||||
return UnknownScheme, "", nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("path is not a directory: %s", userInput)
|
||||
return ImageScheme, imageSpec, nil
|
||||
}
|
||||
|
||||
+232
-21
@@ -1,10 +1,13 @@
|
||||
package scope
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func TestNewScopeFromImageFails(t *testing.T) {
|
||||
@@ -75,8 +78,8 @@ func TestDirectoryScope(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("could not create NewDirScope: %w", err)
|
||||
}
|
||||
if p.DirSrc.Path != test.input {
|
||||
t.Errorf("mismatched stringer: '%s' != '%s'", p.DirSrc.Path, test.input)
|
||||
if p.Source.(DirSource).Path != test.input {
|
||||
t.Errorf("mismatched stringer: '%s' != '%s'", p.Source.(DirSource).Path, test.input)
|
||||
}
|
||||
|
||||
refs, err := p.Resolver.FilesByPath(test.inputPaths...)
|
||||
@@ -214,39 +217,247 @@ func TestFilesByGlob(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidPath(t *testing.T) {
|
||||
func TestDetectScheme(t *testing.T) {
|
||||
type detectorResult struct {
|
||||
src image.Source
|
||||
ref string
|
||||
err error
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
isError bool
|
||||
name string
|
||||
userInput string
|
||||
dirs []string
|
||||
detection detectorResult
|
||||
expectedScheme Scheme
|
||||
expectedLocation string
|
||||
}{
|
||||
{
|
||||
desc: "path is valid",
|
||||
input: "test-fixtures",
|
||||
isError: false,
|
||||
name: "docker-image-ref",
|
||||
userInput: "wagoodman/dive:latest",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "wagoodman/dive:latest",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
expectedLocation: "wagoodman/dive:latest",
|
||||
},
|
||||
{
|
||||
desc: "file is invalid",
|
||||
input: "test-fixtures/.vimrc",
|
||||
isError: true,
|
||||
name: "docker-image-ref-no-tag",
|
||||
userInput: "wagoodman/dive",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "wagoodman/dive",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
expectedLocation: "wagoodman/dive",
|
||||
},
|
||||
{
|
||||
desc: "path does not exist",
|
||||
input: "foo/bar/baz",
|
||||
isError: true,
|
||||
name: "docker-image-explicit-scheme",
|
||||
userInput: "docker:wagoodman/dive:latest",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "wagoodman/dive:latest",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
expectedLocation: "wagoodman/dive:latest",
|
||||
},
|
||||
{
|
||||
name: "docker-image-explicit-scheme-no-tag",
|
||||
userInput: "docker:wagoodman/dive",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "wagoodman/dive",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
expectedLocation: "wagoodman/dive",
|
||||
},
|
||||
{
|
||||
name: "docker-image-edge-case",
|
||||
userInput: "docker:latest",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "latest",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
// we want to be able to handle this case better, however, I don't see a way to do this
|
||||
// the user will need to provide more explicit input (docker:docker:latest)
|
||||
expectedLocation: "latest",
|
||||
},
|
||||
{
|
||||
name: "docker-image-edge-case-explicit",
|
||||
userInput: "docker:docker:latest",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "docker:latest",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
// we want to be able to handle this case better, however, I don't see a way to do this
|
||||
// the user will need to provide more explicit input (docker:docker:latest)
|
||||
expectedLocation: "docker:latest",
|
||||
},
|
||||
{
|
||||
name: "oci-tar",
|
||||
userInput: "some/path-to-file",
|
||||
detection: detectorResult{
|
||||
src: image.OciTarballSource,
|
||||
ref: "some/path-to-file",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
expectedLocation: "some/path-to-file",
|
||||
},
|
||||
{
|
||||
name: "oci-dir",
|
||||
userInput: "some/path-to-dir",
|
||||
detection: detectorResult{
|
||||
src: image.OciDirectorySource,
|
||||
ref: "some/path-to-dir",
|
||||
},
|
||||
dirs: []string{"some/path-to-dir"},
|
||||
expectedScheme: ImageScheme,
|
||||
expectedLocation: "some/path-to-dir",
|
||||
},
|
||||
{
|
||||
name: "guess-dir",
|
||||
userInput: "some/path-to-dir",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
dirs: []string{"some/path-to-dir"},
|
||||
expectedScheme: DirectoryScheme,
|
||||
expectedLocation: "some/path-to-dir",
|
||||
},
|
||||
{
|
||||
name: "generic-dir-does-not-exist",
|
||||
userInput: "some/path-to-dir",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "some/path-to-dir",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
expectedLocation: "some/path-to-dir",
|
||||
},
|
||||
{
|
||||
name: "explicit-dir",
|
||||
userInput: "dir:some/path-to-dir",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
dirs: []string{"some/path-to-dir"},
|
||||
expectedScheme: DirectoryScheme,
|
||||
expectedLocation: "some/path-to-dir",
|
||||
},
|
||||
{
|
||||
name: "explicit-current-dir",
|
||||
userInput: "dir:.",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
expectedScheme: DirectoryScheme,
|
||||
expectedLocation: ".",
|
||||
},
|
||||
{
|
||||
name: "current-dir",
|
||||
userInput: ".",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
expectedScheme: DirectoryScheme,
|
||||
expectedLocation: ".",
|
||||
},
|
||||
// we should support tilde expansion
|
||||
{
|
||||
name: "tilde-expansion-image-implicit",
|
||||
userInput: "~/some-path",
|
||||
detection: detectorResult{
|
||||
src: image.OciDirectorySource,
|
||||
ref: "~/some-path",
|
||||
},
|
||||
expectedScheme: ImageScheme,
|
||||
expectedLocation: "~/some-path",
|
||||
},
|
||||
{
|
||||
name: "tilde-expansion-dir-implicit",
|
||||
userInput: "~/some-path",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
dirs: []string{"~/some-path"},
|
||||
expectedScheme: DirectoryScheme,
|
||||
expectedLocation: "~/some-path",
|
||||
},
|
||||
{
|
||||
name: "tilde-expansion-dir-explicit-exists",
|
||||
userInput: "dir:~/some-path",
|
||||
dirs: []string{"~/some-path"},
|
||||
expectedScheme: DirectoryScheme,
|
||||
expectedLocation: "~/some-path",
|
||||
},
|
||||
{
|
||||
name: "tilde-expansion-dir-explicit-dne",
|
||||
userInput: "dir:~/some-path",
|
||||
expectedScheme: DirectoryScheme,
|
||||
expectedLocation: "~/some-path",
|
||||
},
|
||||
{
|
||||
name: "tilde-expansion-dir-implicit-dne",
|
||||
userInput: "~/some-path",
|
||||
expectedScheme: UnknownScheme,
|
||||
expectedLocation: "",
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
err := isValidPath(test.input)
|
||||
if err != nil && !test.isError {
|
||||
t.Errorf("did not expect and error, got: %w", err)
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
for _, p := range test.dirs {
|
||||
expandedExpectedLocation, err := homedir.Expand(p)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to expand path=%q: %+v", p, err)
|
||||
}
|
||||
err = fs.Mkdir(expandedExpectedLocation, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create dummy tar: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && test.isError {
|
||||
t.Errorf("expected an error but didn't get one")
|
||||
imageDetector := func(string) (image.Source, string, error) {
|
||||
// lean on the users real home directory value
|
||||
switch test.detection.src {
|
||||
case image.OciDirectorySource, image.DockerTarballSource, image.OciTarballSource:
|
||||
expandedExpectedLocation, err := homedir.Expand(test.expectedLocation)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to expand path=%q: %+v", test.expectedLocation, err)
|
||||
}
|
||||
return test.detection.src, expandedExpectedLocation, test.detection.err
|
||||
default:
|
||||
return test.detection.src, test.detection.ref, test.detection.err
|
||||
}
|
||||
}
|
||||
|
||||
actualScheme, actualLocation, err := detectScheme(fs, imageDetector, test.userInput)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err : %+v", err)
|
||||
}
|
||||
|
||||
if actualScheme != test.expectedScheme {
|
||||
t.Errorf("expected scheme %q , got %q", test.expectedScheme, actualScheme)
|
||||
}
|
||||
|
||||
// lean on the users real home directory value
|
||||
expandedExpectedLocation, err := homedir.Expand(test.expectedLocation)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to expand path=%q: %+v", test.expectedLocation, err)
|
||||
}
|
||||
|
||||
if actualLocation != expandedExpectedLocation {
|
||||
t.Errorf("expected location %q , got %q", expandedExpectedLocation, actualLocation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ docker run --rm \
|
||||
ubuntu:latest \
|
||||
/bin/bash -x -c "\
|
||||
DEBIAN_FRONTEND=noninteractive apt install ${DISTDIR}/syft_*_linux_amd64.deb -y && \
|
||||
syft version -v && \
|
||||
syft version && \
|
||||
syft ${TEST_IMAGE} -vv -o json > ${REPORT} \
|
||||
"
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ function cleanup {
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# install skopeo (pinned to 1.1.0)
|
||||
skopeo --version || brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/75e8d7a40af77b48cc91f4bdb7d669f891a6de60/Formula/skopeo.rb
|
||||
# install skopeo
|
||||
skopeo --version || brew install skopeo
|
||||
|
||||
# fetch test image
|
||||
skopeo --override-os linux copy docker://docker.io/${TEST_IMAGE} docker-archive:${TEST_IMAGE_TAR}
|
||||
@@ -38,7 +38,7 @@ ls -alh ${TEST_IMAGE_TAR}
|
||||
|
||||
# run syft
|
||||
chmod 755 ${DISTDIR}/syft_darwin_amd64/syft
|
||||
${DISTDIR}/syft_darwin_amd64/syft version -v
|
||||
${DISTDIR}/syft_darwin_amd64/syft version
|
||||
SYFT_CHECK_FOR_APP_UPDATE=0 ${DISTDIR}/syft_darwin_amd64/syft docker-archive://${TEST_IMAGE_TAR} -vv -o json > ${REPORT}
|
||||
|
||||
# keep the generated report around
|
||||
|
||||
@@ -40,7 +40,7 @@ docker run --rm \
|
||||
centos:latest \
|
||||
/bin/bash -x -c "\
|
||||
rpm -ivh ${DISTDIR}/syft_*_linux_amd64.rpm && \
|
||||
syft version -v && \
|
||||
syft version && \
|
||||
syft ${TEST_IMAGE} -vv -o json > ${REPORT} \
|
||||
"
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ all: clean-syft
|
||||
compare-image: $(SYFT_REPORT) $(INLINE_REPORT)
|
||||
./compare.py $(COMPARE_IMAGE)
|
||||
|
||||
.PHONY: gather-iamge
|
||||
.PHONY: gather-image
|
||||
gather-image: $(SYFT_REPORT) $(INLINE_REPORT)
|
||||
|
||||
$(INLINE_REPORT):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
# TODO: add "alpine:3.12.0" back
|
||||
images=("debian:10.5" "centos:8.2.2004" )
|
||||
# TODO: Add "alpine:3.12.0" back in when we've figured out how to handle the apk version field w/ and w/o release information (see issue: https://github.com/anchore/syft/pull/195)
|
||||
images=("debian:10.5" "centos:8.2.2004" "rails:5.0.1")
|
||||
|
||||
# gather all image analyses
|
||||
for img in "${images[@]}"; do
|
||||
@@ -14,4 +14,4 @@ done
|
||||
for img in "${images[@]}"; do
|
||||
echo "Comparing results for $img"
|
||||
COMPARE_IMAGE=${img} make compare-image
|
||||
done
|
||||
done
|
||||
|
||||
@@ -33,8 +33,9 @@ class InlineScan:
|
||||
|
||||
def packages(self):
|
||||
python_packages, python_metadata = self._python_packages()
|
||||
os_pacakges, os_metadata = self._os_packages()
|
||||
return python_packages | os_pacakges, {**python_metadata, **os_metadata}
|
||||
gem_packages, gem_metadata = self._gem_packages()
|
||||
os_packages, os_metadata = self._os_packages()
|
||||
return python_packages | os_packages | gem_packages , {**python_metadata, **os_metadata, **gem_metadata}
|
||||
|
||||
def _report_path(self, report):
|
||||
return os.path.join(
|
||||
@@ -67,6 +68,18 @@ class InlineScan:
|
||||
|
||||
return packages, metadata
|
||||
|
||||
def _gem_packages(self):
|
||||
packages = set()
|
||||
metadata = collections.defaultdict(dict)
|
||||
for entry in self._enumerate_section(
|
||||
report="content-gem", section="content"
|
||||
):
|
||||
package = Package(name=entry["package"], type=entry["type"].lower(),)
|
||||
packages.add(package)
|
||||
metadata[package.type][package] = Metadata(version=entry["version"])
|
||||
|
||||
return packages, metadata
|
||||
|
||||
def _os_packages(self):
|
||||
packages = set()
|
||||
metadata = collections.defaultdict(dict)
|
||||
@@ -162,7 +175,7 @@ def main(image):
|
||||
for package in syft_packages:
|
||||
metadata = syft_metadata[package.type][package]
|
||||
# we only want to really count mismatched metadata for packages that are at least found by inline
|
||||
if package in inline_metadata[package.type]:
|
||||
if package in inline_metadata.get(package.type, []):
|
||||
syft_overlap_metadata_set.add((package, metadata))
|
||||
|
||||
same_metadata = syft_overlap_metadata_set & inline_metadata_set
|
||||
@@ -202,8 +215,10 @@ def main(image):
|
||||
|
||||
print(colors.bold+"Summary:", colors.reset)
|
||||
print(" Image: %s" % image)
|
||||
print(" Inline Packages: %d" % len(inline_packages))
|
||||
print(" Syft Packages: %d" % len(syft_packages))
|
||||
print(" Inline Packages : %d" % len(inline_packages))
|
||||
print(" Syft Packages : %d" % len(syft_packages))
|
||||
print(" (extra) : %d" % len(bonus_packages))
|
||||
print(" (missing) : %d" % len(missing_packages))
|
||||
print(
|
||||
" Baseline Packages Matched: %2.3f %% (%d/%d packages)"
|
||||
% (percent_overlap_packages, len(same_packages), len(inline_packages))
|
||||
|
||||
@@ -5,9 +5,8 @@ package integration
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
"github.com/go-test/deep"
|
||||
@@ -15,11 +14,11 @@ import (
|
||||
|
||||
func TestDistroImage(t *testing.T) {
|
||||
fixtureImageName := "image-distro-id"
|
||||
_, cleanup := testutils.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := testutils.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
_, cleanup := imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
defer cleanup()
|
||||
|
||||
_, _, actualDistro, err := syft.Catalog("docker-archive://"+tarPath, scope.AllLayersScope)
|
||||
_, _, actualDistro, err := syft.Catalog("docker-archive:"+tarPath, scope.AllLayersScope)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog image: %+v", err)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -99,15 +99,19 @@ func testJsonSchema(t *testing.T, catalog *pkg.Catalog, theScope *scope.Scope, p
|
||||
|
||||
func TestJsonSchemaImg(t *testing.T) {
|
||||
fixtureImageName := "image-pkg-coverage"
|
||||
_, cleanup := testutils.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := testutils.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
_, cleanup := imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
defer cleanup()
|
||||
|
||||
catalog, theScope, _, err := syft.Catalog("docker-archive://"+tarPath, scope.AllLayersScope)
|
||||
catalog, theScope, _, err := syft.Catalog("docker-archive:"+tarPath, scope.AllLayersScope)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog image: %+v", err)
|
||||
}
|
||||
|
||||
var cases []testCase
|
||||
cases = append(cases, commonTestCases...)
|
||||
cases = append(cases, imageOnlyTestCases...)
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
testJsonSchema(t, catalog, theScope, "img")
|
||||
@@ -116,11 +120,15 @@ func TestJsonSchemaImg(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJsonSchemaDirs(t *testing.T) {
|
||||
catalog, theScope, _, err := syft.Catalog("dir://test-fixtures/image-pkg-coverage", scope.AllLayersScope)
|
||||
catalog, theScope, _, err := syft.Catalog("dir:test-fixtures/image-pkg-coverage", scope.AllLayersScope)
|
||||
if err != nil {
|
||||
t.Errorf("unable to create scope from dir: %+v", err)
|
||||
}
|
||||
|
||||
var cases []testCase
|
||||
cases = append(cases, commonTestCases...)
|
||||
cases = append(cases, dirOnlyTestCases...)
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
testJsonSchema(t, catalog, theScope, "dir")
|
||||
|
||||
@@ -4,12 +4,86 @@ package integration
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
var cases = []struct {
|
||||
type testCase struct {
|
||||
name string
|
||||
pkgType pkg.Type
|
||||
pkgLanguage pkg.Language
|
||||
pkgInfo map[string]string
|
||||
}{
|
||||
}
|
||||
|
||||
var imageOnlyTestCases = []testCase{
|
||||
{
|
||||
name: "find gemspec packages",
|
||||
pkgType: pkg.GemPkg,
|
||||
pkgLanguage: pkg.Ruby,
|
||||
pkgInfo: map[string]string{
|
||||
"bundler": "2.1.4",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var dirOnlyTestCases = []testCase{
|
||||
{
|
||||
name: "find gemfile packages",
|
||||
pkgType: pkg.GemPkg,
|
||||
pkgLanguage: pkg.Ruby,
|
||||
pkgInfo: map[string]string{
|
||||
"actionmailer": "4.1.1",
|
||||
"actionpack": "4.1.1",
|
||||
"actionview": "4.1.1",
|
||||
"activemodel": "4.1.1",
|
||||
"activerecord": "4.1.1",
|
||||
"activesupport": "4.1.1",
|
||||
"arel": "5.0.1.20140414130214",
|
||||
"bootstrap-sass": "3.1.1.1",
|
||||
"builder": "3.2.2",
|
||||
"coffee-rails": "4.0.1",
|
||||
"coffee-script": "2.2.0",
|
||||
"coffee-script-source": "1.7.0",
|
||||
"erubis": "2.7.0",
|
||||
"execjs": "2.0.2",
|
||||
"hike": "1.2.3",
|
||||
"i18n": "0.6.9",
|
||||
"jbuilder": "2.0.7",
|
||||
"jquery-rails": "3.1.0",
|
||||
"json": "1.8.1",
|
||||
"kgio": "2.9.2",
|
||||
"libv8": "3.16.14.3",
|
||||
"mail": "2.5.4",
|
||||
"mime-types": "1.25.1",
|
||||
"minitest": "5.3.4",
|
||||
"multi_json": "1.10.1",
|
||||
"mysql2": "0.3.16",
|
||||
"polyglot": "0.3.4",
|
||||
"rack": "1.5.2",
|
||||
"rack-test": "0.6.2",
|
||||
"rails": "4.1.1",
|
||||
"railties": "4.1.1",
|
||||
"raindrops": "0.13.0",
|
||||
"rake": "10.3.2",
|
||||
"rdoc": "4.1.1",
|
||||
"ref": "1.0.5",
|
||||
"sass": "3.2.19",
|
||||
"sass-rails": "4.0.3",
|
||||
"sdoc": "0.4.0",
|
||||
"spring": "1.1.3",
|
||||
"sprockets": "2.11.0",
|
||||
"sprockets-rails": "2.1.3",
|
||||
"sqlite3": "1.3.9",
|
||||
"therubyracer": "0.12.1",
|
||||
"thor": "0.19.1",
|
||||
"thread_safe": "0.3.3",
|
||||
"tilt": "1.4.1",
|
||||
"treetop": "1.4.15",
|
||||
"turbolinks": "2.2.2",
|
||||
"tzinfo": "1.2.0",
|
||||
"uglifier": "2.5.0",
|
||||
"unicorn": "4.8.3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var commonTestCases = []testCase{
|
||||
{
|
||||
name: "find rpmdb packages",
|
||||
pkgType: pkg.RpmPkg,
|
||||
@@ -81,7 +155,13 @@ var cases = []struct {
|
||||
pkgType: pkg.PythonRequirementsPkg,
|
||||
pkgLanguage: pkg.Python,
|
||||
pkgInfo: map[string]string{
|
||||
"flask": "4.0.0",
|
||||
"flask": "4.0.0",
|
||||
"python-dateutil": "2.8.1",
|
||||
"python-swiftclient": "3.8.1",
|
||||
"pytz": "2019.3",
|
||||
"jsonschema": "2.6.0",
|
||||
"passlib": "1.7.2",
|
||||
"pathlib": "1.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -92,64 +172,6 @@ var cases = []struct {
|
||||
"mypy": "v0.770",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find bundler packages",
|
||||
pkgType: pkg.BundlerPkg,
|
||||
pkgLanguage: pkg.Ruby,
|
||||
pkgInfo: map[string]string{
|
||||
"actionmailer": "4.1.1",
|
||||
"actionpack": "4.1.1",
|
||||
"actionview": "4.1.1",
|
||||
"activemodel": "4.1.1",
|
||||
"activerecord": "4.1.1",
|
||||
"activesupport": "4.1.1",
|
||||
"arel": "5.0.1.20140414130214",
|
||||
"bootstrap-sass": "3.1.1.1",
|
||||
"builder": "3.2.2",
|
||||
"coffee-rails": "4.0.1",
|
||||
"coffee-script": "2.2.0",
|
||||
"coffee-script-source": "1.7.0",
|
||||
"erubis": "2.7.0",
|
||||
"execjs": "2.0.2",
|
||||
"hike": "1.2.3",
|
||||
"i18n": "0.6.9",
|
||||
"jbuilder": "2.0.7",
|
||||
"jquery-rails": "3.1.0",
|
||||
"json": "1.8.1",
|
||||
"kgio": "2.9.2",
|
||||
"libv8": "3.16.14.3",
|
||||
"mail": "2.5.4",
|
||||
"mime-types": "1.25.1",
|
||||
"minitest": "5.3.4",
|
||||
"multi_json": "1.10.1",
|
||||
"mysql2": "0.3.16",
|
||||
"polyglot": "0.3.4",
|
||||
"rack": "1.5.2",
|
||||
"rack-test": "0.6.2",
|
||||
"rails": "4.1.1",
|
||||
"railties": "4.1.1",
|
||||
"raindrops": "0.13.0",
|
||||
"rake": "10.3.2",
|
||||
"rdoc": "4.1.1",
|
||||
"ref": "1.0.5",
|
||||
"sass": "3.2.19",
|
||||
"sass-rails": "4.0.3",
|
||||
"sdoc": "0.4.0",
|
||||
"spring": "1.1.3",
|
||||
"sprockets": "2.11.0",
|
||||
"sprockets-rails": "2.1.3",
|
||||
"sqlite3": "1.3.9",
|
||||
"therubyracer": "0.12.1",
|
||||
"thor": "0.19.1",
|
||||
"thread_safe": "0.3.3",
|
||||
"tilt": "1.4.1",
|
||||
"treetop": "1.4.15",
|
||||
"turbolinks": "2.2.2",
|
||||
"tzinfo": "1.2.0",
|
||||
"uglifier": "2.5.0",
|
||||
"unicorn": "4.8.3",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
name: "find apkdb packages",
|
||||
|
||||
@@ -5,22 +5,22 @@ package integration
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/go-test/deep"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
func TestPkgCoverageImage(t *testing.T) {
|
||||
fixtureImageName := "image-pkg-coverage"
|
||||
_, cleanup := testutils.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := testutils.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
_, cleanup := imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
defer cleanup()
|
||||
|
||||
catalog, _, _, err := syft.Catalog("docker-archive://"+tarPath, scope.AllLayersScope)
|
||||
catalog, _, _, err := syft.Catalog("docker-archive:"+tarPath, scope.AllLayersScope)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog image: %+v", err)
|
||||
}
|
||||
@@ -37,6 +37,10 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedPkgs.Add(string(p))
|
||||
}
|
||||
|
||||
var cases []testCase
|
||||
cases = append(cases, commonTestCases...)
|
||||
cases = append(cases, imageOnlyTestCases...)
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
pkgCount := 0
|
||||
@@ -83,15 +87,21 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
// ensure that integration test cases stay in sync with the available catalogers
|
||||
if len(observedLanguages) < len(definedLanguages) {
|
||||
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
|
||||
for _, d := range deep.Equal(observedLanguages, definedLanguages) {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
if len(observedPkgs) < len(definedPkgs) {
|
||||
t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
|
||||
for _, d := range deep.Equal(observedPkgs, definedPkgs) {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPkgCoverageDirectory(t *testing.T) {
|
||||
catalog, _, _, err := syft.Catalog("dir://test-fixtures/image-pkg-coverage", scope.AllLayersScope)
|
||||
catalog, _, _, err := syft.Catalog("dir:test-fixtures/image-pkg-coverage", scope.AllLayersScope)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unable to create scope from dir: %+v", err)
|
||||
@@ -109,6 +119,10 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
||||
definedPkgs.Add(string(p))
|
||||
}
|
||||
|
||||
var cases []testCase
|
||||
cases = append(cases, commonTestCases...)
|
||||
cases = append(cases, dirOnlyTestCases...)
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
pkgCount := 0
|
||||
@@ -152,7 +166,7 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
||||
observedPkgs.Remove(string(pkg.UnknownPkg))
|
||||
definedPkgs.Remove(string(pkg.UnknownPkg))
|
||||
|
||||
// ensure that integration test cases stay in sync with the available catalogers
|
||||
// ensure that integration test commonTestCases stay in sync with the available catalogers
|
||||
if len(observedLanguages) < len(definedLanguages) {
|
||||
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// +build integration
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
func TestRegression212ApkBufferSize(t *testing.T) {
|
||||
// This is a regression test for issue #212 (https://github.com/anchore/syft/issues/212) in which the apk db could
|
||||
// not be processed due to a scanner buffer that was too small
|
||||
|
||||
fixtureImageName := "image-large-apk-data"
|
||||
_, cleanup := imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
defer cleanup()
|
||||
|
||||
catalog, _, _, err := syft.Catalog("docker-archive:"+tarPath, scope.SquashedScope)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog image: %+v", err)
|
||||
}
|
||||
|
||||
expectedPkgs := 57
|
||||
actualPkgs := 0
|
||||
for range catalog.Enumerate(pkg.ApkPkg) {
|
||||
actualPkgs += 1
|
||||
}
|
||||
|
||||
if actualPkgs != expectedPkgs {
|
||||
t.Errorf("unexpected number of APK packages: %d != %d", expectedPkgs, actualPkgs)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM alpine:latest
|
||||
RUN apk add tzdata vim alpine-sdk
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user