Compare commits

..
7 Commits
Author SHA1 Message Date
Alex Goodman 206fd8b7d3 working
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-08 08:37:40 -04:00
Alex Goodman c348d97fe0 not working
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-05 11:16:46 -04:00
Alex Goodman 30bb7cf992 pin stereoscope to dev branch
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-03 18:34:23 -04:00
Alex Goodman 54e0ee4c47 finish repository + make syft client
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-03 18:32:20 -04:00
Alex Goodman c231a9d7cb add collection
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-03 09:28:45 -04:00
Alex Goodman 3e27b6e93f rough draft for plugin system
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-02 23:34:00 -04:00
Alex Goodman 63ee5ba098 refactor catalogers to consume io.Readers instead of strings
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-08-31 17:41:05 -04:00
257 changed files with 6015 additions and 9699 deletions
+129
View File
@@ -0,0 +1,129 @@
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"
+1 -1
View File
@@ -16,5 +16,5 @@ assignees: ''
**Anything else we need to know?**:
**Environment**:
- Output of `syft version`:
- syft version (use `syft version -v`):
- OS (e.g: `cat /etc/os-release` or similar):
@@ -1,31 +0,0 @@
#!/usr/bin/env bash
set -eu
CI_HOME="/Users/runner"
if [[ "${HOME}" != "${CI_HOME}" ]]; then
printf "WARNING! It looks like this isn't the CI environment. This script modifies the macOS Keychain setup in ways you probably wouldn't want for your own machine. It also requires an Apple Developer ID Certificate that you shouldn't have outside of the CI environment.\n\nExiting early to make sure nothing bad happens.\n"
exit 1
fi
# Install gon (see https://github.com/mitchellh/gon for details).
brew tap mitchellh/gon
brew install mitchellh/gon/gon
# Write signing certificate to disk from environment variable.
CERT_FILE="$HOME/developer_id_certificate.p12"
echo -n "$APPLE_DEVELOPER_ID_CERT" | base64 --decode > "$CERT_FILE"
# In order to have all keychain interactions avoid an interactive user prompt, we need to control the password for the keychain in question, which means we need to create a new keychain into which we'll import the signing certificate and from which we'll later access this certificate during code signing.
EPHEMERAL_KEYCHAIN="ci-ephemeral-keychain"
EPHEMERAL_KEYCHAIN_PASSWORD="$(openssl rand -base64 100)"
security create-keychain -p "${EPHEMERAL_KEYCHAIN_PASSWORD}" "${EPHEMERAL_KEYCHAIN}"
# Import signing certificate into the keychain. (This is a pre-requisite for gon, which is invoked via goreleaser.)
EPHEMERAL_KEYCHAIN_FULL_PATH="$HOME/Library/Keychains/${EPHEMERAL_KEYCHAIN}-db"
security import "${CERT_FILE}" -k "${EPHEMERAL_KEYCHAIN_FULL_PATH}" -P "${APPLE_DEVELOPER_ID_CERT_PASS}" -T "$(command -v codesign)"
# Setting the partition list for this certificate's private key to include "apple-tool:" and "apple:" allows the codesign command to access this keychain item without an interactive user prompt. (codesign is invoked by gon.)
security set-key-partition-list -S "apple-tool:,apple:" -s -k "${EPHEMERAL_KEYCHAIN_PASSWORD}" "${EPHEMERAL_KEYCHAIN_FULL_PATH}"
# Make this new keychain the user's default keychain, so that codesign will be able to find this certificate when we specify it during signing.
security default-keychain -d "user" -s "${EPHEMERAL_KEYCHAIN_FULL_PATH}"
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/env bash
set -eu
IS_SNAPSHOT="$1" # e.g. "true", "false"
if [[ "${IS_SNAPSHOT}" == "true" ]]; then
# This is a snapshot build —— skipping signing and notarization...
exit 0
fi
GON_CONFIG="$2" # e.g. "gon.hcl"
NEW_NAME_WITHOUT_EXTENSION="$3" # e.g. "./dist/syft-0.1.0"
ORIGINAL_NAME_WITHOUT_EXTENSION="./dist/output" # This should match dmg and zip output_path in the gon config file, without the extension.
gon "${GON_CONFIG}"
# Rename outputs with specified desired name
mv -v "${ORIGINAL_NAME_WITHOUT_EXTENSION}.dmg" "${NEW_NAME_WITHOUT_EXTENSION}.dmg"
mv -v "${ORIGINAL_NAME_WITHOUT_EXTENSION}.zip" "${NEW_NAME_WITHOUT_EXTENSION}.zip"
+9 -4
View File
@@ -15,9 +15,14 @@ fi
echo "creating and publishing version file"
# create a version file for version-update checks
VERSION_FILE="${DISTDIR}/VERSION"
echo "${VERSION}" | tee "${VERSION_FILE}"
echo "${VERSION}" | tee ${DISTDIR}/VERSION
# upload the version file that supports the application version update check
export AWS_DEFAULT_REGION=us-west-2
aws s3 cp "${VERSION_FILE}" s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION
docker run --rm \
-i \
-e AWS_DEFAULT_REGION=us-west-2 \
-e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
-e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
-v $(pwd)/${DISTDIR}/:/distmount \
amazon/aws-cli \
s3 cp /distmount/VERSION s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION
+7 -45
View File
@@ -14,7 +14,7 @@ env:
jobs:
Build-Snapshot-Artifacts:
runs-on: macos-latest # We're creating these snapshot builds on macOS to be consistent with our release workflow's build process, which also takes place on macOS (due to code signing requirements).
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2
@@ -35,9 +35,9 @@ jobs:
${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}-
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: Bootstrap project dependencies
if: steps.bootstrap-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Bootstrap dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: make ci-bootstrap
- name: Import GPG key
id: import_gpg
@@ -64,20 +64,10 @@ 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 ]
# come Nov 30 2020 ubuntu-latest will be ubuntu-20.04, until then it needs to be explicitly referenced due to python 3.7 specific features being used
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -90,15 +80,6 @@ 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 ]
@@ -115,20 +96,10 @@ 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 ]
# come Nov 30 2020 ubuntu-latest will be ubuntu-20.04, until then it needs to be explicitly referenced due to python 3.7 specific features being used
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -149,13 +120,4 @@ jobs:
path: snapshot
- name: Compare Anchore inline-scan results against snapshot build output
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() }}
run: make compare-snapshot
-71
View File
@@ -1,71 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL Security Scan"
on:
push:
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [main]
schedule:
- cron: '0 0 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['go', 'python']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+12 -32
View File
@@ -13,7 +13,7 @@ env:
jobs:
wait-for-checks:
runs-on: ubuntu-latest # This OS choice is arbitrary. None of the steps in this job are specific to either Linux or macOS.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -25,22 +25,13 @@ 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 analysis results
- name: Check static anaylysis, unit, and integration test results
uses: fountainhead/action-wait-for-check@v1.0.0
id: static-analysis
id: sa-unit-int
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the circle-ci workflow name (in .circleci/config.yaml)
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)"
checkName: "Static Analysis + Unit + Integration"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (linux)
@@ -71,10 +62,9 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Quality gate
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'
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'
run: |
echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}"
echo "Unit & Integration Test Status: ${{ steps.unit-integration.outputs.conclusion }}"
echo "Static/Unit/Integration Status: ${{ steps.sa-unit-int.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 }}"
@@ -82,7 +72,7 @@ jobs:
release:
needs: [ wait-for-checks ]
runs-on: macos-latest # Due to our code signing process, it's vital that we run our release steps on macOS.
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2
@@ -93,7 +83,6 @@ jobs:
with:
fetch-depth: 0
# We are expecting this cache to have been created during the "Build-Snapshot-Artifacts" job in the "Acceptance" workflow.
- name: Restore bootstrap cache
id: cache
uses: actions/cache@v2
@@ -106,6 +95,10 @@ jobs:
${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}-
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: Bootstrap dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: make ci-bootstrap
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v2
@@ -129,21 +122,8 @@ jobs:
SIGNING_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
APPLE_DEVELOPER_ID_CERT: ${{ secrets.APPLE_DEVELOPER_ID_CERT }} # Used during macOS code signing.
APPLE_DEVELOPER_ID_CERT_PASS: ${{ secrets.APPLE_DEVELOPER_ID_CERT_PASS }} # Used during macOS code signing.
AC_USERNAME: ${{ secrets.ENG_CI_APPLE_ID }} # Used during macOS notarization.
AC_PASSWORD: ${{ secrets.ENG_CI_APPLE_ID_PASS }} # Used during macOS notarization.
- 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
path: dist/**/*
path: dist/**/*
@@ -1,104 +0,0 @@
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
-3
View File
@@ -29,6 +29,3 @@ coverage.txt
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# macOS Finder metadata
.DS_STORE
+1 -1
View File
@@ -31,7 +31,6 @@ linters:
- misspell
- nakedret
- nolintlint
- prealloc
- rowserrcheck
- scopelint
- staticcheck
@@ -55,5 +54,6 @@ linters:
# - lll # without a way to specify per-line exception cases, this is not usable
# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations
# - nestif
# - prealloc # this unnecessarily requires preallocations for slices
# - testpackage
# - wsl
+12 -48
View File
@@ -1,38 +1,23 @@
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: auto
prerelease: false
# If set to true, will not auto-publish the release. This is done to allow us to review the changelog before publishing.
draft: true
signs:
- artifacts: checksum
args: ["--output", "${signature}", "--detach-sign", "${artifact}"]
builds:
- binary: syft
id: syft
env:
- CGO_ENABLED=0
goos:
# windows not supported yet (due to jotframe)
# - windows
- linux
goarch:
- amd64
# Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build)
mod_timestamp: '{{ .CommitTimestamp }}'
ldflags: |
-w
-s
-extldflags '-static'
-X github.com/anchore/syft/internal/version.version={{.Version}}
-X github.com/anchore/syft/internal/version.gitCommit={{.Commit}}
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
-X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}}
# For more info on this macOS build, see: https://github.com/mitchellh/gon#usage-with-goreleaser
- binary: syft
id: syft-macos
env:
- CGO_ENABLED=0
goos:
- darwin
goarch:
- amd64
@@ -47,34 +32,6 @@ builds:
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
-X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}}
archives:
- format: tar.gz
builds:
- syft # i.e. Linux only
- format: zip # This is a hack! We don't actually intend to use _this_ ZIP file, we just need goreleaser to consider the ZIP file produced by gon (which will have the same file name) to be an artifact so we can use it downstream in publishing (e.g. to a homebrew tap)
id: syft-zip
builds:
- syft-macos
signs:
- artifacts: checksum
cmd: sh
args:
- '-c'
# we should not include the zip artifact, as the artifact is mutated throughout the next macOS notarization step
# note: sed -i is not portable
- 'sed "/.*\.zip/d" ${artifact} > tmpfile && mv tmpfile ${artifact} && gpg --output ${signature} --detach-sign ${artifact}'
- id: syft-macos-signing
ids:
- syft-macos
cmd: ./.github/scripts/mac-sign-and-notarize.sh
signature: "syft_${VERSION}_darwin_amd64.dmg" # This is somewhat unintuitive. This gets the DMG file recognized as an artifact. In fact, both a DMG and a ZIP file are being produced by this signing step.
args:
- "{{ .IsSnapshot }}"
- "gon.hcl"
- "./dist/syft_{{ .Version }}_darwin_amd64"
artifacts: all
nfpms:
- license: "Apache 2.0"
maintainer: "Anchore, Inc"
@@ -90,3 +47,10 @@ brews:
name: homebrew-syft
homepage: *website
description: *description
archives:
- format: tar.gz
format_overrides:
- goos: windows
format: zip
+33 -58
View File
@@ -27,10 +27,6 @@ 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
@@ -71,13 +67,9 @@ 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:
ci-bootstrap: bootstrap
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y bc jq libxml2-utils
.PHONY:
ci-bootstrap-mac:
github_changelog_generator --version || sudo gem install github_changelog_generator
.PHONY: bootstrap
bootstrap: ## Download and install all go dependencies (+ prep tooling in the ./tmp dir)
$(call title,Bootstrapping dependencies)
@@ -126,7 +118,7 @@ validate-cyclonedx-schema:
.PHONY: unit
unit: fixtures ## Run unit tests (with coverage)
$(call title,Running unit tests)
go test -coverprofile $(COVER_REPORT) $(shell go list ./... | grep -v anchore/syft/test)
go test -coverprofile $(COVER_REPORT) ./...
@go tool cover -func $(COVER_REPORT) | grep total | awk '{print substr($$3, 1, length($$3)-1)}' > $(COVER_TOTAL)
@echo "Coverage: $$(cat $(COVER_TOTAL))"
@if [ $$(echo "$$(cat $(COVER_TOTAL)) >= $(COVERAGE_THRESHOLD)" | bc -l) -ne 1 ]; then echo "$(RED)$(BOLD)Failed coverage quality gate (> $(COVERAGE_THRESHOLD)%)$(RESET)" && false; fi
@@ -134,13 +126,11 @@ unit: fixtures ## Run unit tests (with coverage)
.PHONY: integration
integration: ## Run integration tests
$(call title,Running integration tests)
go test -v ./test/integration
go test -v -tags=integration ./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/cache.fingerprint
find test/integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee test/integration/test-fixtures/tar-cache.fingerprint
.PHONY: java-packages-fingerprint
java-packages-fingerprint:
@@ -167,7 +157,20 @@ 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/cache/*.tar" -delete
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'
proto:
protoc -I syft/plugin/proto/ syft/plugin/proto/cataloger.proto --go_out=plugins=grpc:syft/plugin/proto/
.PHONY: build
build: $(SNAPSHOTDIR) ## Build release snapshot binaries and packages
@@ -226,60 +229,32 @@ acceptance-test-rpm-package-install: $(SNAPSHOTDIR)
$(ACC_TEST_IMAGE) \
$(RESULTSDIR)
.PHONY: changlog-release
changelog-release:
@echo "Last tag: $(SECOND_TO_LAST_TAG)"
@echo "Current tag: $(VERSION)"
@github_changelog_generator \
.PHONY: changlog
changelog:
mkdir -p $(DISTDIR)
@docker run -it --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} \
--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
--unreleased-only \
--future-release $(VERSION)
.PHONY: release
release: clean-dist ci-bootstrap-mac changelog-release ## Build and publish final binaries and packages. Intended to be run only on macOS.
release: clean-dist changelog ## Build and publish final binaries and packages
$(call title,Publishing release artifacts)
# Prepare for macOS-specific signing process
.github/scripts/mac-prepare-for-signing.sh
# create a config with the dist dir overridden
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
# release (note the version transformation from v0.7.0 --> 0.7.0)
bash -c "\
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
VERSION=$(VERSION:v%=%) \
$(TEMPDIR)/goreleaser \
--rm-dist \
--config $(TEMPDIR)/goreleaser.yaml \
--release-notes <(cat CHANGELOG.md)"
# release
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
$(TEMPDIR)/goreleaser \
--rm-dist \
--config $(TEMPDIR)/goreleaser.yaml \
--release-notes <(cat CHANGELOG.md)
# verify checksum signatures
.github/scripts/verify-signature.sh "$(DISTDIR)"
@@ -301,4 +276,4 @@ clean-dist:
.PHONY: clean-json-schema-examples
clean-json-schema-examples:
rm -f schema/json/examples/*
rm -f schema/json/examples/*
+18 -19
View File
@@ -1,7 +1,6 @@
# syft
[![Static Analysis + Unit + Integration](https://github.com/anchore/syft/workflows/Static%20Analysis%20+%20Unit%20+%20Integration/badge.svg)](https://github.com/anchore/syft/actions?query=workflow%3A%22Static+Analysis+%2B+Unit+%2B+Integration%22)
[![Acceptance](https://github.com/anchore/syft/workflows/Acceptance/badge.svg)](https://github.com/anchore/syft/actions?query=workflow%3AAcceptance)
[![CircleCI](https://circleci.com/gh/anchore/syft.svg?style=svg)](https://circleci.com/gh/anchore/syft)
[![Go Report Card](https://goreportcard.com/badge/github.com/anchore/syft)](https://goreportcard.com/report/github.com/anchore/syft)
[![GitHub release](https://img.shields.io/github/release/anchore/syft.svg)](https://github.com/anchore/syft/releases/latest)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/anchore/syft/blob/main/LICENSE)
@@ -14,13 +13,12 @@ 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
If you encounter an issue, please [let us know using the issue tracker](https://github.com/anchore/syft/issues).
> :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).
## Getting started
To generate an SBOM for a Docker or OCI image:
To generate an SBOM for an image:
```
syft <image>
```
@@ -34,41 +32,41 @@ syft <image> --scope all-layers
Syft can generate a SBOM from a variety of sources:
```
# 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 docker image tar (from the result of "docker image save ... -o image.tar" command)
syft docker-archive://path/to/image.tar
# catalog a directory
syft path/to/dir
syft dir://path/to/dir
```
The output format for Syft is configurable as well:
By default Syft shows a summary table, however, more detailed `text` and `json` formats are also available.
```
syft <image> -o <format>
syft <image> -o json
syft <image> -o text
```
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 (macOS and Linux)**
**Recommended**
```bash
# install the latest version to /usr/local/bin
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 -- -b <SOME_BIN_PATH> <RELEASE_VERSION>
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s <RELEASE_VERSION> -b <SOME_BIN_PATH>
```
**Homebrew (macOS)**
**macOS**
```bash
brew tap anchore/syft
brew install syft
```
You may experience a "macOS cannot verify app is free from malware" error upon running Syft because it is not yet signed and notarized. You can override this using `xattr`.
```bash
xattr -rd com.apple.quarantine syft
```
## Configuration
Configuration search paths:
@@ -107,4 +105,5 @@ check-for-app-update: true
## Future plans
The following areas of potential development are currently being investigated:
- Add CycloneDX to list of output formats
- Establish a stable interchange format w/Grype
+20 -5
View File
@@ -3,16 +3,19 @@ package cmd
import (
"fmt"
"os"
"path"
"github.com/gookit/color"
"github.com/adrg/xdg"
"github.com/anchore/syft/internal"
"github.com/spf13/cobra"
"github.com/anchore/syft/syft/presenter"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/scope"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/format"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/logger"
"github.com/anchore/syft/syft"
@@ -50,8 +53,8 @@ func setGlobalCliOptions() {
// scan options
flag := "scope"
rootCmd.Flags().StringP(
"scope", "s", source.SquashedScope.String(),
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))
"scope", "s", scope.SquashedScope.String(),
fmt.Sprintf("selection of layers to catalog, options=%v", scope.Options))
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
@@ -79,6 +82,18 @@ func setGlobalCliOptions() {
}
rootCmd.Flags().CountVarP(&cliOpts.Verbosity, "verbose", "v", "increase verbosity (-v = info, -vv = debug)")
// plugins
defaultPluginDir := path.Join(xdg.CacheHome, internal.ApplicationName, "plugins")
flag = "plugins.dir"
rootCmd.Flags().String(
flag, defaultPluginDir,
"plugin storage directory",
)
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
}
}
func initAppConfig() {
@@ -112,7 +127,7 @@ func logAppConfig() {
if err != nil {
log.Debugf("Could not display application config: %+v", err)
} else {
log.Debugf("Application config:\n%+v", color.Magenta.Sprint(string(appCfgStr)))
log.Debugf("Application config:\n%+v", format.Magenta.Format(string(appCfgStr)))
}
}
-65
View File
@@ -1,65 +0,0 @@
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)
}
+16 -57
View File
@@ -1,10 +1,8 @@
package cmd
import (
"context"
"fmt"
"os"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
@@ -14,9 +12,6 @@ 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"
)
@@ -26,15 +21,10 @@ 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}} 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)
{{.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
`, map[string]interface{}{
"appName": internal.ApplicationName,
}),
@@ -54,20 +44,6 @@ You can also explicitly specify the scheme to use:
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 {
@@ -92,7 +68,17 @@ func startWorker(userInput string) <-chan error {
}
}
src, catalog, distro, err := syft.Catalog(userInput, appConfig.ScopeOpt)
config := syft.Config{
PluginDirectory: appConfig.Plugins.Directory,
}
client, err := syft.NewClient(config)
if err != nil {
errs <- fmt.Errorf("could not initialize syft: %+v", err)
return
}
catalog, scope, distro, err := client.Catalog(userInput, appConfig.ScopeOpt)
if err != nil {
errs <- fmt.Errorf("failed to catalog input: %+v", err)
return
@@ -100,7 +86,7 @@ func startWorker(userInput string) <-chan error {
bus.Publish(partybus.Event{
Type: event.CatalogerFinished,
Value: presenter.GetPresenter(appConfig.PresenterOpt, src.Metadata, catalog, distro),
Value: presenter.GetPresenter(appConfig.PresenterOpt, *scope, catalog, distro),
})
}()
return errs
@@ -112,30 +98,3 @@ 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
}
+6 -28
View File
@@ -1,18 +1,14 @@
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 outputFormat string
var showVerboseVersionInfo bool
var versionCmd = &cobra.Command{
Use: "version",
@@ -21,15 +17,14 @@ var versionCmd = &cobra.Command{
}
func init() {
versionCmd.Flags().StringVarP(&outputFormat, "output", "o", string(presenter.TextPresenter), "format to show version information (available=[text, json])")
versionCmd.Flags().BoolVarP(&showVerboseVersionInfo, "verbose", "v", false, "show additional version information")
rootCmd.AddCommand(versionCmd)
}
func printVersion(_ *cobra.Command, _ []string) {
versionInfo := version.FromBuild()
switch outputFormat {
case "text":
if showVerboseVersionInfo {
fmt.Println("Application: ", internal.ApplicationName)
fmt.Println("Version: ", versionInfo.Version)
fmt.Println("BuildDate: ", versionInfo.BuildDate)
@@ -38,24 +33,7 @@ func printVersion(_ *cobra.Command, _ []string) {
fmt.Println("Platform: ", versionInfo.Platform)
fmt.Println("GoVersion: ", versionInfo.GoVersion)
fmt.Println("Compiler: ", versionInfo.Compiler)
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)
} else {
fmt.Printf("%s %s\n", internal.ApplicationName, versionInfo.Version)
}
}
+19 -9
View File
@@ -4,27 +4,32 @@ go 1.14
require (
github.com/adrg/xdg v0.2.1
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/stereoscope v0.0.0-20201106140100-12e75c48f409
github.com/bmatcuk/doublestar v1.3.3
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
github.com/anchore/stereoscope v0.0.0-20200903223322-a3806f515470
github.com/bmatcuk/doublestar v1.3.1
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect
github.com/dustin/go-humanize v1.0.0
github.com/go-test/deep v1.0.7
github.com/go-test/deep v1.0.6
github.com/golang/protobuf v1.4.2
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-hclog v0.14.1
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-plugin v1.3.0
github.com/hashicorp/go-version v1.2.0
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.3.1
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/afero v1.2.2
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905
github.com/spf13/cobra v1.0.0
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
@@ -32,6 +37,11 @@ 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/mod v0.3.0
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
google.golang.org/grpc v1.31.1
gopkg.in/ini.v1 v1.57.0 // indirect
gopkg.in/yaml.v2 v2.3.0
)
+64 -30
View File
@@ -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.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/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/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=
@@ -124,14 +124,15 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12 h1:xbeIbn5F52JVx3RUIajxCj8b0y+9lywspql4sFhcxWQ=
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12/go.mod h1:juoyWXIj7sJ1IDl4E/KIfyLtovbs5XQVSIdaQifFQT8=
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-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-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-20201106140100-12e75c48f409 h1:xKSpDRjmYrEFrdMeDh4AuSUAFc99pdro6YFBKxy2um0=
github.com/anchore/stereoscope v0.0.0-20201106140100-12e75c48f409/go.mod h1:2Jja/4l0zYggW52og+nn0rut4i+OYjCf9vTyrM8RT4E=
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g=
github.com/anchore/stereoscope v0.0.0-20200903223322-a3806f515470 h1:apXyPyfiaL/YQF7VeXRbDDB9TA7dA9rxdq8YbzPk9ko=
github.com/anchore/stereoscope v0.0.0-20200903223322-a3806f515470/go.mod h1:WntReQTI/I27FOQ87UgLVVzWgku6+ZsqfOTLxpIZFCs=
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=
@@ -160,8 +161,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmatcuk/doublestar v1.3.3 h1:pVP1d49CcQQaNOl+PI6sPybIrIOD/6sux31PFdmhTH0=
github.com/bmatcuk/doublestar v1.3.3/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmatcuk/doublestar v1.3.1 h1:rT8rxDPsavp9G+4ZULzqhhUSaI/OPsTZNG88Z3i0xvY=
github.com/bmatcuk/doublestar v1.3.1/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U=
github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg=
github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
@@ -180,11 +181,10 @@ 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-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
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,6 +193,7 @@ 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=
@@ -208,12 +209,10 @@ 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=
@@ -295,8 +294,8 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
@@ -322,7 +321,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/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
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=
@@ -400,8 +399,11 @@ 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=
@@ -442,8 +444,9 @@ 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=
@@ -471,12 +474,18 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-plugin v1.3.0 h1:4d/wJojzvHV1I4i/rrjVaeuyxWrLzDE1mDCyDy8fXS8=
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
@@ -497,6 +506,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -507,6 +518,8 @@ github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk=
github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s=
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
@@ -586,6 +599,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
@@ -608,6 +622,8 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@@ -636,6 +652,8 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
@@ -664,7 +682,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 v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/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=
@@ -737,6 +755,7 @@ 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=
@@ -762,8 +781,6 @@ 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=
@@ -786,7 +803,6 @@ 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=
@@ -819,8 +835,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=
@@ -922,6 +938,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -958,10 +975,11 @@ 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=
@@ -978,6 +996,8 @@ 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=
@@ -996,6 +1016,7 @@ 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=
@@ -1007,7 +1028,8 @@ 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-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/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=
@@ -1015,7 +1037,6 @@ 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=
@@ -1024,11 +1045,14 @@ 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-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
@@ -1091,6 +1115,7 @@ 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=
@@ -1140,6 +1165,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1166,17 +1192,21 @@ 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.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
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=
@@ -1184,6 +1214,8 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1212,6 +1244,8 @@ 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=
-15
View File
@@ -1,15 +0,0 @@
source = ["./dist/syft-macos_darwin_amd64/syft"] # The 'dist' directory path should ideally reference an env var, where the source of truth is the Makefile. I wasn't able to figure out how to solve this.
bundle_id = "com.anchore.toolbox.syft"
sign {
application_identity = "Developer ID Application: ANCHORE, INC. (9MJHKYX5AT)"
}
dmg {
output_path = "./dist/output.dmg"
volume_name = "Syft"
}
zip {
output_path = "./dist/output.zip"
}
+13 -27
View File
@@ -1,6 +1,6 @@
#!/bin/sh
set -e
# Code generated by godownloader on 2020-08-10T20:55:46Z.
# Code generated by godownloader on 2020-08-10T20:55:46Z. DO NOT EDIT.
#
usage() {
@@ -45,16 +45,11 @@ parse_args() {
execute() {
tmpdir=$(mktemp -d)
log_debug "downloading files into ${tmpdir}"
http_download "${tmpdir}/${ARCHIVE}" "${ARCHIVE_URL}"
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
# macOS has its own secure verification mechanism, and checksums.txt is not used.
if [ "$OS" != "darwin" ]; then
hash_sha256_verify "${tmpdir}/${ARCHIVE}" "${tmpdir}/${CHECKSUM}"
fi
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
srcdir="${tmpdir}"
(cd "${tmpdir}" && unpack "${ARCHIVE}")
(cd "${tmpdir}" && untar "${TARBALL}")
test ! -d "${BINDIR}" && install -d "${BINDIR}"
for binexe in $BINARIES; do
if [ "$OS" = "windows" ]; then
@@ -94,7 +89,6 @@ tag_to_version() {
adjust_format() {
# change format (tar.gz or zip) based on OS
case ${OS} in
darwin) FORMAT=dmg ;;
windows) FORMAT=zip ;;
esac
true
@@ -227,26 +221,18 @@ uname_arch_check() {
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
return 1
}
unpack() {
archive=$1
case "${archive}" in
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${archive}" ;;
*.tar) tar --no-same-owner -xf "${archive}" ;;
*.zip) unzip "${archive}" ;;
*.dmg) extract_from_dmg "${archive}" ;;
untar() {
tarball=$1
case "${tarball}" in
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
*.tar) tar --no-same-owner -xf "${tarball}" ;;
*.zip) unzip "${tarball}" ;;
*)
log_err "unpack unknown archive format for ${archive}"
log_err "untar unknown archive format for ${tarball}"
return 1
;;
esac
}
extract_from_dmg() {
dmg_file=$1
mount_point="/Volumes/tmp-dmg"
hdiutil attach -quiet -nobrowse -mountpoint "${mount_point}" "${dmg_file}"
cp -fR "${mount_point}/" ./
hdiutil detach -quiet -force "${mount_point}"
}
http_download_curl() {
local_file=$1
source_url=$2
@@ -380,8 +366,8 @@ adjust_arch
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
NAME=${PROJECT_NAME}_${VERSION}_${OS}_${ARCH}
ARCHIVE=${NAME}.${FORMAT}
ARCHIVE_URL=${GITHUB_DOWNLOAD}/${TAG}/${ARCHIVE}
TARBALL=${NAME}.${FORMAT}
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
CHECKSUM=${PROJECT_NAME}_${VERSION}_checksums.txt
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
+13 -7
View File
@@ -8,7 +8,7 @@ import (
"github.com/adrg/xdg"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/presenter"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/scope"
"github.com/mitchellh/go-homedir"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
@@ -23,12 +23,18 @@ type Application struct {
ConfigPath string
PresenterOpt presenter.Option
Output string `mapstructure:"output"`
ScopeOpt source.Scope
ScopeOpt scope.Option
Scope string `mapstructure:"scope"`
Quiet bool `mapstructure:"quiet"`
Log Logging `mapstructure:"log"`
CliOptions CliOnlyOptions
CheckForAppUpdate bool `mapstructure:"check-for-app-update"`
CheckForAppUpdate bool `mapstructure:"check-for-app-update"`
Plugins Plugins `mapstructure:"plugins"`
}
type Plugins struct {
Enabled bool `mapstructure:"enabled"`
Directory string `mapstructure:"dir"`
}
type Logging struct {
@@ -79,9 +85,9 @@ func (cfg *Application) Build() error {
}
cfg.PresenterOpt = presenterOption
// set the source
scopeOption := source.ParseScope(cfg.Scope)
if scopeOption == source.UnknownScope {
// set the scope
scopeOption := scope.ParseOption(cfg.Scope)
if scopeOption == scope.UnknownScope {
return fmt.Errorf("bad --scope value '%s'", cfg.Scope)
}
cfg.ScopeOpt = scopeOption
@@ -111,7 +117,7 @@ func (cfg *Application) Build() error {
case v >= 2:
cfg.Log.LevelOpt = logrus.DebugLevel
default:
cfg.Log.LevelOpt = logrus.WarnLevel
cfg.Log.LevelOpt = logrus.ErrorLevel
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
package internal
// ApplicationName is the non-capitalized name of the application (do not change this)
// note: do not change this
const ApplicationName = "syft"
-4
View File
@@ -1,4 +0,0 @@
/*
Package internal contains miscellaneous functions and objects useful within syft but should not be used externally.
*/
package internal
-1
View File
@@ -1,6 +1,5 @@
package file
// GlobMatch evaluates the given glob pattern against the given "name" string, indicating if there is a match or not.
// Source: https://research.swtch.com/glob.go
func GlobMatch(pattern, name string) bool {
px := 0
-16
View File
@@ -1,16 +0,0 @@
package file
import (
"io"
"os"
)
// Opener is an object that stores a path to later be opened as a file.
type Opener struct {
path string
}
// Open the stored path as a io.ReadCloser.
func (o Opener) Open() (io.ReadCloser, error) {
return os.Open(o.path)
}
@@ -1,7 +1,4 @@
#!/usr/bin/env bash
set -eux
# $1 —— absolute path to destination file, should end with .zip, ideally
# $2 —— absolute path to directory from which to add entries to the archive
pushd "$2" && find . -print | zip "$1" -@ && popd
zip -r "$1" zip-source
-131
View File
@@ -1,131 +0,0 @@
package file
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"syscall"
"testing"
)
var expectedZipArchiveEntries = []string{
"some-dir" + string(os.PathSeparator),
filepath.Join("some-dir", "a-file.txt"),
"b-file.txt",
"nested.zip",
}
// fatalIfError calls the supplied function. If the function returns a non-nil error, t.Fatal(err) is called.
func fatalIfError(t *testing.T, fn func() error) {
t.Helper()
if fn == nil {
return
}
err := fn()
if err != nil {
t.Fatal(err)
}
}
// createZipArchive creates a new ZIP archive file at destinationArchivePath based on the directory found at
// sourceDirPath.
func createZipArchive(t *testing.T, sourceDirPath, destinationArchivePath string) error {
t.Helper()
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("unable to get cwd: %+v", err)
}
cmd := exec.Command("./generate-zip-fixture.sh", destinationArchivePath, path.Base(sourceDirPath))
cmd.Dir = filepath.Join(cwd, "test-fixtures")
if err := cmd.Start(); err != nil {
return fmt.Errorf("unable to start generate zip fixture script: %+v", err)
}
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() != 0 {
return fmt.Errorf("failed to generate fixture: rc=%d", status.ExitStatus())
}
}
} else {
return fmt.Errorf("unable to get generate fixture script result: %+v", err)
}
}
return nil
}
// setupZipFileTest encapsulates common test setup work for zip file tests. It returns a cleanup function,
// which should be called (typically deferred) by the caller, the path of the created zip archive, and an error,
// which should trigger a fatal test failure in the consuming test. The returned cleanup function will never be nil
// (even if there's an error), and it should always be called.
func setupZipFileTest(t *testing.T, sourceDirPath string) (func() error, string, error) {
t.Helper()
// Keep track of any needed cleanup work as we go
var cleanupFns []func() error
cleanup := func(fns []func() error) func() error {
return func() error {
for _, fn := range fns {
err := fn()
if err != nil {
return err
}
}
return nil
}
}
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
if err != nil {
return cleanup(cleanupFns), "", fmt.Errorf("unable to create tempfile: %+v", err)
}
cleanupFns = append(cleanupFns, func() error { return os.Remove(archivePrefix.Name()) })
destinationArchiveFilePath := archivePrefix.Name() + ".zip"
t.Logf("archive path: %s", destinationArchiveFilePath)
err = createZipArchive(t, sourceDirPath, destinationArchiveFilePath)
cleanupFns = append(cleanupFns, func() error { return os.Remove(destinationArchiveFilePath) })
if err != nil {
return cleanup(cleanupFns), "", err
}
cwd, err := os.Getwd()
if err != nil {
return cleanup(cleanupFns), "", fmt.Errorf("unable to get cwd: %+v", err)
}
t.Logf("running from: %s", cwd)
return cleanup(cleanupFns), destinationArchiveFilePath, nil
}
// TODO: Consider moving any non-git asset generation to a task (e.g. make) that's run ahead of running go tests.
func ensureNestedZipExists(t *testing.T, sourceDirPath string) error {
t.Helper()
nestedArchiveFilePath := path.Join(sourceDirPath, "nested.zip")
err := createZipArchive(t, sourceDirPath, nestedArchiveFilePath)
if err != nil {
return fmt.Errorf("unable to create nested archive for test fixture: %+v", err)
}
return nil
}
+2 -21
View File
@@ -5,37 +5,28 @@ import (
"fmt"
"os"
"sort"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
)
// ZipFileManifest is a collection of paths and their file metadata.
type ZipFileManifest map[string]os.FileInfo
// newZipManifest creates an empty ZipFileManifest.
func newZipManifest() ZipFileManifest {
return make(ZipFileManifest)
}
// Add a new path and it's file metadata to the collection.
func (z ZipFileManifest) Add(entry string, info os.FileInfo) {
z[entry] = info
}
// GlobMatch returns the path keys that match the given value(s).
func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
uniqueMatches := internal.NewStringSet()
for _, pattern := range patterns {
for entry := range z {
// We want to match globs as if entries begin with a leading slash (akin to an absolute path)
// so that glob logic is consistent inside and outside of ZIP archives
normalizedEntry := normalizeZipEntryName(entry)
if GlobMatch(pattern, normalizedEntry) {
if GlobMatch(pattern, entry) {
uniqueMatches.Add(entry)
}
}
@@ -47,7 +38,6 @@ func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
return results
}
// NewZipFileManifest creates and returns a new ZipFileManifest populated with path and metadata from the given zip archive path.
func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
zipReader, err := zip.OpenReader(archivePath)
manifest := newZipManifest()
@@ -57,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): %+v", archivePath, err)
log.Errorf("unable to close zip archive (%s): %w", archivePath, err)
}
}()
@@ -66,12 +56,3 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
}
return manifest, nil
}
// normalizeZipEntryName takes the given path entry and ensures it is prefixed with "/".
func normalizeZipEntryName(entry string) string {
if !strings.HasPrefix(entry, "/") {
return "/" + entry
}
return entry
}
-109
View File
@@ -1,109 +0,0 @@
package file
import (
"encoding/json"
"os"
"path"
"testing"
)
func TestNewZipFileManifest(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
sourceDirPath := path.Join(cwd, "test-fixtures", "zip-source")
err = ensureNestedZipExists(t, sourceDirPath)
if err != nil {
t.Fatal(err)
}
cleanup, archiveFilePath, err := setupZipFileTest(t, sourceDirPath)
defer fatalIfError(t, cleanup)
if err != nil {
t.Fatal(err)
}
actual, err := NewZipFileManifest(archiveFilePath)
if err != nil {
t.Fatalf("unable to extract from unzip archive: %+v", err)
}
if len(expectedZipArchiveEntries) != len(actual) {
t.Fatalf("mismatched manifest: %d != %d", len(actual), len(expectedZipArchiveEntries))
}
for _, e := range expectedZipArchiveEntries {
_, ok := actual[e]
if !ok {
t.Errorf("missing path: %s", e)
}
}
if t.Failed() {
b, err := json.MarshalIndent(actual, "", " ")
if err != nil {
t.Fatalf("can't show results: %+v", err)
}
t.Errorf("full result: %s", string(b))
}
}
func TestZipFileManifest_GlobMatch(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
sourceDirPath := path.Join(cwd, "test-fixtures", "zip-source")
err = ensureNestedZipExists(t, sourceDirPath)
if err != nil {
t.Fatal(err)
}
cleanup, archiveFilePath, err := setupZipFileTest(t, sourceDirPath)
//goland:noinspection GoNilness
defer fatalIfError(t, cleanup)
if err != nil {
t.Fatal(err)
}
z, err := NewZipFileManifest(archiveFilePath)
if err != nil {
t.Fatalf("unable to extract from unzip archive: %+v", err)
}
cases := []struct {
glob string
expected string
}{
{
"/b*",
"b-file.txt",
},
{
"*/a-file.txt",
"some-dir/a-file.txt",
},
{
"**/*.zip",
"nested.zip",
},
}
for _, tc := range cases {
t.Run(tc.glob, func(t *testing.T) {
glob := tc.glob
results := z.GlobMatch(glob)
if len(results) == 1 && results[0] == tc.expected {
return
}
t.Errorf("unexpected results for glob '%s': %+v", glob, results)
})
}
}
+8 -19
View File
@@ -15,7 +15,6 @@ import (
)
const (
// represents the order of bytes
_ = iota
KB = 1 << (10 * iota)
MB
@@ -34,7 +33,6 @@ func newZipTraverseRequest(paths ...string) zipTraversalRequest {
return results
}
// TraverseFilesInZip enumerates all paths stored within a zip archive using the visitor pattern.
func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths ...string) error {
request := newZipTraverseRequest(paths...)
@@ -45,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): %+v", archivePath, err)
log.Errorf("unable to close zip archive (%s): %w", archivePath, err)
}
}()
@@ -65,9 +63,8 @@ func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths
return nil
}
// ExtractFromZipToUniqueTempFile extracts select paths for the given archive to a temporary directory, returning file openers for each file extracted.
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]Opener, error) {
results := make(map[string]Opener)
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]io.Reader, error) {
results := make(map[string]io.Reader)
// don't allow for full traversal, only select traversal from given paths
if len(paths) == 0 {
@@ -81,21 +78,11 @@ func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (m
if err != nil {
return fmt.Errorf("unable to create temp file: %w", err)
}
// we shouldn't try and keep the tempfile open as the returned result may have several files, which takes up
// resources (leading to "too many open files"). Instead we'll return a file opener to the caller which
// provides a ReadCloser. It is up to the caller to handle closing the file explicitly.
defer tempFile.Close()
zippedFile, err := file.Open()
if err != nil {
return fmt.Errorf("unable to read file=%q from zip=%q: %w", file.Name, archivePath, err)
}
defer func() {
err := zippedFile.Close()
if err != nil {
log.Errorf("unable to close source file=%q from zip=%q: %+v", file.Name, archivePath, err)
}
}()
if file.FileInfo().IsDir() {
return fmt.Errorf("unable to extract directories, only files: %s", file.Name)
@@ -116,15 +103,18 @@ func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (m
return fmt.Errorf("unable to reset file pointer (%s): %w", tempFile.Name(), err)
}
results[file.Name] = Opener{path: tempFile.Name()}
results[file.Name] = tempFile
err = zippedFile.Close()
if err != nil {
return fmt.Errorf("unable to close source file=%q from zip=%q: %w", file.Name, archivePath, err)
}
return nil
}
return results, TraverseFilesInZip(archivePath, visitor, paths...)
}
// ContentsFromZip extracts select paths for the given archive and returns a set of string contents for each path.
func ContentsFromZip(archivePath string, paths ...string) (map[string]string, error) {
results := make(map[string]string)
@@ -166,7 +156,6 @@ func ContentsFromZip(archivePath string, paths ...string) (map[string]string, er
return results, TraverseFilesInZip(archivePath, visitor, paths...)
}
// UnzipToDir extracts a zip archive to a target directory.
func UnzipToDir(archivePath, targetDir string) error {
visitor := func(file *zip.File) error {
// the zip-slip attack protection is still being erroneously detected
+122 -31
View File
@@ -6,14 +6,47 @@ import (
"io"
"io/ioutil"
"os"
"path"
"os/exec"
"path/filepath"
"strings"
"syscall"
"testing"
"github.com/go-test/deep"
)
func generateFixture(t *testing.T, archivePath string) {
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
cmd := exec.Command("./generate-zip-fixture.sh", archivePath)
cmd.Dir = filepath.Join(cwd, "test-fixtures")
if err := cmd.Start(); err != nil {
t.Fatalf("unable to start generate zip fixture script: %+v", err)
}
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() != 0 {
t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus())
}
}
} else {
t.Fatalf("unable to get generate fixture script result: %+v", err)
}
}
}
func equal(r1, r2 io.Reader) (bool, error) {
w1 := sha256.New()
w2 := sha256.New()
@@ -34,50 +67,54 @@ func equal(r1, r2 io.Reader) (bool, error) {
}
func TestUnzipToDir(t *testing.T) {
cwd, err := os.Getwd()
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
if err != nil {
t.Fatal(err)
t.Fatalf("unable to create tempfile: %+v", err)
}
defer os.Remove(archivePrefix.Name())
// the zip utility will add ".zip" to the end of the given name
archivePath := archivePrefix.Name() + ".zip"
defer os.Remove(archivePath)
t.Logf("archive path: %s", archivePath)
goldenRootDir := filepath.Join(cwd, "test-fixtures")
sourceDirPath := path.Join(goldenRootDir, "zip-source")
cleanup, archiveFilePath, err := setupZipFileTest(t, sourceDirPath)
defer fatalIfError(t, cleanup)
if err != nil {
t.Fatal(err)
}
generateFixture(t, archivePrefix.Name())
unzipDestinationDir, err := ioutil.TempDir("", "syft-ziputil-contents-TEST-")
defer os.RemoveAll(unzipDestinationDir)
contentsDir, err := ioutil.TempDir("", "syft-ziputil-contents-TEST-")
if err != nil {
t.Fatalf("unable to create tempdir: %+v", err)
}
defer os.RemoveAll(contentsDir)
t.Logf("content path: %s", unzipDestinationDir)
t.Logf("content path: %s", contentsDir)
expectedPaths := len(expectedZipArchiveEntries)
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
t.Logf("running from: %s", cwd)
// note: zip utility already includes "zip-source" as a parent dir for all contained files
goldenRootDir := filepath.Join(cwd, "test-fixtures")
expectedPaths := 4
observedPaths := 0
err = UnzipToDir(archiveFilePath, unzipDestinationDir)
err = UnzipToDir(archivePath, contentsDir)
if err != nil {
t.Fatalf("unable to unzip archive: %+v", err)
}
// compare the source dir tree and the unzipped tree
err = filepath.Walk(unzipDestinationDir,
err = filepath.Walk(filepath.Join(contentsDir, "zip-source"),
func(path string, info os.FileInfo, err error) error {
// We don't unzip the root archive dir, since there's no archive entry for it
if path != unzipDestinationDir {
t.Logf("unzipped path: %s", path)
observedPaths++
}
t.Logf("unzipped path: %s", path)
observedPaths++
if err != nil {
t.Fatalf("this should not happen")
return err
}
goldenPath := filepath.Join(sourceDirPath, strings.TrimPrefix(path, unzipDestinationDir))
goldenPath := filepath.Join(goldenRootDir, strings.TrimPrefix(path, contentsDir))
if info.IsDir() {
i, err := os.Stat(goldenPath)
@@ -119,11 +156,12 @@ func TestUnzipToDir(t *testing.T) {
}
if observedPaths != expectedPaths {
t.Errorf("missed test paths: %d != %d", observedPaths, expectedPaths)
t.Errorf("missed test paths: %d!=%d", observedPaths, expectedPaths)
}
}
func TestContentsFromZip(t *testing.T) {
func TestExtractFilesFromZipFile(t *testing.T) {
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
if err != nil {
t.Fatalf("unable to create tempfile: %+v", err)
@@ -134,10 +172,7 @@ func TestContentsFromZip(t *testing.T) {
defer os.Remove(archivePath)
t.Logf("archive path: %s", archivePath)
err = createZipArchive(t, "zip-source", archivePrefix.Name())
if err != nil {
t.Fatal(err)
}
generateFixture(t, archivePrefix.Name())
cwd, err := os.Getwd()
if err != nil {
@@ -146,8 +181,8 @@ func TestContentsFromZip(t *testing.T) {
t.Logf("running from: %s", cwd)
aFilePath := filepath.Join("some-dir", "a-file.txt")
bFilePath := filepath.Join("b-file.txt")
aFilePath := filepath.Join("zip-source", "some-dir", "a-file.txt")
bFilePath := filepath.Join("zip-source", "b-file.txt")
expected := map[string]string{
aFilePath: "A file! nice!",
@@ -172,4 +207,60 @@ func TestContentsFromZip(t *testing.T) {
t.Errorf("full result: %s", string(b))
}
}
func TestZipFileManifest(t *testing.T) {
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
if err != nil {
t.Fatalf("unable to create tempfile: %+v", err)
}
defer os.Remove(archivePrefix.Name())
// the zip utility will add ".zip" to the end of the given name
archivePath := archivePrefix.Name() + ".zip"
defer os.Remove(archivePath)
t.Logf("archive path: %s", archivePath)
generateFixture(t, archivePrefix.Name())
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
t.Logf("running from: %s", cwd)
expected := []string{
filepath.Join("zip-source") + string(os.PathSeparator),
filepath.Join("zip-source", "some-dir") + string(os.PathSeparator),
filepath.Join("zip-source", "some-dir", "a-file.txt"),
filepath.Join("zip-source", "b-file.txt"),
}
actual, err := NewZipFileManifest(archivePath)
if err != nil {
t.Fatalf("unable to extract from unzip archive: %+v", err)
}
if len(expected) != len(actual) {
t.Fatalf("mismatched manifest: %d != %d", len(actual), len(expected))
}
for _, e := range expected {
_, ok := actual[e]
if !ok {
t.Errorf("missing path: %s", e)
}
}
if t.Failed() {
b, err := json.MarshalIndent(actual, "", " ")
if err != nil {
t.Fatalf("can't show results: %+v", err)
}
t.Errorf("full result: %s", string(b))
}
}
+21
View File
@@ -0,0 +1,21 @@
package format
import "fmt"
const (
DefaultColor Color = iota + 30
Red
Green
Yellow
Blue
Magenta
Cyan
White
)
type Color uint8
// TODO: not cross platform (windows...)
func (c Color) Format(s string) string {
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", c, s)
}
+3 -21
View File
@@ -1,49 +1,31 @@
/*
Package log contains the singleton object and helper functions for facilitating logging within the syft library.
*/
package log
import "github.com/anchore/syft/syft/logger"
import (
"github.com/anchore/syft/syft/logger"
)
// Log is the singleton used to facilitate logging internally within syft
var Log logger.Logger = &nopLogger{}
// Errorf takes a formatted template string and template arguments for the error logging level.
func Errorf(format string, args ...interface{}) {
Log.Errorf(format, args...)
}
// Error logs the given arguments at the error logging level.
func Error(args ...interface{}) {
Log.Error(args...)
}
// Warnf takes a formatted template string and template arguments for the warning logging level.
func Warnf(format string, args ...interface{}) {
Log.Warnf(format, args...)
}
// Warn logs the given arguments at the warning logging level.
func Warn(args ...interface{}) {
Log.Warn(args...)
}
// Infof takes a formatted template string and template arguments for the info logging level.
func Infof(format string, args ...interface{}) {
Log.Infof(format, args...)
}
// Info logs the given arguments at the info logging level.
func Info(args ...interface{}) {
Log.Info(args...)
}
// Debugf takes a formatted template string and template arguments for the debug logging level.
func Debugf(format string, args ...interface{}) {
Log.Debugf(format, args...)
}
// Debug logs the given arguments at the debug logging level.
func Debug(args ...interface{}) {
Log.Debug(args...)
}
-2
View File
@@ -4,8 +4,6 @@ type nopLogger struct{}
func (l *nopLogger) Errorf(format string, args ...interface{}) {}
func (l *nopLogger) Error(args ...interface{}) {}
func (l *nopLogger) Warnf(format string, args ...interface{}) {}
func (l *nopLogger) Warn(args ...interface{}) {}
func (l *nopLogger) Infof(format string, args ...interface{}) {}
func (l *nopLogger) Info(args ...interface{}) {}
func (l *nopLogger) Debugf(format string, args ...interface{}) {}
-4
View File
@@ -1,4 +0,0 @@
/*
Package logger contains implementations for the syft.logger.Logger interface.
*/
package logger
+78
View File
@@ -0,0 +1,78 @@
package logger
import (
"bytes"
"strings"
"github.com/hashicorp/go-hclog"
)
// Source: https://github.com/hashicorp/go-hclog/blob/master/stdlog.go (MIT License)
// Provides a io.Writer to shim the data out of *log.Logger
// and back into our Logger. This is basically the only way to
// build upon *log.Logger.
type HCLogStdlogAdapter struct {
log hclog.Logger
inferLevels bool
forceLevel hclog.Level
}
// Take the data, infer the levels if configured, and send it through
// a regular Logger.
func (s *HCLogStdlogAdapter) Write(data []byte) (int, error) {
str := string(bytes.TrimRight(data, " \t\n"))
if s.forceLevel != hclog.NoLevel {
// Use pickLevel to strip log levels included in the line since we are
// forcing the level
_, str := s.pickLevel(str)
// Log at the forced level
s.dispatch(str, s.forceLevel)
} else if s.inferLevels {
level, str := s.pickLevel(str)
s.dispatch(str, level)
} else {
s.log.Info(str)
}
return len(data), nil
}
func (s *HCLogStdlogAdapter) dispatch(str string, level hclog.Level) {
switch level {
case hclog.Trace:
s.log.Trace(str)
case hclog.Debug:
s.log.Debug(str)
case hclog.Info:
s.log.Info(str)
case hclog.Warn:
s.log.Warn(str)
case hclog.Error:
s.log.Error(str)
default:
s.log.Info(str)
}
}
// Detect, based on conventions, what log level this is.
func (s *HCLogStdlogAdapter) pickLevel(str string) (hclog.Level, string) {
switch {
case strings.HasPrefix(str, "[DEBUG]"):
return hclog.Debug, strings.TrimSpace(str[7:])
case strings.HasPrefix(str, "[TRACE]"):
return hclog.Trace, strings.TrimSpace(str[7:])
case strings.HasPrefix(str, "[INFO]"):
return hclog.Info, strings.TrimSpace(str[6:])
case strings.HasPrefix(str, "[WARN]"):
return hclog.Warn, strings.TrimSpace(str[7:])
case strings.HasPrefix(str, "[ERROR]"):
return hclog.Error, strings.TrimSpace(str[7:])
case strings.HasPrefix(str, "[ERR]"):
return hclog.Error, strings.TrimSpace(str[5:])
default:
return hclog.Info, str
}
}
+148
View File
@@ -0,0 +1,148 @@
package logger
import (
"io"
"log"
"github.com/anchore/syft/syft/logger"
"github.com/hashicorp/go-hclog"
)
// integrity check
var _ hclog.Logger = &LoggerHCLogAdapter{}
// LoggerHCLogAdapter is used to (partially) adapt a syft logger.Logger object to the hclog.Logger interface for syft plugins.
// Note: this does not implement all functionality required by the hclog.Logger.
type LoggerHCLogAdapter struct {
Logger logger.Logger
}
func NewLoggerHCLogAdapter(logger logger.Logger) *LoggerHCLogAdapter {
return &LoggerHCLogAdapter{
Logger: logger,
}
}
// Args are alternating key, val pairs
// keys must be strings
// vals can be any type, but display is implementation specific
// Emit a message and key/value pairs at a provided log level
func (l *LoggerHCLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
l.Logger.Info(append([]interface{}{msg}, args...)...)
}
// Emit a message and key/value pairs at the TRACE level
func (l *LoggerHCLogAdapter) Trace(msg string, args ...interface{}) {
l.Logger.Debug(append([]interface{}{msg}, args...)...)
}
// Emit a message and key/value pairs at the DEBUG level
func (l *LoggerHCLogAdapter) Debug(msg string, args ...interface{}) {
l.Logger.Debug(append([]interface{}{msg}, args...)...)
}
// Emit a message and key/value pairs at the INFO level
func (l *LoggerHCLogAdapter) Info(msg string, args ...interface{}) {
l.Logger.Info(append([]interface{}{msg}, args...)...)
}
// Emit a message and key/value pairs at the WARN level
func (l *LoggerHCLogAdapter) Warn(msg string, args ...interface{}) {
l.Logger.Error(append([]interface{}{msg}, args...)...)
}
// Emit a message and key/value pairs at the ERROR level
func (l *LoggerHCLogAdapter) Error(msg string, args ...interface{}) {
l.Logger.Error(append([]interface{}{msg}, args...)...)
}
// Indicate if TRACE logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LoggerHCLogAdapter) IsTrace() bool {
// TODO: not implemented
return true
}
// Indicate if DEBUG logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LoggerHCLogAdapter) IsDebug() bool {
// TODO: not implemented
return true
}
// Indicate if INFO logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LoggerHCLogAdapter) IsInfo() bool {
// TODO: not implemented
return true
}
// Indicate if WARN logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LoggerHCLogAdapter) IsWarn() bool {
// TODO: not implemented
return true
}
// Indicate if ERROR logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LoggerHCLogAdapter) IsError() bool {
// TODO: not implemented
return true
}
// ImpliedArgs returns With key/value pairs
func (l *LoggerHCLogAdapter) ImpliedArgs() []interface{} {
// TODO: not implemented
return nil
}
// Creates a sublogger that will always have the given key/value pairs
func (l *LoggerHCLogAdapter) With(args ...interface{}) hclog.Logger {
// TODO: not implemented
return l
}
// Returns the Name of the logger
func (l *LoggerHCLogAdapter) Name() string {
// TODO: not implemented
return ""
}
// Create a logger that will prepend the name string on the front of all messages.
// If the logger already has a name, the new value will be appended to the current
// name. That way, a major subsystem can use this to decorate all it's own logs
// without losing context.
func (l *LoggerHCLogAdapter) Named(name string) hclog.Logger {
// TODO: not implemented
return l
}
// Create a logger that will prepend the name string on the front of all messages.
// This sets the name of the logger to the value directly, unlike Named which honor
// the current name as well.
func (l *LoggerHCLogAdapter) ResetNamed(name string) hclog.Logger {
// TODO: not implemented
return l
}
// Updates the level. This should affect all sub-loggers as well. If an
// implementation cannot update the level on the fly, it should no-op.
func (l *LoggerHCLogAdapter) SetLevel(level hclog.Level) {
// TODO: not implemented
}
// Return a value that conforms to the stdlib log.Logger interface
func (l *LoggerHCLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
return log.New(l.StandardWriter(opts), "", log.LstdFlags)
}
// Return a value that conforms to io.Writer, which can be passed into log.SetOutput()
func (l *LoggerHCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
return &HCLogStdlogAdapter{
log: l,
inferLevels: opts.InferLevels,
forceLevel: opts.ForceLevel,
}
}
+25 -56
View File
@@ -6,11 +6,15 @@ import (
"io/ioutil"
"os"
"github.com/anchore/syft/syft/logger"
"github.com/sirupsen/logrus"
prefixed "github.com/x-cray/logrus-prefixed-formatter"
)
// LogrusConfig contains all configurable values for the Logrus logger
// integrity check
var _ logger.Logger = &LogrusLogger{}
var _ logger.Logger = &LogrusNestedLogger{}
type LogrusConfig struct {
EnableConsole bool
EnableFile bool
@@ -19,19 +23,16 @@ type LogrusConfig struct {
FileLocation string
}
// LogrusLogger contains all runtime values for using Logrus with the configured output target and input configuration values.
type LogrusLogger struct {
Config LogrusConfig
Logger *logrus.Logger
Output io.Writer
}
// LogrusNestedLogger is a wrapper for Logrus to enable nested logging configuration (loggers that always attach key-value pairs to all log entries)
type LogrusNestedLogger struct {
Logger *logrus.Entry
}
// NewLogrusLogger creates a new LogrusLogger with the given configuration
func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger {
appLogger := logrus.New()
@@ -80,82 +81,50 @@ func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger {
}
}
// Debugf takes a formatted template string and template arguments for the debug logging level.
func (l *LogrusLogger) Debugf(format string, args ...interface{}) {
l.Logger.Debugf(format, args...)
}
// Infof takes a formatted template string and template arguments for the info logging level.
func (l *LogrusLogger) Infof(format string, args ...interface{}) {
l.Logger.Infof(format, args...)
}
// Warnf takes a formatted template string and template arguments for the warning logging level.
func (l *LogrusLogger) Warnf(format string, args ...interface{}) {
l.Logger.Warnf(format, args...)
}
// Errorf takes a formatted template string and template arguments for the error logging level.
func (l *LogrusLogger) Errorf(format string, args ...interface{}) {
l.Logger.Errorf(format, args...)
}
// Debug logs the given arguments at the debug logging level.
func (l *LogrusLogger) Debug(args ...interface{}) {
l.Logger.Debug(args...)
func (l *LogrusLogger) Infof(format string, args ...interface{}) {
l.Logger.Infof(format, args...)
}
// Info logs the given arguments at the info logging level.
func (l *LogrusLogger) Info(args ...interface{}) {
l.Logger.Info(args...)
func (l *LogrusLogger) Debugf(format string, args ...interface{}) {
l.Logger.Debugf(format, args...)
}
// Warn logs the given arguments at the warning logging level.
func (l *LogrusLogger) Warn(args ...interface{}) {
l.Logger.Warn(args...)
}
// Error logs the given arguments at the error logging level.
func (l *LogrusLogger) Error(args ...interface{}) {
l.Logger.Error(args...)
}
// Debugf takes a formatted template string and template arguments for the debug logging level.
func (l *LogrusNestedLogger) Debugf(format string, args ...interface{}) {
l.Logger.Debugf(format, args...)
func (l *LogrusLogger) Info(args ...interface{}) {
l.Logger.Info(args...)
}
// Infof takes a formatted template string and template arguments for the info logging level.
func (l *LogrusNestedLogger) Infof(format string, args ...interface{}) {
l.Logger.Infof(format, args...)
func (l *LogrusLogger) Debug(args ...interface{}) {
l.Logger.Debug(args...)
}
// Warnf takes a formatted template string and template arguments for the warning logging level.
func (l *LogrusNestedLogger) Warnf(format string, args ...interface{}) {
l.Logger.Warnf(format, args...)
}
// Errorf takes a formatted template string and template arguments for the error logging level.
func (l *LogrusNestedLogger) Errorf(format string, args ...interface{}) {
l.Logger.Errorf(format, args...)
}
// Debug logs the given arguments at the debug logging level.
func (l *LogrusNestedLogger) Debug(args ...interface{}) {
l.Logger.Debug(args...)
func (l *LogrusNestedLogger) Infof(format string, args ...interface{}) {
l.Logger.Infof(format, args...)
}
func (l *LogrusNestedLogger) Debugf(format string, args ...interface{}) {
l.Logger.Debugf(format, args...)
}
func (l *LogrusNestedLogger) Error(args ...interface{}) {
l.Logger.Error(args...)
}
// Info logs the given arguments at the info logging level.
func (l *LogrusNestedLogger) Info(args ...interface{}) {
l.Logger.Info(args...)
}
// Warn logs the given arguments at the warning logging level.
func (l *LogrusNestedLogger) Warn(args ...interface{}) {
l.Logger.Warn(args...)
}
// Error logs the given arguments at the error logging level.
func (l *LogrusNestedLogger) Error(args ...interface{}) {
l.Logger.Error(args...)
func (l *LogrusNestedLogger) Debug(args ...interface{}) {
l.Logger.Debug(args...)
}
+181
View File
@@ -0,0 +1,181 @@
package logger
import (
"fmt"
"io"
"log"
"github.com/hashicorp/go-hclog"
"github.com/sirupsen/logrus"
)
// integrity check
var _ hclog.Logger = &LogrusHCLogAdapter{}
var levels = map[hclog.Level]logrus.Level{
hclog.NoLevel: logrus.PanicLevel,
hclog.Error: logrus.ErrorLevel,
hclog.Warn: logrus.WarnLevel,
hclog.Info: logrus.InfoLevel,
hclog.Debug: logrus.DebugLevel,
hclog.Trace: logrus.TraceLevel,
}
// LogrusHCLogAdapter is used to adapt a logrus logger object to the hclog.Logger interface for syft plugins.
type LogrusHCLogAdapter struct {
logger *logrus.Logger
entry *logrus.Entry
}
func NewLogrusHCLogAdapter(logger *logrus.Logger, fields map[string]interface{}) *LogrusHCLogAdapter {
if fields != nil {
return &LogrusHCLogAdapter{
logger: logger,
entry: logger.WithFields(fields),
}
}
return &LogrusHCLogAdapter{
logger: logger,
}
}
func (l *LogrusHCLogAdapter) log(level hclog.Level, msg string, args ...interface{}) {
if l.entry != nil {
l.entry.WithFields(makeArgFields(args...)).Log(levels[level], msg)
return
}
l.logger.WithFields(makeArgFields(args...)).Log(levels[level], msg)
}
// Args are alternating key, val pairs
// keys must be strings
// vals can be any type, but display is implementation specific
// Emit a message and key/value pairs at a provided log level
func (l *LogrusHCLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
l.log(level, msg, args...)
}
// Emit a message and key/value pairs at the TRACE level
func (l *LogrusHCLogAdapter) Trace(msg string, args ...interface{}) {
l.log(hclog.Trace, msg, args...)
}
// Emit a message and key/value pairs at the DEBUG level
func (l *LogrusHCLogAdapter) Debug(msg string, args ...interface{}) {
l.log(hclog.Debug, msg, args...)
}
// Emit a message and key/value pairs at the INFO level
func (l *LogrusHCLogAdapter) Info(msg string, args ...interface{}) {
l.log(hclog.Info, msg, args...)
}
// Emit a message and key/value pairs at the WARN level
func (l *LogrusHCLogAdapter) Warn(msg string, args ...interface{}) {
l.log(hclog.Warn, msg, args...)
}
// Emit a message and key/value pairs at the ERROR level
func (l *LogrusHCLogAdapter) Error(msg string, args ...interface{}) {
l.log(hclog.Error, msg, args...)
}
// Indicate if TRACE logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LogrusHCLogAdapter) IsTrace() bool {
return l.logger.Level <= logrus.TraceLevel
}
// Indicate if DEBUG logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LogrusHCLogAdapter) IsDebug() bool {
return l.logger.Level <= logrus.DebugLevel
}
// Indicate if INFO logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LogrusHCLogAdapter) IsInfo() bool {
return l.logger.Level <= logrus.InfoLevel
}
// Indicate if WARN logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LogrusHCLogAdapter) IsWarn() bool {
return l.logger.Level <= logrus.WarnLevel
}
// Indicate if ERROR logs would be emitted. This and the other Is* guards
// are used to elide expensive logging code based on the current level.
func (l *LogrusHCLogAdapter) IsError() bool {
return l.logger.Level <= logrus.ErrorLevel
}
// ImpliedArgs returns With key/value pairs
func (l *LogrusHCLogAdapter) ImpliedArgs() []interface{} {
// TODO: not implemented
return nil
}
// Creates a sublogger that will always have the given key/value pairs
func (l *LogrusHCLogAdapter) With(args ...interface{}) hclog.Logger {
l.entry = l.logger.WithFields(makeArgFields(args...))
return l
}
// Returns the Name of the logger
func (l *LogrusHCLogAdapter) Name() string {
// TODO: not implemented
return ""
}
// Create a logger that will prepend the name string on the front of all messages.
// If the logger already has a name, the new value will be appended to the current
// name. That way, a major subsystem can use this to decorate all it's own logs
// without losing context.
func (l *LogrusHCLogAdapter) Named(name string) hclog.Logger {
return NewLogrusHCLogAdapter(l.logger, logrus.Fields{"name": name})
}
// Create a logger that will prepend the name string on the front of all messages.
// This sets the name of the logger to the value directly, unlike Named which honor
// the current name as well.
func (l *LogrusHCLogAdapter) ResetNamed(name string) hclog.Logger {
return NewLogrusHCLogAdapter(l.logger, logrus.Fields{"name": name})
}
// Updates the level. This should affect all sub-loggers as well. If an
// implementation cannot update the level on the fly, it should no-op.
func (l *LogrusHCLogAdapter) SetLevel(level hclog.Level) {
l.logger.SetLevel(levels[level])
}
// Return a value that conforms to the stdlib log.Logger interface
func (l *LogrusHCLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
return log.New(l.StandardWriter(opts), "", log.LstdFlags)
}
// Return a value that conforms to io.Writer, which can be passed into log.SetOutput()
func (l *LogrusHCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
return &HCLogStdlogAdapter{
log: l,
inferLevels: opts.InferLevels,
forceLevel: opts.ForceLevel,
}
}
func makeArgFields(args ...interface{}) logrus.Fields {
if len(args)%2 != 0 {
panic(fmt.Errorf("odd number of logger key-value pairs (%d): %+v", len(args), args))
}
var key string
var fields = make(logrus.Fields)
for i, arg := range args {
if i%2 == 0 {
key = arg.(string)
} else {
fields[key] = arg
}
}
return fields
}
-15
View File
@@ -1,15 +0,0 @@
package internal
import "regexp"
// 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
}
-10
View File
@@ -1,16 +1,11 @@
package internal
import "sort"
// StringSet represents a set of string types.
type StringSet map[string]struct{}
// NewStringSet creates a new empty StringSet.
func NewStringSet() StringSet {
return make(StringSet)
}
// NewStringSetFromSlice creates a StringSet populated with values from the given slice.
func NewStringSetFromSlice(start []string) StringSet {
ret := make(StringSet)
for _, s := range start {
@@ -19,23 +14,19 @@ func NewStringSetFromSlice(start []string) StringSet {
return ret
}
// Add a string to the set.
func (s StringSet) Add(i string) {
s[i] = struct{}{}
}
// Remove a string from the set.
func (s StringSet) Remove(i string) {
delete(s, i)
}
// Contains indicates if the given string is contained within the set.
func (s StringSet) Contains(i string) bool {
_, ok := s[i]
return ok
}
// ToSlice returns a sorted slice of strings that are contained within the set.
func (s StringSet) ToSlice() []string {
ret := make([]string, len(s))
idx := 0
@@ -43,6 +34,5 @@ func (s StringSet) ToSlice() []string {
ret[idx] = v
idx++
}
sort.Strings(ret)
return ret
}
+7 -10
View File
@@ -1,6 +1,3 @@
/*
Package version contains all build time metadata (version, build time, git commit, etc).
*/
package version
import (
@@ -19,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 `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
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
}
// FromBuild provides all version details
-1
View File
@@ -18,7 +18,6 @@ var latestAppVersionURL = struct {
path: fmt.Sprintf("/%s/releases/latest/VERSION", internal.ApplicationName),
}
// IsUpdateAvailable indicates if there is a newer application version available, and if so, what the new version is.
func IsUpdateAvailable() (bool, string, error) {
currentVersionStr := FromBuild().Version
currentVersion, err := hashiVersion.NewVersion(currentVersionStr)
+5
View File
@@ -0,0 +1,5 @@
#!/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
-9
View File
@@ -1,9 +0,0 @@
## Updating the JSON schema
Today the JSON schema is generated from integration test data. Specifically, when integration tests are run, the `/schema/json/examples` directory is populated with syft JSON output data. This examples directory is used to drive automatically generating the JSON schema.
The caveats with this approach is:
1) the JSON schema is only as good as the examples provided
2) there is an integration test that ensures that the JSON schema is valid relative to what the code currently generates.
This means to update the JSON schema you need to
1) Open up `test/integration/json_schema_test.go` and comment out invocations of the `validateAgainstV1Schema` function.
2) From the root of the repo run `generate-json-schema`. Now there should be a new schema generated at `/schema/json/schema.json`
3) Uncomment the `validateAgainstV1Schema` function.
+389 -310
View File
@@ -4,39 +4,34 @@
"artifacts": {
"items": {
"properties": {
"foundBy": {
"type": "string"
},
"language": {
"type": "string"
},
"licenses": {
"anyOf": [
{
"type": "null"
},
{
"items": {
"type": "string"
},
"type": "array"
}
]
"found-by": {
"items": {
"type": "string"
},
"type": "array"
},
"locations": {
"items": {
"properties": {
"layerID": {
"anyOf": [
{
"type": "string"
},
"path": {
"type": "string"
{
"properties": {
"layer-index": {
"type": "integer"
},
"path": {
"type": "string"
}
},
"required": [
"layer-index",
"path"
],
"type": "object"
}
},
"required": [
"path"
],
"type": "object"
]
},
"type": "array"
},
@@ -45,12 +40,6 @@
"architecture": {
"type": "string"
},
"author": {
"type": "string"
},
"authorEmail": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -58,258 +47,412 @@
"type": "integer"
},
"files": {
"anyOf": [
{
"type": "null"
},
{
"items": {
"anyOf": [
{
"type": "string"
},
{
"properties": {
"checksum": {
"type": "string"
},
"digest": {
"properties": {
"algorithm": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"algorithm",
"value"
],
"type": "object"
},
"ownerGid": {
"type": "string"
},
"ownerUid": {
"type": "string"
},
"path": {
"type": "string"
},
"permissions": {
"type": "string"
},
"size": {
"type": "string"
}
},
"required": [
"path"
],
"type": "object"
}
]
"items": {
"properties": {
"checksum": {
"type": "string"
},
"type": "array"
}
]
"owner-gid": {
"type": "string"
},
"owner-uid": {
"type": "string"
},
"path": {
"type": "string"
},
"permissions": {
"type": "string"
}
},
"required": [
"checksum",
"owner-gid",
"owner-uid",
"path",
"permissions"
],
"type": "object"
},
"type": "array"
},
"gitCommitOfApkPort": {
"git-commit-of-apk-port": {
"type": "string"
},
"homepage": {
"type": "string"
},
"installedSize": {
"installed-size": {
"type": "integer"
},
"license": {
"type": "string"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"maintainer": {
"type": "string"
},
"manifest": {
"properties": {
"main": {
"anyOf": [
{
"type": "null"
},
{
"properties": {
"Archiver-Version": {
"extra-fields": {
"properties": {
"Archiver-Version": {
"type": "string"
},
"Build-Jdk": {
"type": "string"
},
"Built-By": {
"type": "string"
},
"Created-By": {
"type": "string"
},
"Extension-Name": {
"type": "string"
},
"Group-Id": {
"type": "string"
},
"Hudson-Version": {
"type": "string"
},
"Jenkins-Version": {
"type": "string"
},
"Long-Name": {
"type": "string"
},
"Main-Class": {
"type": "string"
},
"Minimum-Java-Version": {
"type": "string"
},
"Plugin-Dependencies": {
"type": "string"
},
"Plugin-Developers": {
"type": "string"
},
"Plugin-License-Name": {
"type": "string"
},
"Plugin-License-Url": {
"type": "string"
},
"Plugin-ScmUrl": {
"type": "string"
},
"Plugin-Version": {
"type": "string"
},
"Short-Name": {
"type": "string"
}
},
"required": [
"Archiver-Version",
"Build-Jdk",
"Built-By",
"Created-By"
],
"type": "object"
},
"implementation-title": {
"type": "string"
},
"Build-Jdk": {
"implementation-vendor": {
"type": "string"
},
"Built-By": {
"implementation-version": {
"type": "string"
},
"Created-By": {
"manifest-version": {
"type": "string"
},
"Extension-Name": {
"name": {
"type": "string"
},
"Group-Id": {
"specification-title": {
"type": "string"
},
"Hudson-Version": {
"specification-vendor": {
"type": "string"
},
"Implementation-Title": {
"type": "string"
},
"Implementation-Version": {
"type": "string"
},
"Jenkins-Version": {
"type": "string"
},
"Long-Name": {
"type": "string"
},
"Main-Class": {
"type": "string"
},
"Manifest-Version": {
"type": "string"
},
"Minimum-Java-Version": {
"type": "string"
},
"Plugin-Dependencies": {
"type": "string"
},
"Plugin-Developers": {
"type": "string"
},
"Plugin-License-Name": {
"type": "string"
},
"Plugin-License-Url": {
"type": "string"
},
"Plugin-ScmUrl": {
"type": "string"
},
"Plugin-Version": {
"type": "string"
},
"Short-Name": {
"type": "string"
},
"Specification-Title": {
"specification-version": {
"type": "string"
}
},
"required": [
"Archiver-Version",
"Build-Jdk",
"Built-By",
"Created-By",
"Manifest-Version"
"extra-fields",
"implementation-title",
"implementation-vendor",
"implementation-version",
"manifest-version",
"name",
"specification-title",
"specification-vendor",
"specification-version"
],
"type": "object"
}
},
"required": [
"main"
],
"type": "object"
]
},
"name": {
"type": "string"
},
"originPackage": {
"origin-package": {
"type": "string"
},
"package": {
"type": "string"
},
"platform": {
"type": "string"
},
"pomProperties": {
"properties": {
"artifactId": {
"type": "string"
},
"extraFields": {
"parent-package": {
"anyOf": [
{
"type": "null"
},
"groupId": {
{
"properties": {
"found-by": {
"type": "string"
},
"language": {
"type": "integer"
},
"licenses": {
"type": "null"
},
"manifest": {
"type": "string"
},
"metadata": {
"properties": {
"manifest": {
"properties": {
"extra-fields": {
"properties": {
"Archiver-Version": {
"type": "string"
},
"Build-Jdk": {
"type": "string"
},
"Built-By": {
"type": "string"
},
"Created-By": {
"type": "string"
},
"Extension-Name": {
"type": "string"
},
"Group-Id": {
"type": "string"
},
"Hudson-Version": {
"type": "string"
},
"Jenkins-Version": {
"type": "string"
},
"Long-Name": {
"type": "string"
},
"Main-Class": {
"type": "string"
},
"Minimum-Java-Version": {
"type": "string"
},
"Plugin-Dependencies": {
"type": "string"
},
"Plugin-Developers": {
"type": "string"
},
"Plugin-License-Name": {
"type": "string"
},
"Plugin-License-Url": {
"type": "string"
},
"Plugin-ScmUrl": {
"type": "string"
},
"Plugin-Version": {
"type": "string"
},
"Short-Name": {
"type": "string"
}
},
"required": [
"Archiver-Version",
"Build-Jdk",
"Built-By",
"Created-By"
],
"type": "object"
},
"implementation-title": {
"type": "string"
},
"implementation-vendor": {
"type": "string"
},
"implementation-version": {
"type": "string"
},
"manifest-version": {
"type": "string"
},
"name": {
"type": "string"
},
"specification-title": {
"type": "string"
},
"specification-vendor": {
"type": "string"
},
"specification-version": {
"type": "string"
}
},
"required": [
"extra-fields",
"implementation-title",
"implementation-vendor",
"implementation-version",
"manifest-version",
"name",
"specification-title",
"specification-vendor",
"specification-version"
],
"type": "object"
},
"parent-package": {
"type": "null"
},
"pom-properties": {
"properties": {
"Path": {
"type": "string"
},
"artifact-id": {
"type": "string"
},
"extra-fields": {
"type": "null"
},
"group-id": {
"type": "string"
},
"name": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"Path",
"artifact-id",
"extra-fields",
"group-id",
"name",
"version"
],
"type": "object"
}
},
"required": [
"manifest",
"parent-package",
"pom-properties"
],
"type": "object"
},
"sources": {
"type": "null"
},
"type": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"found-by",
"language",
"licenses",
"manifest",
"metadata",
"sources",
"type",
"version"
],
"type": "object"
}
]
},
"pom-properties": {
"properties": {
"Path": {
"type": "string"
},
"artifact-id": {
"type": "string"
},
"extra-fields": {
"type": "null"
},
"group-id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"artifactId",
"extraFields",
"groupId",
"Path",
"artifact-id",
"extra-fields",
"group-id",
"name",
"path",
"version"
],
"type": "object"
},
"pullChecksum": {
"pull-checksum": {
"type": "string"
},
"pullDependencies": {
"pull-dependencies": {
"type": "string"
},
"release": {
"type": "string"
},
"sitePackagesRootPath": {
"type": "string"
},
"size": {
"type": "integer"
},
"source": {
"type": "string"
},
"sourceRpm": {
"type": "string"
},
"topLevelPackages": {
"items": {
"type": "string"
},
"type": "array"
},
"url": {
"type": "string"
},
"vendor": {
"type": "string"
},
"version": {
"type": "string"
},
"virtualPath": {
"type": "string"
}
},
"type": "object"
},
"metadataType": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -321,11 +464,8 @@
}
},
"required": [
"foundBy",
"language",
"licenses",
"found-by",
"locations",
"metadataType",
"name",
"type",
"version"
@@ -334,122 +474,61 @@
},
"type": "array"
},
"descriptor": {
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"name",
"version"
],
"type": "object"
"directory": {
"type": "string"
},
"distro": {
"image": {
"properties": {
"idLike": {
"digest": {
"type": "string"
},
"name": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"idLike",
"name",
"version"
],
"type": "object"
},
"source": {
"properties": {
"target": {
"anyOf": [
{
"type": "string"
},
{
"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"
},
"scope": {
"type": "string"
},
"size": {
"type": "integer"
},
"tags": {
"items": {
"type": "string"
},
"type": "array"
},
"userInput": {
"type": "string"
}
"layers": {
"items": {
"properties": {
"digest": {
"type": "string"
},
"required": [
"digest",
"layers",
"mediaType",
"scope",
"size",
"tags",
"userInput"
],
"type": "object"
}
]
"media-type": {
"type": "string"
},
"size": {
"type": "integer"
}
},
"required": [
"digest",
"media-type",
"size"
],
"type": "object"
},
"type": "array"
},
"type": {
"media-type": {
"type": "string"
},
"size": {
"type": "integer"
},
"tags": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"target",
"type"
"digest",
"layers",
"media-type",
"size",
"tags"
],
"type": "object"
}
},
"required": [
"artifacts",
"descriptor",
"distro",
"source"
"artifacts"
],
"type": "object"
}
+30 -3
View File
@@ -4,14 +4,41 @@ Package apkdb provides a concrete Cataloger implementation for Alpine DB files.
package apkdb
import (
"io"
"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"
)
// NewApkdbCataloger returns a new Alpine DB cataloger object.
func NewApkdbCataloger() *common.GenericCataloger {
// 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 {
globParsers := map[string]common.ParserFn{
"**/lib/apk/db/installed": parseApkDB,
}
return common.NewGenericCataloger(nil, globParsers, "apkdb-cataloger")
return &Cataloger{
cataloger: common.NewGenericCataloger(nil, globParsers),
}
}
// Name returns a string that uniquely describes this cataloger.
func (c *Cataloger) Name() string {
return "apkdb-cataloger"
}
// SelectFiles returns a set of discovered Alpine DB files from the user content source.
func (c *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return c.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all Alpine DB files discovered.
func (c *Cataloger) Catalog(contents map[file.Reference]io.Reader) ([]pkg.Package, error) {
return c.cataloger.Catalog(contents)
}
+12 -29
View File
@@ -20,14 +20,9 @@ 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' {
@@ -49,12 +44,11 @@ func parseApkDB(_ string, reader io.Reader) ([]pkg.Package, error) {
}
if metadata != nil {
packages = append(packages, pkg.Package{
Name: metadata.Package,
Version: metadata.Version,
Licenses: strings.Split(metadata.License, " "),
Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType,
Metadata: *metadata,
Name: metadata.Package,
Version: metadata.Version,
Licenses: strings.Split(metadata.License, " "),
Type: pkg.ApkPkg,
Metadata: *metadata,
})
}
}
@@ -89,16 +83,7 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
switch key {
case "F":
currentFile := "/" + value
newFileRecord := pkg.ApkFileRecord{
Path: currentFile,
}
files = append(files, newFileRecord)
fileRecord = &files[len(files)-1]
// future aux references are relative to previous "F" records
lastFile = currentFile
lastFile = "/" + value
continue
case "R":
newFileRecord := pkg.ApkFileRecord{
@@ -106,24 +91,22 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
}
files = append(files, newFileRecord)
fileRecord = &files[len(files)-1]
case "a", "M":
case "a":
ownershipFields := strings.Split(value, ":")
if len(ownershipFields) < 3 {
log.Warnf("unexpected APK ownership field: %q", value)
if len(ownershipFields) != 3 {
log.Errorf("unexpected APK ownership field: %q", value)
continue
}
if fileRecord == nil {
log.Warnf("ownership field with no parent record: %q", value)
log.Errorf("ownership field with no parent record: %q", value)
continue
}
fileRecord.OwnerUID = ownershipFields[0]
fileRecord.OwnerGID = ownershipFields[1]
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.Warnf("checksum field with no parent record: %q", value)
log.Errorf("checksum field with no parent record: %q", value)
continue
}
fileRecord.Checksum = value
+23 -476
View File
@@ -10,78 +10,13 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func TestExtraFileAttributes(t *testing.T) {
func TestSinglePackage(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",
},
{
Path: "/usr/lib",
},
{
Path: "/usr/lib/jvm",
},
{
Path: "/usr/lib/jvm/java-1.8-openjdk",
},
{
Path: "/usr/lib/jvm/java-1.8-openjdk/bin",
},
{
Path: "/usr/lib/jvm/java-1.8-openjdk/bin/policytool",
OwnerUID: "0",
OwnerGID: "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 TestSinglePackageDetails(t *testing.T) {
tests := []struct {
fixture string
expected pkg.ApkMetadata
}{
{
fixture: "test-fixtures/single",
name: "Test Single Package",
expected: pkg.ApkMetadata{
Package: "musl-utils",
OriginPackage: "musl",
@@ -97,428 +32,51 @@ func TestSinglePackageDetails(t *testing.T) {
PullChecksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
GitCommitOfAport: "4024cc3b29ad4c65544ad068b8f59172b5494306",
Files: []pkg.ApkFileRecord{
{
Path: "/sbin",
},
{
Path: "/sbin/ldconfig",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
},
{
Path: "/usr",
},
{
Path: "/usr/bin",
},
{
Path: "/usr/bin/iconv",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
},
{
Path: "/usr/bin/ldd",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
},
{
Path: "/usr/bin/getconf",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
},
{
Path: "/usr/bin/getent",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
},
},
},
},
{
fixture: "test-fixtures/base",
expected: pkg.ApkMetadata{
Package: "alpine-baselayout",
OriginPackage: "alpine-baselayout",
Version: "3.2.0-r6",
Description: "Alpine base dir structure and init scripts",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
License: "GPL-2.0-only",
Architecture: "x86_64",
URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
Size: 19917,
InstalledSize: 409600,
PullDependencies: "/bin/sh so:libc.musl-x86_64.so.1",
PullChecksum: "Q1myMNfd7u5v5UTgNHeq1e31qTjZU=",
GitCommitOfAport: "e1c51734fa96fa4bac92e9f14a474324c67916fc",
Files: []pkg.ApkFileRecord{
{
Path: "/dev",
},
{
Path: "/dev/pts",
},
{
Path: "/dev/shm",
},
{
Path: "/etc",
},
{
Path: "/etc/fstab",
Checksum: "Q11Q7hNe8QpDS531guqCdrXBzoA/o=",
},
{
Path: "/etc/group",
Checksum: "Q1oJ16xWudgKOrXIEquEDzlF2Lsm4=",
},
{
Path: "/etc/hostname",
Checksum: "Q16nVwYVXP/tChvUPdukVD2ifXOmc=",
},
{
Path: "/etc/hosts",
Checksum: "Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=",
},
{
Path: "/etc/inittab",
Checksum: "Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=",
},
{
Path: "/etc/modules",
Checksum: "Q1toogjUipHGcMgECgPJX64SwUT1M=",
},
{
Path: "/etc/motd",
Checksum: "Q1XmduVVNURHQ27TvYp1Lr5TMtFcA=",
},
{
Path: "/etc/mtab",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "777",
Checksum: "Q1kiljhXXH1LlQroHsEJIkPZg2eiw=",
},
{
Path: "/etc/passwd",
Checksum: "Q1TchuuLUfur0izvfZQZxgN/LJhB8=",
},
{
Path: "/etc/profile",
Checksum: "Q1KpFb8kl5LvwXWlY3e58FNsjrI34=",
},
{
Path: "/etc/protocols",
Checksum: "Q13FqXUnvuOpMDrH/6rehxuYAEE34=",
},
{
Path: "/etc/services",
Checksum: "Q1C6HJNgQvLWqt5VY+n7MZJ1rsDuY=",
},
{
Path: "/etc/shadow",
OwnerUID: "0",
OwnerGID: "42",
Permissions: "640",
Checksum: "Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=",
},
{
Path: "/etc/shells",
Checksum: "Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=",
},
{
Path: "/etc/sysctl.conf",
Checksum: "Q14upz3tfnNxZkIEsUhWn7Xoiw96g=",
},
{
Path: "/etc/apk",
},
{
Path: "/etc/conf.d",
},
{
Path: "/etc/crontabs",
},
{
Path: "/etc/crontabs/root",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "600",
Checksum: "Q1vfk1apUWI4yLJGhhNRd0kJixfvY=",
},
{
Path: "/etc/init.d",
},
{
Path: "/etc/modprobe.d",
},
{
Path: "/etc/modprobe.d/aliases.conf",
Checksum: "Q1WUbh6TBYNVK7e4Y+uUvLs/7viqk=",
},
{
Path: "/etc/modprobe.d/blacklist.conf",
Checksum: "Q1xxYGU6S6TLQvb7ervPrWWwAWqMg=",
},
{
Path: "/etc/modprobe.d/i386.conf",
Checksum: "Q1pnay/njn6ol9cCssL7KiZZ8etlc=",
},
{
Path: "/etc/modprobe.d/kms.conf",
Checksum: "Q1ynbLn3GYDpvajba/ldp1niayeog=",
},
{
Path: "/etc/modules-load.d",
},
{
Path: "/etc/network",
},
{
Path: "/etc/network/if-down.d",
},
{
Path: "/etc/network/if-post-down.d",
},
{
Path: "/etc/network/if-pre-up.d",
},
{
Path: "/etc/network/if-up.d",
},
{
Path: "/etc/opt",
},
{
Path: "/etc/periodic",
},
{
Path: "/etc/periodic/15min",
},
{
Path: "/etc/periodic/daily",
},
{
Path: "/etc/periodic/hourly",
},
{
Path: "/etc/periodic/monthly",
},
{
Path: "/etc/periodic/weekly",
},
{
Path: "/etc/profile.d",
},
{
Path: "/etc/profile.d/color_prompt",
Checksum: "Q10wL23GuSCVfumMRgakabUI6EsSk=",
},
{
Path: "/etc/profile.d/locale",
Checksum: "Q1R4bIEpnKxxOSrlnZy9AoawqZ5DU=",
},
{
Path: "/etc/sysctl.d",
},
{
Path: "/home",
},
{
Path: "/lib",
},
{
Path: "/lib/firmware",
},
{
Path: "/lib/mdev",
},
{
Path: "/lib/modules-load.d",
},
{
Path: "/lib/sysctl.d",
},
{
Path: "/lib/sysctl.d/00-alpine.conf",
Checksum: "Q1HpElzW1xEgmKfERtTy7oommnq6c=",
},
{
Path: "/media",
},
{
Path: "/media/cdrom",
},
{
Path: "/media/floppy",
},
{
Path: "/media/usb",
},
{
Path: "/mnt",
},
{
Path: "/opt",
},
{
Path: "/proc",
},
{
Path: "/root",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "700",
},
{
Path: "/run",
},
{
Path: "/sbin",
},
{
Path: "/sbin/mkmntdirs",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1YeuSmC7iDbEWrusPzA/zUQF6YSg=",
},
{
Path: "/srv",
},
{
Path: "/sys",
},
{
Path: "/tmp",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "1777",
},
{
Path: "/usr",
},
{
Path: "/usr/lib",
},
{
Path: "/usr/lib/modules-load.d",
},
{
Path: "/usr/local",
},
{
Path: "/usr/local/bin",
},
{
Path: "/usr/local/lib",
},
{
Path: "/usr/local/share",
},
{
Path: "/usr/sbin",
},
{
Path: "/usr/share",
},
{
Path: "/usr/share/man",
},
{
Path: "/usr/share/misc",
},
{
Path: "/var",
},
{
Path: "/var/run",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "777",
Checksum: "Q11/SNZz/8cK2dSKK+cJpVrZIuF4Q=",
},
{
Path: "/var/cache",
},
{
Path: "/var/cache/misc",
},
{
Path: "/var/empty",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "555",
},
{
Path: "/var/lib",
},
{
Path: "/var/lib/misc",
},
{
Path: "/var/local",
},
{
Path: "/var/lock",
},
{
Path: "/var/lock/subsys",
},
{
Path: "/var/log",
},
{
Path: "/var/mail",
},
{
Path: "/var/opt",
},
{
Path: "/var/spool",
},
{
Path: "/var/spool/mail",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "777",
Checksum: "Q1dzbdazYZA2nTzSIG3YyNw7d4Juc=",
},
{
Path: "/var/spool/cron",
},
{
Path: "/var/spool/cron/crontabs",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "777",
Checksum: "Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA=",
},
{
Path: "/var/tmp",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "1777",
},
},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
file, err := os.Open(test.fixture)
t.Run(test.name, func(t *testing.T) {
file, err := os.Open("test-fixtures/single")
if err != nil {
t.Fatal("Unable to read fixture: ", err)
t.Fatal("Unable to read test_fixtures/single: ", err)
}
defer func() {
err := file.Close()
@@ -552,11 +110,10 @@ func TestMultiplePackages(t *testing.T) {
fixture: "test-fixtures/multiple",
expected: []pkg.Package{
{
Name: "libc-utils",
Version: "0.7.2-r0",
Licenses: []string{"BSD"},
Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType,
Name: "libc-utils",
Version: "0.7.2-r0",
Licenses: []string{"BSD"},
Type: pkg.ApkPkg,
Metadata: pkg.ApkMetadata{
Package: "libc-utils",
OriginPackage: "libc-dev",
@@ -575,11 +132,10 @@ func TestMultiplePackages(t *testing.T) {
},
},
{
Name: "musl-utils",
Version: "1.1.24-r2",
Licenses: []string{"MIT", "BSD", "GPL2+"},
Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType,
Name: "musl-utils",
Version: "1.1.24-r2",
Licenses: []string{"MIT", "BSD", "GPL2+"},
Type: pkg.ApkPkg,
Metadata: pkg.ApkMetadata{
Package: "musl-utils",
OriginPackage: "musl",
@@ -595,47 +151,38 @@ func TestMultiplePackages(t *testing.T) {
PullChecksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
GitCommitOfAport: "4024cc3b29ad4c65544ad068b8f59172b5494306",
Files: []pkg.ApkFileRecord{
{
Path: "/sbin",
},
{
Path: "/sbin/ldconfig",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
},
{
Path: "/usr",
},
{
Path: "/usr/bin",
},
{
Path: "/usr/bin/iconv",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
},
{
Path: "/usr/bin/ldd",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
},
{
Path: "/usr/bin/getconf",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
},
{
Path: "/usr/bin/getent",
OwnerUID: "0",
OwnerGID: "0",
OwnerGUI: "0",
Permissions: "755",
Checksum: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
},
-149
View File
@@ -1,149 +0,0 @@
C:Q1myMNfd7u5v5UTgNHeq1e31qTjZU=
P:alpine-baselayout
V:3.2.0-r6
A:x86_64
S:19917
I:409600
T:Alpine base dir structure and init scripts
U:https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout
L:GPL-2.0-only
o:alpine-baselayout
m:Natanael Copa <ncopa@alpinelinux.org>
t:1590679557
c:e1c51734fa96fa4bac92e9f14a474324c67916fc
D:/bin/sh so:libc.musl-x86_64.so.1
p:cmd:mkmntdirs
F:dev
F:dev/pts
F:dev/shm
F:etc
R:fstab
Z:Q11Q7hNe8QpDS531guqCdrXBzoA/o=
R:group
Z:Q1oJ16xWudgKOrXIEquEDzlF2Lsm4=
R:hostname
Z:Q16nVwYVXP/tChvUPdukVD2ifXOmc=
R:hosts
Z:Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=
R:inittab
Z:Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=
R:modules
Z:Q1toogjUipHGcMgECgPJX64SwUT1M=
R:motd
Z:Q1XmduVVNURHQ27TvYp1Lr5TMtFcA=
R:mtab
a:0:0:777
Z:Q1kiljhXXH1LlQroHsEJIkPZg2eiw=
R:passwd
Z:Q1TchuuLUfur0izvfZQZxgN/LJhB8=
R:profile
Z:Q1KpFb8kl5LvwXWlY3e58FNsjrI34=
R:protocols
Z:Q13FqXUnvuOpMDrH/6rehxuYAEE34=
R:services
Z:Q1C6HJNgQvLWqt5VY+n7MZJ1rsDuY=
R:shadow
a:0:42:640
Z:Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=
R:shells
Z:Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=
R:sysctl.conf
Z:Q14upz3tfnNxZkIEsUhWn7Xoiw96g=
F:etc/apk
F:etc/conf.d
F:etc/crontabs
R:root
a:0:0:600
Z:Q1vfk1apUWI4yLJGhhNRd0kJixfvY=
F:etc/init.d
F:etc/modprobe.d
R:aliases.conf
Z:Q1WUbh6TBYNVK7e4Y+uUvLs/7viqk=
R:blacklist.conf
Z:Q1xxYGU6S6TLQvb7ervPrWWwAWqMg=
R:i386.conf
Z:Q1pnay/njn6ol9cCssL7KiZZ8etlc=
R:kms.conf
Z:Q1ynbLn3GYDpvajba/ldp1niayeog=
F:etc/modules-load.d
F:etc/network
F:etc/network/if-down.d
F:etc/network/if-post-down.d
F:etc/network/if-pre-up.d
F:etc/network/if-up.d
F:etc/opt
F:etc/periodic
F:etc/periodic/15min
F:etc/periodic/daily
F:etc/periodic/hourly
F:etc/periodic/monthly
F:etc/periodic/weekly
F:etc/profile.d
R:color_prompt
Z:Q10wL23GuSCVfumMRgakabUI6EsSk=
R:locale
Z:Q1R4bIEpnKxxOSrlnZy9AoawqZ5DU=
F:etc/sysctl.d
F:home
F:lib
F:lib/firmware
F:lib/mdev
F:lib/modules-load.d
F:lib/sysctl.d
R:00-alpine.conf
Z:Q1HpElzW1xEgmKfERtTy7oommnq6c=
F:media
F:media/cdrom
F:media/floppy
F:media/usb
F:mnt
F:opt
F:proc
F:root
M:0:0:700
F:run
F:sbin
R:mkmntdirs
a:0:0:755
Z:Q1YeuSmC7iDbEWrusPzA/zUQF6YSg=
F:srv
F:sys
F:tmp
M:0:0:1777
F:usr
F:usr/lib
F:usr/lib/modules-load.d
F:usr/local
F:usr/local/bin
F:usr/local/lib
F:usr/local/share
F:usr/sbin
F:usr/share
F:usr/share/man
F:usr/share/misc
F:var
R:run
a:0:0:777
Z:Q11/SNZz/8cK2dSKK+cJpVrZIuF4Q=
F:var/cache
F:var/cache/misc
F:var/empty
M:0:0:555
F:var/lib
F:var/lib/misc
F:var/local
F:var/lock
F:var/lock/subsys
F:var/log
F:var/mail
F:var/opt
F:var/spool
R:mail
a:0:0:777
Z:Q1dzbdazYZA2nTzSIG3YyNw7d4Juc=
F:var/spool/cron
R:crontabs
a:0:0:777
Z:Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA=
F:var/tmp
M:0:0:1777
@@ -1,22 +0,0 @@
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=
+44
View File
@@ -0,0 +1,44 @@
/*
Package bundler provides a concrete Cataloger implementation for Ruby Gemfile.lock bundler files.
*/
package bundler
import (
"io"
"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 (c *Cataloger) Name() string {
return "bundler-cataloger"
}
// SelectFiles returns a set of discovered Gemfile.lock files from the user content source.
func (c *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return c.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all Gemfile.lock files discovered.
func (c *Cataloger) Catalog(contents map[file.Reference]io.Reader) ([]pkg.Package, error) {
return c.cataloger.Catalog(contents)
}
@@ -1,4 +1,4 @@
package ruby
package bundler
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.GemPkg,
Type: pkg.BundlerPkg,
})
}
}
@@ -0,0 +1,100 @@
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)
}
}
}
+57 -38
View File
@@ -1,16 +1,72 @@
package cataloger
import (
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/scope"
"github.com/hashicorp/go-multierror"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
)
// Catalog a given scope (container image or filesystem) with the given catalogers, returning all discovered packages.
// In order to efficiently retrieve contents from a underlying container image the content fetch requests are
// done in bulk. Specifically, all files of interest are collected from each catalogers and accumulated into a single
// request.
func Catalog(resolver scope.Resolver, catalogers ...Cataloger) (*pkg.Catalog, error) {
catalog := pkg.NewCatalog()
fileSelection := make([]file.Reference, 0)
filesProcessed, packagesDiscovered := newMonitor()
// ask catalogers for files to extract from the image tar
for _, a := range catalogers {
catalogerSelection := a.SelectFiles(resolver)
fileSelection = append(fileSelection, catalogerSelection...)
log.Debugf("cataloger '%s' selected '%d' files", a.Name(), len(catalogerSelection))
filesProcessed.N += int64(len(fileSelection))
}
// fetch contents for requested selection by catalogers
// TODO: we should consider refactoring to return a set of io.Readers instead of the full contents themselves (allow for optional buffering).
contents, err := resolver.MultipleFileContentsByRef(fileSelection...)
if err != nil {
return nil, err
}
// perform analysis, accumulating errors for each failed analysis
var errs error
for _, c := range catalogers {
// TODO: check for multiple rounds of analyses by Iterate error
packages, err := c.Catalog(contents)
if err != nil {
errs = multierror.Append(errs, err)
continue
}
log.Debugf("cataloger '%s' discovered '%d' packages", c.Name(), len(packages))
packagesDiscovered.N += int64(len(packages))
catalogerName := c.Name()
for _, p := range packages {
p.FoundBy = catalogerName
catalog.Add(p)
}
}
if errs != nil {
return nil, errs
}
filesProcessed.SetCompleted()
packagesDiscovered.SetCompleted()
return catalog, nil
}
// Monitor provides progress-related data for observing the progress of a Catalog() call (published on the event bus).
type Monitor struct {
FilesProcessed progress.Monitorable // the number of files selected and contents analyzed from all registered catalogers
@@ -31,40 +87,3 @@ func newMonitor() (*progress.Manual, *progress.Manual) {
})
return &filesProcessed, &packagesDiscovered
}
// Catalog a given source (container image or filesystem) with the given catalogers, returning all discovered packages.
// In order to efficiently retrieve contents from a underlying container image the content fetch requests are
// done in bulk. Specifically, all files of interest are collected from each catalogers and accumulated into a single
// request.
func Catalog(resolver source.Resolver, catalogers ...Cataloger) (*pkg.Catalog, error) {
catalog := pkg.NewCatalog()
filesProcessed, packagesDiscovered := newMonitor()
// perform analysis, accumulating errors for each failed analysis
var errs error
for _, theCataloger := range catalogers {
packages, err := theCataloger.Catalog(resolver)
if err != nil {
errs = multierror.Append(errs, err)
continue
}
catalogedPackages := len(packages)
log.Debugf("cataloger '%s' discovered '%d' packages", theCataloger.Name(), catalogedPackages)
packagesDiscovered.N += int64(catalogedPackages)
for _, p := range packages {
catalog.Add(p)
}
}
if errs != nil {
return nil, errs
}
filesProcessed.SetCompleted()
packagesDiscovered.SetCompleted()
return catalog, nil
}
+22 -30
View File
@@ -6,16 +6,20 @@ catalogers defined in child packages as well as the interface definition to impl
package cataloger
import (
"io"
"github.com/anchore/syft/syft/cataloger/dpkg"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/cataloger/apkdb"
"github.com/anchore/syft/syft/cataloger/deb"
"github.com/anchore/syft/syft/cataloger/bundler"
"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/source"
"github.com/anchore/syft/syft/scope"
)
// Cataloger describes behavior for an object to participate in parsing container image or file system
@@ -24,35 +28,23 @@ import (
type Cataloger interface {
// Name returns a string that uniquely describes a cataloger
Name() string
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
Catalog(resolver source.Resolver) ([]pkg.Package, error)
// SelectFiles discovers and returns specific files that the cataloger would like to inspect the contents of.
SelectFiles(scope.FileResolver) []file.Reference
// Catalog is given the file contents and should return any discovered Packages after analyzing the contents.
Catalog(map[file.Reference]io.Reader) ([]pkg.Package, error)
// TODO: add "IterationNeeded" error to indicate to the driver to continue with another Select/Catalog pass
}
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
func ImageCatalogers() []Cataloger {
// All returns a slice of all locally defined catalogers (defined in child packages).
func BuiltIn() []Cataloger {
return []Cataloger{
ruby.NewGemSpecCataloger(),
python.NewPythonPackageCataloger(),
javascript.NewJavascriptPackageCataloger(),
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.NewPythonIndexCataloger(),
python.NewPythonPackageCataloger(),
javascript.NewJavascriptLockCataloger(),
deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(),
java.NewJavaCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModCataloger(),
dpkg.New(),
bundler.New(),
python.New(),
rpmdb.New(),
java.New(),
apkdb.New(),
golang.New(),
javascript.New(),
}
}
+37 -54
View File
@@ -4,113 +4,96 @@ Package common provides generic utilities used by multiple catalogers.
package common
import (
"strings"
"io"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/scope"
)
// 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 []source.Location
parsers map[source.Location]ParserFn
upstreamCataloger string
globParsers map[string]ParserFn
pathParsers map[string]ParserFn
selectedFiles []file.Reference
parsers map[file.Reference]ParserFn
}
// 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, upstreamCataloger string) *GenericCataloger {
return &GenericCataloger{
globParsers: globParsers,
pathParsers: pathParsers,
selectedFiles: make([]source.Location, 0),
parsers: make(map[source.Location]ParserFn),
upstreamCataloger: upstreamCataloger,
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),
}
}
// Name returns a string that uniquely describes the upstream cataloger that this Generic Cataloger represents.
func (c *GenericCataloger) Name() string {
return c.upstreamCataloger
}
// register pairs a set of file references with a parser function for future cataloging (when the file contents are resolved)
func (c *GenericCataloger) register(files []source.Location, parser ParserFn) {
c.selectedFiles = append(c.selectedFiles, files...)
func (a *GenericCataloger) register(files []file.Reference, parser ParserFn) {
a.selectedFiles = append(a.selectedFiles, files...)
for _, f := range files {
c.parsers[f] = parser
a.parsers[f] = parser
}
}
// clear deletes all registered file-reference-to-parser-function pairings from former SelectFiles() and register() calls
func (c *GenericCataloger) clear() {
c.selectedFiles = make([]source.Location, 0)
c.parsers = make(map[source.Location]ParserFn)
}
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
func (c *GenericCataloger) Catalog(resolver source.Resolver) ([]pkg.Package, error) {
fileSelection := c.selectFiles(resolver)
contents, err := resolver.MultipleFileContentsByLocation(fileSelection)
if err != nil {
return nil, err
}
return c.catalog(contents)
func (a *GenericCataloger) clear() {
a.selectedFiles = make([]file.Reference, 0)
a.parsers = make(map[file.Reference]ParserFn)
}
// SelectFiles takes a set of file trees and resolves and file references of interest for future cataloging
func (c *GenericCataloger) selectFiles(resolver source.FileResolver) []source.Location {
func (a *GenericCataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
// select by exact path
for path, parser := range c.pathParsers {
files, err := resolver.FilesByPath(path)
for path, parser := range a.pathParsers {
files, err := resolver.FilesByPath(file.Path(path))
if err != nil {
log.Warnf("cataloger failed to select files by path: %+v", err)
log.Errorf("cataloger failed to select files by path: %w", err)
}
if files != nil {
c.register(files, parser)
a.register(files, parser)
}
}
// select by glob pattern
for globPattern, parser := range c.globParsers {
for globPattern, parser := range a.globParsers {
fileMatches, err := resolver.FilesByGlob(globPattern)
if err != nil {
log.Warnf("failed to find files by glob: %s", globPattern)
log.Errorf("failed to find files by glob: %s", globPattern)
}
if fileMatches != nil {
c.register(fileMatches, parser)
a.register(fileMatches, parser)
}
}
return c.selectedFiles
return a.selectedFiles
}
// catalog takes a set of file contents and uses any configured parser functions to resolve and return discovered packages
func (c *GenericCataloger) catalog(contents map[source.Location]string) ([]pkg.Package, error) {
defer c.clear()
// 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]io.Reader) ([]pkg.Package, error) {
defer a.clear()
packages := make([]pkg.Package, 0)
for location, parser := range c.parsers {
content, ok := contents[location]
for reference, parser := range a.parsers {
content, ok := contents[reference]
if !ok {
log.Warnf("cataloger '%s' missing file content: %+v", c.upstreamCataloger, location)
log.Errorf("cataloger missing file content: %+v", reference)
continue
}
entries, err := parser(location.Path, strings.NewReader(content))
entries, err := parser(string(reference.Path), content)
if err != nil {
// TODO: should we fail? or only log?
log.Warnf("cataloger '%s' failed to parse entries (location=%+v): %+v", c.upstreamCataloger, location, err)
log.Errorf("cataloger failed to parse entries (reference=%+v): %w", reference, err)
continue
}
for _, entry := range entries {
entry.FoundBy = c.upstreamCataloger
entry.Locations = []source.Location{location}
entry.Source = []file.Reference{reference}
packages = append(packages, entry)
}
+41 -40
View File
@@ -6,50 +6,37 @@ import (
"io/ioutil"
"testing"
"github.com/anchore/syft/syft/source"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/pkg"
)
type testResolverMock struct {
contents map[source.Location]string
type testResolver struct {
contents map[file.Reference]string
}
func newTestResolver() *testResolverMock {
return &testResolverMock{
contents: make(map[source.Location]string),
func newTestResolver() *testResolver {
return &testResolver{
contents: make(map[file.Reference]string),
}
}
func (r *testResolverMock) FileContentsByLocation(_ source.Location) (string, error) {
return "", fmt.Errorf("not implemented")
}
func (r *testResolverMock) MultipleFileContentsByLocation([]source.Location) (map[source.Location]string, error) {
return r.contents, nil
}
func (r *testResolverMock) FilesByPath(paths ...string) ([]source.Location, error) {
results := make([]source.Location, len(paths))
func (r *testResolver) FilesByPath(paths ...file.Path) ([]file.Reference, error) {
results := make([]file.Reference, len(paths))
for idx, p := range paths {
results[idx] = source.NewLocation(p)
results[idx] = file.NewFileReference(p)
r.contents[results[idx]] = fmt.Sprintf("%s file contents!", p)
}
return results, nil
}
func (r *testResolverMock) FilesByGlob(_ ...string) ([]source.Location, error) {
func (r *testResolver) FilesByGlob(patterns ...string) ([]file.Reference, error) {
path := "/a-path.txt"
location := source.NewLocation(path)
r.contents[location] = fmt.Sprintf("%s file contents!", path)
return []source.Location{location}, nil
}
func (r *testResolverMock) RelativeFileByPath(_ source.Location, _ string) *source.Location {
panic(fmt.Errorf("not implemented"))
return nil
ref := file.NewFileReference(file.Path(path))
r.contents[ref] = fmt.Sprintf("%s file contents!", path)
return []file.Reference{ref}, nil
}
func parser(_ string, reader io.Reader) ([]pkg.Package, error) {
@@ -73,20 +60,34 @@ func TestGenericCataloger(t *testing.T) {
"/another-path.txt": parser,
"/last/path.txt": parser,
}
upstream := "some-other-cataloger"
resolver := newTestResolver()
cataloger := NewGenericCataloger(pathParsers, globParsers, upstream)
expectedSelection := []string{"/last/path.txt", "/another-path.txt", "/a-path.txt"}
expectedPkgs := make(map[string]pkg.Package)
for _, path := range expectedSelection {
expectedPkgs[path] = pkg.Package{
FoundBy: upstream,
Name: fmt.Sprintf("%s file contents!", path),
resolver := newTestResolver()
cataloger := NewGenericCataloger(pathParsers, globParsers)
selected := cataloger.SelectFiles(resolver)
if len(selected) != 3 {
t.Fatalf("unexpected selection length: %d", len(selected))
}
expectedSelection := internal.NewStringSetFromSlice([]string{"/last/path.txt", "/another-path.txt", "/a-path.txt"})
selectionByPath := make(map[string]file.Reference)
for _, s := range selected {
if !expectedSelection.Contains(string(s.Path)) {
t.Errorf("unexpected selection path: %+v", s.Path)
}
selectionByPath[string(s.Path)] = s
}
expectedPkgs := make(map[file.Reference]pkg.Package)
for path, ref := range selectionByPath {
expectedPkgs[ref] = pkg.Package{
Source: []file.Reference{ref},
Name: fmt.Sprintf("%s file contents!", path),
}
}
actualPkgs, err := cataloger.Catalog(resolver)
actualPkgs, err := cataloger.Catalog(resolver.contents)
if err != nil {
t.Fatalf("cataloger catalog action failed: %+v", err)
}
@@ -96,8 +97,8 @@ func TestGenericCataloger(t *testing.T) {
}
for _, p := range actualPkgs {
ref := p.Locations[0]
exP, ok := expectedPkgs[ref.Path]
ref := p.Source[0]
exP, ok := expectedPkgs[ref]
if !ok {
t.Errorf("missing expected pkg: ref=%+v", ref)
continue
@@ -108,7 +109,7 @@ func TestGenericCataloger(t *testing.T) {
}
if exP.Name != p.Name {
t.Errorf("bad contents mapping: %+v", p.Locations)
t.Errorf("bad contents mapping: %+v", p.Source)
}
}
}
-185
View File
@@ -1,185 +0,0 @@
/*
Package dpkg provides a concrete Cataloger implementation for Debian package DB status files.
*/
package deb
import (
"fmt"
"io"
"path"
"path/filepath"
"strings"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
const (
dpkgStatusGlob = "**/var/lib/dpkg/status"
md5sumsExt = ".md5sums"
docsPath = "/usr/share/doc"
)
type Cataloger struct{}
// NewDpkgdbCataloger returns a new Deb package cataloger object.
func NewDpkgdbCataloger() *Cataloger {
return &Cataloger{}
}
// Name returns a string that uniquely describes a cataloger
func (c *Cataloger) Name() string {
return "dpkgdb-cataloger"
}
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing dpkg support files.
func (c *Cataloger) Catalog(resolver source.Resolver) ([]pkg.Package, error) {
dbFileMatches, err := resolver.FilesByGlob(dpkgStatusGlob)
if err != nil {
return nil, fmt.Errorf("failed to find dpkg status files's by glob: %w", err)
}
var pkgs []pkg.Package
for _, dbLocation := range dbFileMatches {
dbContents, err := resolver.FileContentsByLocation(dbLocation)
if err != nil {
return nil, err
}
pkgs, err = parseDpkgStatus(strings.NewReader(dbContents))
if err != nil {
return nil, fmt.Errorf("unable to catalog dpkg package=%+v: %w", dbLocation.Path, err)
}
md5ContentsByName, md5RefsByName, err := fetchMd5Contents(resolver, dbLocation, pkgs)
if err != nil {
return nil, fmt.Errorf("unable to find dpkg md5 contents: %w", err)
}
copyrightContentsByName, copyrightRefsByName, err := fetchCopyrightContents(resolver, dbLocation, pkgs)
if err != nil {
return nil, fmt.Errorf("unable to find dpkg copyright contents: %w", err)
}
for i := range pkgs {
p := &pkgs[i]
p.FoundBy = c.Name()
p.Locations = []source.Location{dbLocation}
if md5Reader, ok := md5ContentsByName[md5Key(*p)]; ok {
// attach the file list
metadata := p.Metadata.(pkg.DpkgMetadata)
metadata.Files = parseDpkgMD5Info(md5Reader)
p.Metadata = metadata
// keep a record of the file where this was discovered
if ref, ok := md5RefsByName[md5Key(*p)]; ok {
p.Locations = append(p.Locations, ref)
}
}
copyrightReader, ok := copyrightContentsByName[p.Name]
if ok {
// attach the licenses
p.Licenses = parseLicensesFromCopyright(copyrightReader)
// keep a record of the file where this was discovered
if ref, ok := copyrightRefsByName[p.Name]; ok {
p.Locations = append(p.Locations, ref)
}
}
}
}
return pkgs, nil
}
func fetchMd5Contents(resolver source.Resolver, dbLocation source.Location, pkgs []pkg.Package) (map[string]io.Reader, map[string]source.Location, error) {
// fetch all MD5 file contents. This approach is more efficient than fetching each MD5 file one at a time
var md5FileMatches []source.Location
var nameByRef = make(map[source.Location]string)
parentPath := filepath.Dir(dbLocation.Path)
for _, p := range pkgs {
// look for /var/lib/dpkg/info/NAME:ARCH.md5sums
name := md5Key(p)
md5sumPath := path.Join(parentPath, "info", name+md5sumsExt)
md5SumLocation := resolver.RelativeFileByPath(dbLocation, md5sumPath)
if md5SumLocation == nil {
// the most specific key did not work, fallback to just the name
// look for /var/lib/dpkg/info/NAME.md5sums
name := p.Name
md5sumPath := path.Join(parentPath, "info", name+md5sumsExt)
md5SumLocation = resolver.RelativeFileByPath(dbLocation, md5sumPath)
}
// we should have at least one reference
if md5SumLocation != nil {
md5FileMatches = append(md5FileMatches, *md5SumLocation)
nameByRef[*md5SumLocation] = name
}
}
// fetch the md5 contents
md5ContentsByLocation, err := resolver.MultipleFileContentsByLocation(md5FileMatches)
if err != nil {
return nil, nil, err
}
// organize content results and refs by a combination of name and architecture
var contentsByName = make(map[string]io.Reader)
var refsByName = make(map[string]source.Location)
for location, contents := range md5ContentsByLocation {
name := nameByRef[location]
contentsByName[name] = strings.NewReader(contents)
refsByName[name] = location
}
return contentsByName, refsByName, nil
}
func fetchCopyrightContents(resolver source.Resolver, dbLocation source.Location, pkgs []pkg.Package) (map[string]io.Reader, map[string]source.Location, error) {
// fetch all copyright file contents. This approach is more efficient than fetching each copyright file one at a time
var copyrightFileMatches []source.Location
var nameByLocation = make(map[source.Location]string)
for _, p := range pkgs {
// look for /usr/share/docs/NAME/copyright files
name := p.Name
copyrightPath := path.Join(docsPath, name, "copyright")
copyrightLocation := resolver.RelativeFileByPath(dbLocation, copyrightPath)
// we may not have a copyright file for each package, ignore missing files
if copyrightLocation != nil {
copyrightFileMatches = append(copyrightFileMatches, *copyrightLocation)
nameByLocation[*copyrightLocation] = name
}
}
// fetch the copyright contents
copyrightContentsByLocation, err := resolver.MultipleFileContentsByLocation(copyrightFileMatches)
if err != nil {
return nil, nil, err
}
// organize content results and refs by package name
var contentsByName = make(map[string]io.Reader)
var refsByName = make(map[string]source.Location)
for location, contents := range copyrightContentsByLocation {
name := nameByLocation[location]
contentsByName[name] = strings.NewReader(contents)
refsByName[name] = location
}
return contentsByName, refsByName, nil
}
func md5Key(p pkg.Package) string {
metadata := p.Metadata.(pkg.DpkgMetadata)
contentKey := p.Name
if metadata.Architecture != "" && metadata.Architecture != "all" {
contentKey = contentKey + ":" + metadata.Architecture
}
return contentKey
}
-99
View File
@@ -1,99 +0,0 @@
package deb
import (
"testing"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/go-test/deep"
)
func TestDpkgCataloger(t *testing.T) {
tests := []struct {
name string
sources map[string][]string
expected []pkg.Package
}{
{
name: "go-case",
sources: map[string][]string{
"libpam-runtime": {"/var/lib/dpkg/status", "/var/lib/dpkg/info/libpam-runtime.md5sums", "/usr/share/doc/libpam-runtime/copyright"},
},
expected: []pkg.Package{
{
Name: "libpam-runtime",
Version: "1.1.8-3.6",
FoundBy: "dpkgdb-cataloger",
Licenses: []string{"GPL-2", "LGPL-2.1"},
Type: pkg.DebPkg,
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
Package: "libpam-runtime",
Source: "pam",
Version: "1.1.8-3.6",
Architecture: "all",
Maintainer: "Steve Langasek <vorlon@debian.org>",
InstalledSize: 1016,
Files: []pkg.DpkgFileRecord{
{Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", MD5: "55f905631797551d4d936a34c7e73474"},
{Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", MD5: "cede84bda30d2380217f97753c8ccf3a"},
{Path: "/usr/share/doc/zlib1g/changelog.gz", MD5: "f3c9dafa6da7992c47328b4464f6d122"},
{Path: "/usr/share/doc/zlib1g/copyright", MD5: "a4fae96070439a5209a62ae5b8017ab2"},
},
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-dpkg")
defer cleanup()
s, err := source.NewFromImage(img, source.AllLayersScope, "")
if err != nil {
t.Fatal(err)
}
c := NewDpkgdbCataloger()
actual, err := c.Catalog(s.Resolver)
if err != nil {
t.Fatalf("failed to catalog: %+v", err)
}
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Logf(" %+v", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
}
// test sources...
for idx := range actual {
a := &actual[idx]
// we will test the sources separately
var sourcesList = make([]string, len(a.Locations))
for i, s := range a.Locations {
sourcesList[i] = s.Path
}
a.Locations = nil
for _, d := range deep.Equal(sourcesList, test.sources[a.Name]) {
t.Errorf("diff: %+v", d)
}
}
// test remaining fields...
for _, d := range deep.Equal(actual, test.expected) {
t.Errorf("diff: %+v", d)
}
})
}
}
-38
View File
@@ -1,38 +0,0 @@
package deb
import (
"bufio"
"io"
"sort"
"strings"
"github.com/anchore/syft/internal"
)
// For more information see: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-syntax
func parseLicensesFromCopyright(reader io.Reader) []string {
findings := internal.NewStringSet()
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "License:") {
candidate := strings.Replace(line, "License:", "", 1)
candidate = strings.TrimSpace(candidate)
if strings.Contains(candidate, " or ") || strings.Contains(candidate, " and ") {
// this is a multi-license summary, ignore this as other recurrent license lines should cover this
continue
}
if candidate != "" && strings.ToLower(candidate) != "none" {
findings.Add(candidate)
}
}
}
results := findings.ToSlice()
sort.Strings(results)
return results
}
@@ -1,58 +0,0 @@
package deb
import (
"os"
"testing"
"github.com/go-test/deep"
)
func TestParseLicensesFromCopyright(t *testing.T) {
tests := []struct {
fixture string
expected []string
}{
{
fixture: "test-fixtures/copyright/trilicense",
expected: []string{"GPL-2", "LGPL-2.1", "MPL-1.1"},
},
{
fixture: "test-fixtures/copyright/liblzma5",
expected: []string{"Autoconf", "GPL-2", "GPL-2+", "LGPL-2.1+", "PD", "PD-debian", "config-h", "noderivs", "permissive-fsf", "permissive-nowarranty", "probably-PD"},
},
{
fixture: "test-fixtures/copyright/libaudit-common",
expected: []string{"GPL-2", "LGPL-2.1"},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
file, err := os.Open(test.fixture)
if err != nil {
t.Fatal("Unable to read: ", err)
}
defer func() {
err := file.Close()
if err != nil {
t.Fatal("closing file failed:", err)
}
}()
actual := parseLicensesFromCopyright(file)
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Logf(" %+v", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
}
diffs := deep.Equal(actual, test.expected)
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
})
}
}
@@ -1,30 +0,0 @@
package deb
import (
"bufio"
"io"
"strings"
"github.com/anchore/syft/syft/pkg"
)
func parseDpkgMD5Info(reader io.Reader) []pkg.DpkgFileRecord {
var findings []pkg.DpkgFileRecord
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
fields := strings.SplitN(line, " ", 2)
if len(fields) == 2 {
path := strings.TrimSpace(fields[1])
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
findings = append(findings, pkg.DpkgFileRecord{
Path: path,
MD5: strings.TrimSpace(fields[0]),
})
}
}
return findings
}
@@ -1,57 +0,0 @@
package deb
import (
"os"
"testing"
"github.com/go-test/deep"
"github.com/anchore/syft/syft/pkg"
)
func TestMD5SumInfoParsing(t *testing.T) {
tests := []struct {
fixture string
expected []pkg.DpkgFileRecord
}{
{
fixture: "test-fixtures/info/zlib1g.md5sums",
expected: []pkg.DpkgFileRecord{
{Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", MD5: "55f905631797551d4d936a34c7e73474"},
{Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", MD5: "cede84bda30d2380217f97753c8ccf3a"},
{Path: "/usr/share/doc/zlib1g/changelog.gz", MD5: "f3c9dafa6da7992c47328b4464f6d122"},
{Path: "/usr/share/doc/zlib1g/copyright", MD5: "a4fae96070439a5209a62ae5b8017ab2"},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
file, err := os.Open(test.fixture)
if err != nil {
t.Fatal("Unable to read: ", err)
}
defer func() {
err := file.Close()
if err != nil {
t.Fatal("closing file failed:", err)
}
}()
actual := parseDpkgMD5Info(file)
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Logf(" %+v", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
}
diffs := deep.Equal(actual, test.expected)
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
})
}
}
@@ -1,43 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: auditd
Source: https://people.redhat.com/sgrubb/audit/
Files: *
Copyright: 2012-2016 Steve Grubb <sgrubb@redhat.com>
2006-2012 Rik Faith
License: GPL-2
Files: src/libev/*
Copyright: 2007-2009 Marc Alexamder Lehmann
License: GPL-2
Files: lib/*
Copyright: 2005-2008 Steve Grubb <sgrubb@redhat.com>
License: LGPL-2.1
The audit daemon's library libaudit.* is released under LGPL
so that it may be linked with 3rd party software.
.
On Debian systems, refer to /usr/share/common-licenses/LGPL-2.1
for the complete text of the GNU Lesser General Public License.
Files: debian/*
Copyright: 2007-2011 Philipp Matthias Hahn <pmhahn@debian.org>
2012-2016 Laurent Bigonville <bigon@debian.org>
License: GPL-2
License: GPL-2
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2,
as published by the Free Software Foundation.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this package; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
.
On Debian systems, the complete text of the GNU General
Public License can be found in `/usr/share/common-licenses/GPL-1'.
@@ -1,383 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: XZ Utils
Upstream-Contact:
Lasse Collin <lasse.collin@tukaani.org>
http://tukaani.org/xz/lists.html
Source:
http://tukaani.org/xz
http://git.tukaani.org/xz.git
Comment:
XZ Utils is developed and maintained upstream by Lasse Collin. Major
portions are based on code by other authors; see AUTHORS for details.
Most of the source has been put into the public domain, but some files
have not (details below).
.
This file describes the source package. The binary packages contain
some files derived from other works: for example, images in the API
documentation come from Doxygen.
License:
Different licenses apply to different files in this package. Here
is a rough summary of which licenses apply to which parts of this
package (but check the individual files to be sure!):
.
- liblzma is in the public domain.
.
- xz, xzdec, and lzmadec command line tools are in the public
domain unless GNU getopt_long had to be compiled and linked
in from the lib directory. The getopt_long code is under
GNU LGPLv2.1+.
.
- The scripts to grep, diff, and view compressed files have been
adapted from gzip. These scripts and their documentation are
under GNU GPLv2+.
.
- All the documentation in the doc directory and most of the
XZ Utils specific documentation files in other directories
are in the public domain.
.
- Translated messages are in the public domain.
.
- The build system contains public domain files, and files that
are under GNU GPLv2+ or GNU GPLv3+. None of these files end up
in the binaries being built.
.
- Test files and test code in the tests directory, and debugging
utilities in the debug directory are in the public domain.
.
- The extra directory may contain public domain files, and files
that are under various free software licenses.
.
You can do whatever you want with the files that have been put into
the public domain. If you find public domain legally problematic,
take the previous sentence as a license grant. If you still find
the lack of copyright legally problematic, you have too many
lawyers.
.
As usual, this software is provided "as is", without any warranty.
.
If you copy significant amounts of public domain code from XZ Utils
into your project, acknowledging this somewhere in your software is
polite (especially if it is proprietary, non-free software), but
naturally it is not legally required. Here is an example of a good
notice to put into "about box" or into documentation:
.
This software includes code from XZ Utils <http://tukaani.org/xz/>.
.
The following license texts are included in the following files:
- COPYING.LGPLv2.1: GNU Lesser General Public License version 2.1
- COPYING.GPLv2: GNU General Public License version 2
- COPYING.GPLv3: GNU General Public License version 3
.
Note that the toolchain (compiler, linker etc.) may add some code
pieces that are copyrighted. Thus, it is possible that e.g. liblzma
binary wouldn't actually be in the public domain in its entirety
even though it contains no copyrighted code from the XZ Utils source
package.
.
If you have questions, don't hesitate to ask the author(s) for more
information.
Files: *
Copyright: 2006-2012, Lasse Collin
1999-2008, Igor Pavlov
2006, Ville Koskinen
1998, Steve Reid
2000, Wei Dai
2003, Kevin Springle
2009, Jonathan Nieder
2010, Anders F Björklund
License: PD
This file has been put in the public domain.
You can do whatever you want with this file.
Comment:
From: Lasse Collin <lasse.collin@tukaani.org>
To: Jonathan Nieder <jrnieder@gmail.com>
Subject: Re: XZ utils for Debian
Date: Sun, 19 Jul 2009 13:28:23 +0300
Message-Id: <200907191328.23816.lasse.collin@tukaani.org>
.
[...]
.
> AUTHORS, ChangeLog, COPYING, README, THANKS, TODO,
> dos/README, windows/README
.
COPYING says that most docs are in the public domain. Maybe that's not
clear enough, but on the other hand it looks a bit stupid to put
copyright information in tiny and relatively small docs like README.
.
I don't dare to say that _all_ XZ Utils specific docs are in the public
domain unless otherwise mentioned in the file. I'm including PDF files
generated by groff + ps2pdf, and some day I might include Doxygen-
generated HTML docs too. Those don't include any copyright notices, but
it seems likely that groff + ps2pdf or at least Doxygen put some
copyrighted content into the generated files.
Files: INSTALL NEWS PACKAGERS
windows/README-Windows.txt
windows/INSTALL-Windows.txt
Copyright: 2009-2010, Lasse Collin
License: probably-PD
See the note on AUTHORS, README, and so on above.
Files: src/scripts/* lib/* extra/scanlzma/scanlzma.c
Copyright: © 1993, Jean-loup Gailly
© 1989-1994, 1996-1999, 2001-2007, Free Software Foundation, Inc.
© 2006 Timo Lindfors
2005, Charles Levert
2005, 2009, Lasse Collin
2009, Andrew Dudman
Other-Authors: Paul Eggert, Ulrich Drepper
License: GPL-2+
Files: src/scripts/Makefile.am src/scripts/xzless.1
Copyright: 2009, Andrew Dudman
2009, Lasse Collin
License: PD
This file has been put in the public domain.
You can do whatever you want with this file.
Files: doc/examples/xz_pipe_comp.c doc/examples/xz_pipe_decomp.c
Copyright: 2010, Daniel Mealha Cabrita
License: PD
Not copyrighted -- provided to the public domain.
Files: lib/getopt.c lib/getopt1.c lib/getopt.in.h
Copyright: © 1987-2007 Free Software Foundation, Inc.
Other-Authors: Ulrich Drepper
License: LGPL-2.1+
Files: m4/getopt.m4 m4/posix-shell.m4
Copyright: © 2002-2006, 2008 Free Software Foundation, Inc.
© 2007-2008 Free Software Foundation, Inc.
Other-Authors: Bruno Haible, Paul Eggert
License: permissive-fsf
Files: m4/acx_pthread.m4
Copyright: © 2008, Steven G. Johnson <stevenj@alum.mit.edu>
License: Autoconf
Files: Doxyfile.in
Copyright: © 1997-2007 by Dimitri van Heesch
Origin: Doxygen 1.4.7
License: GPL-2
Files: src/liblzma/check/crc32_table_?e.h
src/liblzma/check/crc64_table_?e.h
src/liblzma/lzma/fastpos_table.c
src/liblzma/rangecoder/price_table.c
Copyright: none, automatically generated data
Generated-With:
src/liblzma/check/crc32_tablegen.c
src/liblzma/check/crc64_tablegen.c
src/liblzma/lzma/fastpos_tablegen.c
src/liblzma/rangecoder/price_tablegen.c
License: none
No copyright to license.
Files: .gitignore m4/.gitignore po/.gitignore po/LINGUAS po/POTFILES.in
Copyright: none; these are just short lists.
License: none
No copyright to license.
Files: tests/compress_prepared_bcj_*
Copyright: 2008-2009, Lasse Collin
Source-Code: tests/bcj_test.c
License: PD
This file has been put into the public domain.
You can do whatever you want with this file.
Comment:
changelog.gz (commit 975d8fd) explains:
.
Recreated the BCJ test files for x86 and SPARC. The old files
were linked with crt*.o, which are copyrighted, and thus the
old test files were not in the public domain as a whole. They
are freely distributable though, but it is better to be careful
and avoid including any copyrighted pieces in the test files.
The new files are just compiled and assembled object files,
and thus don't contain any copyrighted code.
Files: po/cs.po po/de.po po/fr.po
Copyright: 2010, Marek Černocký
2010, Andre Noll
2011, Adrien Nader
License: PD
This file is put in the public domain.
Files: po/it.po po/pl.po
Copyright: 2009, 2010, Gruppo traduzione italiano di Ubuntu-it
2010, Lorenzo De Liso
2009, 2010, 2011, Milo Casagrande
2011, Jakub Bogusz
License: PD
This file is in the public domain
Files: INSTALL.generic
Copyright: © 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
License: permissive-nowarranty
Files: dos/config.h
Copyright: © 1992, 1993, 1994, 1999, 2000, 2001, 2002, 2005
Free Software Foundation, Inc.
2007-2010, Lasse Collin
Other-Authors: Roland McGrath, Akim Demaille, Paul Eggert,
David Mackenzie, Bruno Haible, and many others.
Origin: configure.ac from XZ Utils,
visibility.m4 serial 1 (gettext-0.15),
Autoconf 2.52g
License: config-h
configure.ac:
.
# Author: Lasse Collin
#
# This file has been put into the public domain.
# You can do whatever you want with this file.
.
visibility.m4:
.
dnl Copyright (C) 2005 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
.
dnl From Bruno Haible.
.
comments from Autoconf 2.52g:
.
# Copyright 1992, 1993, 1994, 1999, 2000, 2001, 2002
# Free Software Foundation, Inc.
.
[...]
.
# As a special exception, the Free Software Foundation gives unlimited
# permission to copy, distribute and modify the configure scripts that
# are the output of Autoconf. You need not follow the terms of the GNU
# General Public License when using or distributing such scripts, even
# though portions of the text of Autoconf appear in them. The GNU
# General Public License (GPL) does govern all other use of the material
# that constitutes the Autoconf program.
.
On Debian systems, the complete text of the GNU General Public
License version 2 can be found in ‘/usr/share/common-licenses/GPL-2’.
dos/config.h was generated with autoheader, which tells Autoconf to
output a script to generate a config.h file and then runs it.
Files: po/Makevars
Origin: gettext-runtime/po/Makevars (gettext-0.12)
Copyright: © 2003 Free Software Foundation, Inc.
Authors: Bruno Haible
License: LGPL-2.1+
The gettext-runtime package is under the LGPL, see files intl/COPYING.LIB-2.0
and intl/COPYING.LIB-2.1.
.
On Debian systems, the complete text of intl/COPYING.LIB-2.0 from
gettext-runtime 0.12 can be found in ‘/usr/share/common-licenses/LGPL-2’
and the text of intl/COPYING.LIB-2.1 can be found in
‘/usr/share/common-licenses/LGPL-2.1’.
.
po/Makevars consists mostly of helpful comments and does not contain a
copyright and license notice.
Files: COPYING.GPLv2 COPYING.GPLv3 COPYING.LGPLv2.1
Copyright: © 1989, 1991, 1999, 2007 Free Software Foundation, Inc.
License: noderivs
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Files: debian/*
Copyright: 2009-2012, Jonathan Nieder
License: PD-debian
The Debian packaging files are in the public domain.
You may freely use, modify, distribute, and relicense them.
License: LGPL-2.1+
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1, or (at your option)
any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
.
On Debian systems, the complete text of the GNU Lesser General Public
License version 2.1 can be found in ‘/usr/share/common-licenses/LGPL-2.1’.
License: GPL-2
Permission to use, copy, modify, and distribute this software and its
documentation under the terms of the GNU General Public License is
hereby granted. No representations are made about the suitability of
this software for any purpose. It is provided "as is" without express
or implied warranty. See the GNU General Public License for more
details.
.
Documents produced by doxygen are derivative works derived from the
input used in their production; they are not affected by this license.
.
On Debian systems, the complete text of the version of the GNU General
Public License distributed with Doxygen can be found in
‘/usr/share/common-licenses/GPL-2’.
License: GPL-2+
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
.
On Debian systems, the complete text of the GNU General Public License
version 2 can be found in ‘/usr/share/common-licenses/GPL-2’.
License: Autoconf
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.
.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
.
As a special exception, the respective Autoconf Macro's copyright owner
gives unlimited permission to copy, distribute and modify the configure
scripts that are the output of Autoconf when processing the Macro. You
need not follow the terms of the GNU General Public License when using
or distributing such scripts, even though portions of the text of the
Macro appear in them. The GNU General Public License (GPL) does govern
all other use of the material that constitutes the Autoconf Macro.
.
This special exception to the GPL applies to versions of the Autoconf
Macro released by the Autoconf Archive. When you make and distribute a
modified version of the Autoconf Macro, you may extend this special
exception to the GPL to apply to your modified version as well.
.
On Debian systems, the complete text of the GNU General Public
License version 3 can be found in ‘/usr/share/common-licenses/GPL-3’.
License: permissive-fsf
This file is free software; the Free Software Foundation
gives unlimited permission to copy and/or distribute it,
with or without modifications, as long as this notice is preserved.
License: permissive-nowarranty
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without warranty of any kind.
@@ -1,13 +0,0 @@
Files: src/js/editline/*
Copyright: 1993, John Doe
1993, Joe Average
License: MPL-1.1 or GPL-2 or LGPL-2.1
License: MPL-1.1
[LICENSE TEXT]
License: GPL-2
[LICENSE TEXT]
License: LGPL-2.1
[LICENSE TEXT]
@@ -1,2 +0,0 @@
FROM scratch
COPY . .
@@ -1,43 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: auditd
Source: https://people.redhat.com/sgrubb/audit/
Files: *
Copyright: 2012-2016 Steve Grubb <sgrubb@redhat.com>
2006-2012 Rik Faith
License: GPL-2
Files: src/libev/*
Copyright: 2007-2009 Marc Alexamder Lehmann
License: GPL-2
Files: lib/*
Copyright: 2005-2008 Steve Grubb <sgrubb@redhat.com>
License: LGPL-2.1
The audit daemon's library libaudit.* is released under LGPL
so that it may be linked with 3rd party software.
.
On Debian systems, refer to /usr/share/common-licenses/LGPL-2.1
for the complete text of the GNU Lesser General Public License.
Files: debian/*
Copyright: 2007-2011 Philipp Matthias Hahn <pmhahn@debian.org>
2012-2016 Laurent Bigonville <bigon@debian.org>
License: GPL-2
License: GPL-2
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2,
as published by the Free Software Foundation.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this package; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
.
On Debian systems, the complete text of the GNU General
Public License can be found in `/usr/share/common-licenses/GPL-1'.
@@ -1,4 +0,0 @@
55f905631797551d4d936a34c7e73474 lib/x86_64-linux-gnu/libz.so.1.2.11
cede84bda30d2380217f97753c8ccf3a usr/share/doc/zlib1g/changelog.Debian.gz
f3c9dafa6da7992c47328b4464f6d122 usr/share/doc/zlib1g/changelog.gz
a4fae96070439a5209a62ae5b8017ab2 usr/share/doc/zlib1g/copyright
@@ -1,21 +0,0 @@
Package: libpam-runtime
Status: install ok installed
Priority: required
Section: admin
Installed-Size: 1016
Maintainer: Steve Langasek <vorlon@debian.org>
Architecture: all
Multi-Arch: foreign
Source: pam
Version: 1.1.8-3.6
Replaces: libpam0g-dev, libpam0g-util
Depends: debconf (>= 0.5) | debconf-2.0, debconf (>= 1.5.19) | cdebconf, libpam-modules (>= 1.0.1-6)
Conflicts: libpam0g-util
Conffiles:
/etc/pam.conf 87fc76f18e98ee7d3848f6b81b3391e5
/etc/pam.d/other 31aa7f2181889ffb00b87df4126d1701
Description: Runtime support for the PAM library
Contains configuration files and directories required for
authentication to work on Debian systems. This package is required
on almost all installations.
Homepage: http://www.linux-pam.org/
@@ -1,4 +0,0 @@
55f905631797551d4d936a34c7e73474 lib/x86_64-linux-gnu/libz.so.1.2.11
cede84bda30d2380217f97753c8ccf3a usr/share/doc/zlib1g/changelog.Debian.gz
f3c9dafa6da7992c47328b4464f6d122 usr/share/doc/zlib1g/changelog.gz
a4fae96070439a5209a62ae5b8017ab2 usr/share/doc/zlib1g/copyright
+44
View File
@@ -0,0 +1,44 @@
/*
Package dpkg provides a concrete Cataloger implementation for Debian package DB status files.
*/
package dpkg
import (
"io"
"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 (c *Cataloger) Name() string {
return "dpkg-cataloger"
}
// SelectFiles returns a set of discovered DPKG status files from the user content source.
func (c *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return c.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all DPKG status files discovered.
func (c *Cataloger) Catalog(contents map[file.Reference]io.Reader) ([]pkg.Package, error) {
return c.cataloger.Catalog(contents)
}
@@ -1,44 +1,40 @@
package deb
package dpkg
import (
"bufio"
"errors"
"fmt"
"io"
"strconv"
"strings"
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
"github.com/mitchellh/mapstructure"
)
// integrity check
var _ common.ParserFn = parseDpkgStatus
var errEndOfPackages = fmt.Errorf("no more packages to read")
// parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed.
func parseDpkgStatus(reader io.Reader) ([]pkg.Package, error) {
func parseDpkgStatus(_ string, reader io.Reader) ([]pkg.Package, error) {
buffedReader := bufio.NewReader(reader)
var packages = make([]pkg.Package, 0)
continueProcessing := true
for continueProcessing {
for {
entry, err := parseDpkgStatusEntry(buffedReader)
if err != nil {
if errors.Is(err, errEndOfPackages) {
continueProcessing = false
} else {
return nil, err
if err == errEndOfPackages {
break
}
return nil, err
}
if entry.Package != "" {
packages = append(packages, pkg.Package{
Name: entry.Package,
Version: entry.Version,
Type: pkg.DebPkg,
MetadataType: pkg.DpkgMetadataType,
Metadata: entry,
})
}
packages = append(packages, pkg.Package{
Name: entry.Package,
Version: entry.Version,
Type: pkg.DebPkg,
Metadata: entry,
})
}
return packages, nil
@@ -46,16 +42,14 @@ func parseDpkgStatus(reader io.Reader) ([]pkg.Package, error) {
// parseDpkgStatusEntry returns an individual Dpkg entry, or returns errEndOfPackages if there are no more packages to parse from the reader.
func parseDpkgStatusEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err error) {
dpkgFields := make(map[string]interface{})
var retErr error
dpkgFields := make(map[string]string)
var key string
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
retErr = errEndOfPackages
break
return pkg.DpkgMetadata{}, errEndOfPackages
}
return pkg.DpkgMetadata{}, err
}
@@ -87,16 +81,18 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err err
dpkgFields[key] = val
default:
// parse a new key
var val interface{}
key, val, err = handleNewKeyValue(line)
if err != nil {
return pkg.DpkgMetadata{}, err
}
if i := strings.Index(line, ":"); i > 0 {
key = strings.TrimSpace(line[0:i])
val := strings.TrimSpace(line[i+1:])
if _, ok := dpkgFields[key]; ok {
return pkg.DpkgMetadata{}, fmt.Errorf("duplicate key discovered: %s", key)
if _, ok := dpkgFields[key]; ok {
return pkg.DpkgMetadata{}, fmt.Errorf("duplicate key discovered: %s", key)
}
dpkgFields[key] = val
} else {
return pkg.DpkgMetadata{}, fmt.Errorf("cannot parse field from line: '%s'", line)
}
dpkgFields[key] = val
}
}
@@ -105,29 +101,5 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err err
return pkg.DpkgMetadata{}, err
}
return entry, retErr
}
// handleNewKeyValue parse a new key-value pair from the given unprocessed line
func handleNewKeyValue(line string) (string, interface{}, error) {
if i := strings.Index(line, ":"); i > 0 {
var key = strings.TrimSpace(line[0:i])
// mapstruct cant handle "-"
key = strings.ReplaceAll(key, "-", "")
val := strings.TrimSpace(line[i+1:])
// further processing of values based on the key that was discovered
switch key {
case "InstalledSize":
numVal, err := strconv.Atoi(val)
if err != nil {
return "", nil, fmt.Errorf("bad installed-size value=%q: %w", val, err)
}
return key, numVal, nil
default:
return key, val, nil
}
}
return "", nil, fmt.Errorf("cannot parse field from line: '%s'", line)
return entry, nil
}
@@ -1,4 +1,4 @@
package deb
package dpkg
import (
"bufio"
@@ -24,19 +24,17 @@ func TestSinglePackage(t *testing.T) {
{
name: "Test Single Package",
expected: pkg.DpkgMetadata{
Package: "apt",
Source: "apt-dev",
Version: "1.8.2",
Architecture: "amd64",
InstalledSize: 4064,
Maintainer: "APT Development Team <deity@lists.debian.org>",
Package: "apt",
Source: "apt-dev",
Version: "1.8.2",
Architecture: "amd64",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
file, err := os.Open("test-fixtures/status/single")
file, err := os.Open("test-fixtures/single")
if err != nil {
t.Fatal("Unable to read test_fixtures/single: ", err)
}
@@ -68,19 +66,15 @@ func TestMultiplePackages(t *testing.T) {
name: "Test Multiple Package",
expected: []pkg.DpkgMetadata{
{
Package: "tzdata",
Version: "2020a-0+deb10u1",
Source: "tzdata-dev",
Architecture: "all",
InstalledSize: 3036,
Maintainer: "GNU Libc Maintainers <debian-glibc@lists.debian.org>",
Package: "tzdata",
Version: "2020a-0+deb10u1",
Source: "tzdata-dev",
Architecture: "all",
},
{
Package: "util-linux",
Version: "2.33.1-0.1",
Architecture: "amd64",
InstalledSize: 4327,
Maintainer: "LaMont Jones <lamont@debian.org>",
Package: "util-linux",
Version: "2.33.1-0.1",
Architecture: "amd64",
},
},
},
@@ -88,7 +82,7 @@ func TestMultiplePackages(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
file, err := os.Open("test-fixtures/status/multiple")
file, err := os.Open("test-fixtures/multiple")
if err != nil {
t.Fatal("Unable to read: ", err)
}
@@ -99,7 +93,7 @@ func TestMultiplePackages(t *testing.T) {
}
}()
pkgs, err := parseDpkgStatus(file)
pkgs, err := parseDpkgStatus(file.Name(), file)
if err != nil {
t.Fatal("Unable to read file contents: ", err)
}
+30 -3
View File
@@ -4,14 +4,41 @@ Package golang provides a concrete Cataloger implementation for go.mod files.
package golang
import (
"io"
"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"
)
// NewGoModCataloger returns a new Go module cataloger object.
func NewGoModCataloger() *common.GenericCataloger {
// 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 {
globParsers := map[string]common.ParserFn{
"**/go.mod": parseGoMod,
}
return common.NewGenericCataloger(nil, globParsers, "go-cataloger")
return &Cataloger{
cataloger: common.NewGenericCataloger(nil, globParsers),
}
}
// Name returns a string that uniquely describes this cataloger.
func (c *Cataloger) Name() string {
return "go-cataloger"
}
// SelectFiles returns a set of discovered go.mod files from the user content source.
func (c *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return c.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all go.mod files discovered.
func (c *Cataloger) Catalog(contents map[file.Reference]io.Reader) ([]pkg.Package, error) {
return c.cataloger.Catalog(contents)
}
+2 -1
View File
@@ -6,8 +6,9 @@ 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.
+41 -85
View File
@@ -10,100 +10,36 @@ import (
"github.com/anchore/syft/syft/pkg"
)
// nameAndVersionPattern finds the package name and version (as named capture
// groups) in a string. The pattern's strategy is to start at the beginning of
// the string, and for every next dash-delimited group, consider the group to be
// a continuation of the package name, unless the group begins with a number or
// matches any of a specified set of "version-indicating" patterns. When a given
// group meets this criterion, consider the group and the remainder of the
// string to be the package version.
//
// Regex components of note:
//
// (?Ui) ... Sets the "U" and the "i" options for this Regex —— (ungreedy,
// and case-insensitive, respectively). "Ungreedy" is important so that the '*' that trails the package name
// component doesn't consume the rest of the string.
//
// [[:alpha:]][[:word:]]* ... Matches any word, and the word can include "word" characters (
// which includes numbers and underscores), but the first character of the word MUST be a letter.
//
// (?:\.[[:alpha:]][[:word:]]*)* ... This looks redundant, but it's not. It
// extends the previous pattern such that the net effect of both components is
// that words can also include a period and more words (thus, when combined, not
// only is "something" matched, but so is "com.prefix.thing"
//
// (?:\d.*|(?:build\d*.*)|(?:rc?\d+(?:^[[:alpha:]].*)?)) ...
// This match group covers the "version-indicating" patterns mentioned in the above description. Given the pipes (
// '|'), this functions as a series of 'OR'-joined conditions:
//
// \d.* ... "If it starts with a numeric digit, this is a version, no matter what follows."
// build\d*.* ... "If it starts with "build" and then a numeric digit immediately after, this is a version."
// rc?\d+(?:^[[:alpha:]].*)? ... "If it starts with "r" or "rc" and then one or more numeric digits immediately
// after, but no alpha characters right after that (in the same word), this is a version."
//
// Match examples:
// some-package-4.0.1 --> name="some-package", version="4.0.1"
// prefix.thing-4 --> name="prefix.thing", version="4"
// my-http2-server-5 --> name="my-http2-server", version="5"
// jetpack-build235-rc5 --> name="jetpack", version="build2.0-rc5"
// ironman-r4-2009 --> name="ironman", version="r4-2009"
var nameAndVersionPattern = regexp.MustCompile(`(?Ui)^(?P<name>(?:[[:alpha:]][[:word:]]*(?:\.[[:alpha:]][[:word:]]*)*-?)+)(?:-(?P<version>(?:\d.*|(?:build\d*.*)|(?:rc?\d+(?:^[[:alpha:]].*)?))))?$`)
// match examples:
// pkg-extra-field-4.3.2-rc1 --> match(name=pkg-extra-field version=4.3.2-rc1)
// pkg-extra-field-4.3-rc1 --> match(name=pkg-extra-field version=4.3-rc1)
// pkg-extra-field-4.3 --> match(name=pkg-extra-field version=4.3)
var versionPattern = regexp.MustCompile(`(?P<name>.+)-(?P<version>(\d+\.)?(\d+\.)?(\*|\d+)(-[a-zA-Z0-9\-\.]+)*)`)
type archiveFilename struct {
raw string
name string
version string
}
// TODO: Remove this method once we're using Go 1.15+.
//
// Go 1.15 introduces a `SubexpIndex` method for the Regexp type that would let
// this code be made more elegant. Once we've reached 1.15, we should eliminate
// this function in favor of that method.
func subexpIndex(re *regexp.Regexp, name string) int {
for i, subexpName := range re.SubexpNames() {
if subexpName == name {
return i
}
}
return -1
}
func getSubexp(matches []string, subexpName string, re *regexp.Regexp, raw string) string {
if len(matches) < 1 {
log.Warnf("unexpectedly empty matches for archive '%s'", raw)
return ""
}
index := subexpIndex(re, subexpName)
if index < 1 {
log.Warnf("unexpected index of '%s' capture group for Java archive '%s'", subexpName, raw)
return ""
}
// Prevent out-of-range panic
if len(matches) < index+1 {
log.Warnf("no match found for '%s' in '%s'", subexpName, matches[0])
return ""
}
return matches[index]
raw string
fields []map[string]string
}
func newJavaArchiveFilename(raw string) archiveFilename {
// trim the file extension and remove any path prefixes
cleanedFileName := strings.TrimSuffix(filepath.Base(raw), filepath.Ext(raw))
name := strings.TrimSuffix(filepath.Base(raw), filepath.Ext(raw))
matches := nameAndVersionPattern.FindStringSubmatch(cleanedFileName)
name := getSubexp(matches, "name", nameAndVersionPattern, raw)
version := getSubexp(matches, "version", nameAndVersionPattern, raw)
matches := versionPattern.FindAllStringSubmatch(name, -1)
fields := make([]map[string]string, 0)
for _, match := range matches {
item := make(map[string]string)
for i, name := range versionPattern.SubexpNames() {
if i != 0 && name != "" {
item[name] = match[i]
}
}
fields = append(fields, item)
}
return archiveFilename{
raw: raw,
name: name,
version: version,
raw: raw,
fields: fields,
}
}
@@ -121,3 +57,23 @@ func (a archiveFilename) pkgType() pkg.Type {
return pkg.UnknownPkg
}
}
func (a archiveFilename) version() string {
if len(a.fields) > 1 {
log.Errorf("discovered multiple name-version pairings from %q: %+v", a.raw, a.fields)
return ""
} else if len(a.fields) < 1 {
return ""
}
return a.fields[0]["version"]
}
func (a archiveFilename) name() string {
// there should be only one name, if there is more or less then something is wrong
if len(a.fields) != 1 {
return ""
}
return a.fields[0]["name"]
}
+6 -75
View File
@@ -1,10 +1,9 @@
package java
import (
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/sergi/go-diff/diffmatchpatch"
"testing"
)
func TestExtractInfoFromJavaArchiveFilename(t *testing.T) {
@@ -57,98 +56,30 @@ func TestExtractInfoFromJavaArchiveFilename(t *testing.T) {
name: "pkg-extra-field-maven",
ty: pkg.JenkinsPluginPkg,
},
{
filename: "/some/path-with-version-5.4.3/wagon-webdav-1.0.2-beta-2.2.3a-hudson.jar",
version: "1.0.2-beta-2.2.3a-hudson",
extension: "jar",
name: "wagon-webdav",
ty: pkg.JavaPkg,
},
{
filename: "/some/path-with-version-5.4.3/wagon-webdav-1.0.2-beta-2.2.3-hudson.jar",
version: "1.0.2-beta-2.2.3-hudson",
extension: "jar",
name: "wagon-webdav",
ty: pkg.JavaPkg,
},
{
filename: "/some/path-with-version-5.4.3/windows-remote-command-1.0.jar",
version: "1.0",
extension: "jar",
name: "windows-remote-command",
ty: pkg.JavaPkg,
},
{
filename: "/some/path-with-version-5.4.3/wagon-http-lightweight-1.0.5-beta-2.jar",
version: "1.0.5-beta-2",
extension: "jar",
name: "wagon-http-lightweight",
ty: pkg.JavaPkg,
},
{
filename: "/hudson.war:WEB-INF/lib/commons-jelly-1.1-hudson-20100305.jar",
version: "1.1-hudson-20100305",
extension: "jar",
name: "commons-jelly",
ty: pkg.JavaPkg,
},
{
filename: "/hudson.war:WEB-INF/lib/jtidy-4aug2000r7-dev-hudson-1.jar",
version: "4aug2000r7-dev-hudson-1",
extension: "jar",
name: "jtidy",
ty: pkg.JavaPkg,
},
{
filename: "/hudson.war:WEB-INF/lib/trilead-ssh2-build212-hudson-5.jar",
version: "build212-hudson-5",
extension: "jar",
name: "trilead-ssh2",
ty: pkg.JavaPkg,
},
{
filename: "/hudson.war:WEB-INF/lib/guava-r06.jar",
version: "r06",
extension: "jar",
name: "guava",
ty: pkg.JavaPkg,
},
{
filename: "BOOT-INF/lib/spring-data-r2dbc-1.1.0.RELEASE.jar", // Regression: https://github.com/anchore/syft/issues/255
version: "1.1.0.RELEASE",
extension: "jar",
name: "spring-data-r2dbc",
ty: pkg.JavaPkg,
},
}
for _, test := range tests {
t.Run(test.filename, func(t *testing.T) {
obj := newJavaArchiveFilename(test.filename)
ty := obj.pkgType()
if ty != test.ty {
t.Errorf("mismatched type: %+v != %v", ty, test.ty)
}
version := obj.version
version := obj.version()
if version != test.version {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(test.version, version, true)
diffs := dmp.DiffMain(version, test.version, true)
t.Errorf("mismatched version:\n%s", dmp.DiffPrettyText(diffs))
}
extension := obj.extension()
if extension != test.extension {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(test.extension, extension, true)
diffs := dmp.DiffMain(extension, test.extension, true)
t.Errorf("mismatched extension:\n%s", dmp.DiffPrettyText(diffs))
}
name := obj.name
name := obj.name()
if name != test.name {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(test.name, name, true)
diffs := dmp.DiffMain(name, test.name, true)
t.Errorf("mismatched name:\n%s", dmp.DiffPrettyText(diffs))
}
})
+19 -66
View File
@@ -5,8 +5,6 @@ import (
"io"
"strings"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft/cataloger/common"
@@ -66,17 +64,13 @@ func newJavaArchiveParser(virtualPath string, reader io.Reader, detectNested boo
return nil, cleanupFn, fmt.Errorf("unable to read files from java archive: %w", err)
}
// fetch the last element of the virtual path
virtualElements := strings.Split(virtualPath, ":")
currentFilepath := virtualElements[len(virtualElements)-1]
return &archiveParser{
discoveredPkgs: internal.NewStringSet(),
fileManifest: fileManifest,
virtualPath: virtualPath,
archivePath: archivePath,
contentPath: contentPath,
fileInfo: newJavaArchiveFilename(currentFilepath),
fileInfo: newJavaArchiveFilename(virtualPath),
detectNested: detectNested,
}, cleanupFn, nil
}
@@ -124,7 +118,7 @@ func (j *archiveParser) parse() ([]pkg.Package, error) {
// discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages.
func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
// search and parse java manifest files
manifestMatches := j.fileManifest.GlobMatch(manifestGlob)
manifestMatches := j.fileManifest.GlobMatch(manifestPath)
if len(manifestMatches) > 1 {
return nil, fmt.Errorf("found multiple manifests in the jar: %+v", manifestMatches)
} else if len(manifestMatches) == 0 {
@@ -140,27 +134,24 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
// parse the manifest file into a rich object
manifestContents := contents[manifestMatches[0]]
manifest, err := parseJavaManifest(j.archivePath, strings.NewReader(manifestContents))
manifest, err := parseJavaManifest(strings.NewReader(manifestContents))
if err != nil {
return nil, fmt.Errorf("failed to parse java manifest (%s): %w", j.virtualPath, err)
}
return &pkg.Package{
Name: selectName(manifest, j.fileInfo),
Version: selectVersion(manifest, j.fileInfo),
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Name: selectName(manifest, j.fileInfo),
Version: selectVersion(manifest, j.fileInfo),
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
VirtualPath: j.virtualPath,
Manifest: manifest,
Manifest: manifest,
},
}, nil
}
// discoverPkgsFromPomProperties parses Maven POM properties for a given parent package, returning all listed Java packages found and
// associating each discovered package to the given parent package.
// nolint:funlen,gocognit
func (j *archiveParser) discoverPkgsFromPomProperties(parentPkg *pkg.Package) ([]pkg.Package, error) {
var pkgs = make([]pkg.Package, 0)
parentKey := uniquePkgKey(parentPkg)
@@ -182,22 +173,13 @@ func (j *archiveParser) discoverPkgsFromPomProperties(parentPkg *pkg.Package) ([
if propsObj.Version != "" && propsObj.ArtifactID != "" {
// TODO: if there is no parentPkg (no java manifest) one of these poms could be the parent. We should discover the right parent and attach the correct info accordingly to each discovered package
// keep the artifact name within the virtual path if this package does not match the parent package
vPathSuffix := ""
if !strings.HasPrefix(propsObj.ArtifactID, parentPkg.Name) {
vPathSuffix += ":" + propsObj.ArtifactID
}
virtualPath := j.virtualPath + vPathSuffix
// discovered props = new package
p := pkg.Package{
Name: propsObj.ArtifactID,
Version: propsObj.Version,
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Name: propsObj.ArtifactID,
Version: propsObj.Version,
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
VirtualPath: virtualPath,
PomProperties: propsObj,
Parent: parentPkg,
},
@@ -205,35 +187,16 @@ func (j *archiveParser) discoverPkgsFromPomProperties(parentPkg *pkg.Package) ([
pkgKey := uniquePkgKey(&p)
// the name/version pair matches...
matchesParentPkg := pkgKey == parentKey
// the virtual path matches...
matchesParentPkg = matchesParentPkg || parentPkg.Metadata.(pkg.JavaMetadata).VirtualPath == virtualPath
// the pom artifactId has the parent name or vice versa
if propsObj.ArtifactID != "" {
matchesParentPkg = matchesParentPkg || strings.Contains(parentPkg.Name, propsObj.ArtifactID) || strings.Contains(propsObj.ArtifactID, parentPkg.Name)
}
if matchesParentPkg {
if !j.discoveredPkgs.Contains(pkgKey) {
// only keep packages we haven't seen yet
pkgs = append(pkgs, p)
} else if pkgKey == parentKey {
// we've run across more information about our parent package, add this info to the parent package metadata
// the pom properties is typically a better source of information for name and version than the manifest
if p.Name != parentPkg.Name {
parentPkg.Name = p.Name
}
if p.Version != parentPkg.Version {
parentPkg.Version = p.Version
}
parentMetadata, ok := parentPkg.Metadata.(pkg.JavaMetadata)
if ok {
parentMetadata.PomProperties = propsObj
parentPkg.Metadata = parentMetadata
}
} else if !j.discoveredPkgs.Contains(pkgKey) {
// only keep packages we haven't seen yet (and are not related to the parent package)
pkgs = append(pkgs, p)
}
}
}
@@ -251,28 +214,18 @@ func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) (
}
// search and parse pom.properties files & fetch the contents
openers, err := file.ExtractFromZipToUniqueTempFile(j.archivePath, j.contentPath, j.fileManifest.GlobMatch(archiveFormatGlobs...)...)
readers, err := file.ExtractFromZipToUniqueTempFile(j.archivePath, j.contentPath, j.fileManifest.GlobMatch(archiveFormatGlobs...)...)
if err != nil {
return nil, fmt.Errorf("unable to extract files from zip: %w", err)
}
// discover nested artifacts
for archivePath, archiveOpener := range openers {
archiveReadCloser, err := archiveOpener.Open()
if err != nil {
return nil, fmt.Errorf("unable to open archived file from tempdir: %w", err)
}
for archivePath, archiveReader := range readers {
nestedPath := fmt.Sprintf("%s:%s", j.virtualPath, archivePath)
nestedPkgs, err := parseJavaArchive(nestedPath, archiveReadCloser)
nestedPkgs, err := parseJavaArchive(nestedPath, archiveReader)
if err != nil {
if closeErr := archiveReadCloser.Close(); closeErr != nil {
log.Warnf("unable to close archived file from tempdir: %+v", closeErr)
}
return nil, fmt.Errorf("unable to process nested java archive (%s): %w", archivePath, err)
}
if err = archiveReadCloser.Close(); err != nil {
return nil, fmt.Errorf("unable to close archived file from tempdir: %w", err)
}
// attach the parent package to all discovered packages that are not already associated with a java archive
for _, p := range nestedPkgs {
+84 -61
View File
@@ -78,6 +78,51 @@ func generateJavaBuildFixture(t *testing.T, fixturePath string) {
}
}
func TestSelectName(t *testing.T) {
tests := []struct {
desc string
manifest pkg.JavaManifest
archive archiveFilename
expected string
}{
{
desc: "name from Implementation-Title",
archive: archiveFilename{},
manifest: pkg.JavaManifest{
Name: "",
SpecTitle: "",
ImplTitle: "maven-wrapper",
},
expected: "maven-wrapper",
},
{
desc: "Implementation-Title does not override",
manifest: pkg.JavaManifest{
Name: "Foo",
SpecTitle: "",
ImplTitle: "maven-wrapper",
},
archive: archiveFilename{
fields: []map[string]string{
{"name": "omg"},
},
},
expected: "omg",
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
result := selectName(&test.manifest, test.archive)
if result != test.expected {
t.Errorf("mismatch in names: '%s' != '%s'", result, test.expected)
}
})
}
}
func TestParseJar(t *testing.T) {
tests := []struct {
fixture string
@@ -92,20 +137,17 @@ func TestParseJar(t *testing.T) {
},
expected: map[string]pkg.Package{
"example-jenkins-plugin": {
Name: "example-jenkins-plugin",
Version: "1.0-SNAPSHOT",
Language: pkg.Java,
Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType,
Name: "example-jenkins-plugin",
Version: "1.0-SNAPSHOT",
Language: pkg.Java,
Type: pkg.JenkinsPluginPkg,
Metadata: pkg.JavaMetadata{
VirtualPath: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
"Specification-Title": "The Jenkins Plugins Parent POM Project",
"Implementation-Title": "example-jenkins-plugin",
"Implementation-Version": "1.0-SNAPSHOT",
// extra fields...
ManifestVersion: "1.0",
SpecTitle: "The Jenkins Plugins Parent POM Project",
ImplTitle: "example-jenkins-plugin",
ImplVersion: "1.0-SNAPSHOT",
Extra: map[string]string{
"Archiver-Version": "Plexus Archiver",
"Plugin-License-Url": "https://opensource.org/licenses/MIT",
"Plugin-License-Name": "MIT License",
@@ -139,17 +181,13 @@ func TestParseJar(t *testing.T) {
fixture: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
expected: map[string]pkg.Package{
"example-java-app-gradle": {
Name: "example-java-app-gradle",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Name: "example-java-app-gradle",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
},
ManifestVersion: "1.0",
},
},
},
@@ -162,17 +200,14 @@ func TestParseJar(t *testing.T) {
},
expected: map[string]pkg.Package{
"example-java-app-maven": {
Name: "example-java-app-maven",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Name: "example-java-app-maven",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
// extra fields...
ManifestVersion: "1.0",
Extra: map[string]string{
"Archiver-Version": "Plexus Archiver",
"Created-By": "Apache Maven 3.6.3",
"Built-By": "?",
@@ -189,15 +224,11 @@ func TestParseJar(t *testing.T) {
},
},
"joda-time": {
Name: "joda-time",
Version: "2.9.2",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Name: "joda-time",
Version: "2.9.2",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
// ensure that nested packages with different names than that of the parent are appended as
// a suffix on the virtual path
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar:joda-time",
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/joda-time/joda-time/pom.properties",
GroupID: "joda-time",
@@ -264,11 +295,11 @@ func TestParseJar(t *testing.T) {
metadata := a.Metadata.(pkg.JavaMetadata)
metadata.Parent = nil
// ignore select fields (only works for the main section)
// ignore select fields
for _, field := range test.ignoreExtras {
if metadata.Manifest != nil && metadata.Manifest.Main != nil {
if _, ok := metadata.Manifest.Main[field]; ok {
delete(metadata.Manifest.Main, field)
if metadata.Manifest != nil && metadata.Manifest.Extra != nil {
if _, ok := metadata.Manifest.Extra[field]; ok {
delete(metadata.Manifest.Extra, field)
}
}
}
@@ -496,7 +527,7 @@ func TestParseNestedJar(t *testing.T) {
t.Fatalf("failed to parse java archive: %+v", err)
}
expectedNameVersionPairSet := internal.NewStringSet()
nameVersionPairSet := internal.NewStringSet()
makeKey := func(p *pkg.Package) string {
if p == nil {
@@ -506,31 +537,23 @@ func TestParseNestedJar(t *testing.T) {
}
for _, e := range test.expected {
expectedNameVersionPairSet.Add(makeKey(&e))
nameVersionPairSet.Add(makeKey(&e))
}
actualNameVersionPairSet := internal.NewStringSet()
for _, a := range actual {
key := makeKey(&a)
actualNameVersionPairSet.Add(key)
if !expectedNameVersionPairSet.Contains(key) {
t.Errorf("extra package: %s", a)
if len(actual) != len(nameVersionPairSet) {
for _, a := range actual {
t.Log(" ", a)
}
}
for _, key := range expectedNameVersionPairSet.ToSlice() {
if !actualNameVersionPairSet.Contains(key) {
t.Errorf("missing package: %s", key)
}
}
if len(actual) != len(expectedNameVersionPairSet) {
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expectedNameVersionPairSet))
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(nameVersionPairSet))
}
for _, a := range actual {
actualKey := makeKey(&a)
if !nameVersionPairSet.Contains(actualKey) {
t.Errorf("unexpected pkg: %q", actualKey)
}
metadata := a.Metadata.(pkg.JavaMetadata)
if actualKey == "spring-boot|0.0.1-SNAPSHOT" {
if metadata.Parent != nil {
@@ -540,7 +563,7 @@ func TestParseNestedJar(t *testing.T) {
if metadata.Parent == nil {
t.Errorf("unassigned error for pkg=%q", actualKey)
} else if makeKey(metadata.Parent) != "spring-boot|0.0.1-SNAPSHOT" {
// NB: this is a hard-coded condition to simplify the test harness to account for https://github.com/micrometer-metrics/micrometer/issues/1785
// NB: this is a hard-coded condition to simplify the test harness
if a.Name == "pcollections" {
if metadata.Parent.Name != "micrometer-core" {
t.Errorf("nested 'pcollections' pkg has wrong parent: %q", metadata.Parent.Name)
+30 -3
View File
@@ -4,15 +4,42 @@ Package java provides a concrete Cataloger implementation for Java archives (jar
package java
import (
"io"
"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"
)
// NewJavaCataloger returns a new Java archive cataloger object.
func NewJavaCataloger() *common.GenericCataloger {
// 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 {
globParsers := make(map[string]common.ParserFn)
for _, pattern := range archiveFormatGlobs {
globParsers[pattern] = parseJavaArchive
}
return common.NewGenericCataloger(nil, globParsers, "java-cataloger")
return &Cataloger{
cataloger: common.NewGenericCataloger(nil, globParsers),
}
}
// Name returns a string that uniquely describes this cataloger.
func (c *Cataloger) Name() string {
return "java-cataloger"
}
// SelectFiles returns a set of discovered Java archive files from the user content source.
func (c *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return c.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all Java archive files discovered.
func (c *Cataloger) Catalog(contents map[file.Reference]io.Reader) ([]pkg.Package, error) {
return c.cataloger.Catalog(contents)
}
+40 -63
View File
@@ -4,41 +4,25 @@ import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/mitchellh/mapstructure"
)
const manifestGlob = "/META-INF/MANIFEST.MF"
const manifestPath = "META-INF/MANIFEST.MF"
// nolint:funlen
// parseJavaManifest takes MANIFEST.MF file content and returns sections of parsed key/value pairs.
// For more information: https://docs.oracle.com/en/java/javase/11/docs/specs/jar/jar.html#jar-manifest
func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error) {
func parseJavaManifest(reader io.Reader) (*pkg.JavaManifest, error) {
var manifest pkg.JavaManifest
sections := []map[string]string{
make(map[string]string),
}
currentSection := 0
manifestMap := make(map[string]string)
scanner := bufio.NewScanner(reader)
var lastKey string
for scanner.Scan() {
line := scanner.Text()
// empty lines denote section separators
// ignore empty lines
if strings.TrimSpace(line) == "" {
currentSection++
// we don't want to allocate a new section map that wont necessarily be used, do that once there is
// a non-empty line to process
// do not process line continuations after this
lastKey = ""
continue
} else if currentSection >= len(sections) {
sections = append(sections, make(map[string]string))
}
if line[0] == ' ' {
@@ -46,7 +30,7 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error)
if lastKey == "" {
return nil, fmt.Errorf("found continuation with no previous key (%s)", line)
}
sections[currentSection][lastKey] += strings.TrimSpace(line)
manifestMap[lastKey] += strings.TrimSpace(line)
} else {
// this is a new key-value pair
idx := strings.Index(line, ":")
@@ -56,9 +40,9 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error)
key := strings.TrimSpace(line[0:idx])
value := strings.TrimSpace(line[idx+1:])
sections[currentSection][key] = value
manifestMap[key] = value
// keep track of key for potential future continuations
// keep track of key for future continuations
lastKey = key
}
}
@@ -67,24 +51,14 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error)
return nil, fmt.Errorf("unable to read java manifest: %w", err)
}
if len(sections) > 0 {
manifest.Main = sections[0]
if len(sections) > 1 {
manifest.NamedSections = make(map[string]map[string]string)
for i, s := range sections[1:] {
name, ok := s["Name"]
if !ok {
// per the manifest spec (https://docs.oracle.com/en/java/javase/11/docs/specs/jar/jar.html#jar-manifest)
// this should never happen. If it does we want to know about it, but not necessarily stop
// cataloging entirely... for this reason we only log.
log.Warnf("java manifest section found without a name: %s", path)
name = strconv.Itoa(i)
} else {
delete(s, "Name")
}
manifest.NamedSections[name] = s
}
}
if err := mapstructure.Decode(manifestMap, &manifest); err != nil {
return nil, fmt.Errorf("unable to parse java manifest: %w", err)
}
// clean select fields
if strings.Trim(manifest.ImplVersion, " ") != "" {
// transform versions with dates attached to just versions (e.g. "1.3 2244 October 5 2008" --> "1.3")
manifest.ImplVersion = strings.Split(manifest.ImplVersion, " ")[0]
}
return &manifest, nil
@@ -93,23 +67,26 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error)
func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
var name string
switch {
case filenameObj.name != "":
name = filenameObj.name
case manifest.Main["Name"] != "":
case filenameObj.name() != "":
name = filenameObj.name()
case manifest.Name != "":
// Manifest original spec...
name = manifest.Main["Name"]
case manifest.Main["Bundle-Name"] != "":
name = manifest.Name
case manifest.Extra["Bundle-Name"] != "":
// BND tooling...
name = manifest.Main["Bundle-Name"]
case manifest.Main["Short-Name"] != "":
name = manifest.Extra["Bundle-Name"]
case manifest.Extra["Short-Name"] != "":
// Jenkins...
name = manifest.Main["Short-Name"]
case manifest.Main["Extension-Name"] != "":
name = manifest.Extra["Short-Name"]
case manifest.Extra["Extension-Name"] != "":
// Jenkins...
name = manifest.Main["Extension-Name"]
case manifest.Main["Implementation-Title"] != "":
// last ditch effort...
name = manifest.Main["Implementation-Title"]
name = manifest.Extra["Extension-Name"]
}
// in situations where we hit this point and no name was
// determined, look at the Implementation-Title
if name == "" && manifest.ImplTitle != "" {
name = manifest.ImplTitle
}
return name
}
@@ -117,14 +94,14 @@ func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string
func selectVersion(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
var version string
switch {
case filenameObj.version != "":
version = filenameObj.version
case manifest.Main["Implementation-Version"] != "":
version = manifest.Main["Implementation-Version"]
case manifest.Main["Specification-Version"] != "":
version = manifest.Main["Specification-Version"]
case manifest.Main["Plugin-Version"] != "":
version = manifest.Main["Plugin-Version"]
case manifest.ImplVersion != "":
version = manifest.ImplVersion
case filenameObj.version() != "":
version = filenameObj.version()
case manifest.SpecVersion != "":
version = manifest.SpecVersion
case manifest.Extra["Plugin-Version"] != "":
version = manifest.Extra["Plugin-Version"]
}
return version
}
+22 -78
View File
@@ -2,11 +2,10 @@ package java
import (
"encoding/json"
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
"os"
"testing"
)
func TestParseJavaManifest(t *testing.T) {
@@ -17,62 +16,49 @@ func TestParseJavaManifest(t *testing.T) {
{
fixture: "test-fixtures/manifest/small",
expected: pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
},
ManifestVersion: "1.0",
},
},
{
fixture: "test-fixtures/manifest/standard-info",
expected: pkg.JavaManifest{
Main: map[string]string{
"Name": "the-best-name",
"Manifest-Version": "1.0",
"Specification-Title": "the-spec-title",
"Specification-Version": "the-spec-version",
"Specification-Vendor": "the-spec-vendor",
"Implementation-Title": "the-impl-title",
"Implementation-Version": "the-impl-version",
"Implementation-Vendor": "the-impl-vendor",
},
ManifestVersion: "1.0",
Name: "the-best-name",
SpecTitle: "the-spec-title",
SpecVersion: "the-spec-version",
SpecVendor: "the-spec-vendor",
ImplTitle: "the-impl-title",
ImplVersion: "the-impl-version",
ImplVendor: "the-impl-vendor",
},
},
{
fixture: "test-fixtures/manifest/extra-info",
expected: pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
ManifestVersion: "1.0",
Extra: map[string]string{
"Archiver-Version": "Plexus Archiver",
"Build-Jdk": "14.0.1",
"Built-By": "?",
"Created-By": "Apache Maven 3.6.3",
},
NamedSections: map[string]map[string]string{
"thing-1": {
"Built-By": "?",
},
"1": {
"Build-Jdk": "14.0.1",
"Main-Class": "hello.HelloWorld",
},
"Main-Class": "hello.HelloWorld",
},
},
},
{
fixture: "test-fixtures/manifest/continuation",
expected: pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
"Plugin-ScmUrl": "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin",
ManifestVersion: "1.0",
Extra: map[string]string{
"Plugin-ScmUrl": "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin",
},
},
},
{
// regression test, we should always keep the full version
fixture: "test-fixtures/manifest/version-with-date",
expected: pkg.JavaManifest{
Main: map[string]string{
"Manifest-Version": "1.0",
"Implementation-Version": "1.3 2244 October 5 2005",
},
ManifestVersion: "1.0",
ImplVersion: "1.3", // ensure the date is stripped off during processing
},
},
}
@@ -84,7 +70,7 @@ func TestParseJavaManifest(t *testing.T) {
t.Fatalf("could not open fixture: %+v", err)
}
actual, err := parseJavaManifest(test.fixture, fixture)
actual, err := parseJavaManifest(fixture)
if err != nil {
t.Fatalf("failed to parse manifest: %+v", err)
}
@@ -105,45 +91,3 @@ func TestParseJavaManifest(t *testing.T) {
})
}
}
func TestSelectName(t *testing.T) {
tests := []struct {
desc string
manifest pkg.JavaManifest
archive archiveFilename
expected string
}{
{
desc: "Get name from Implementation-Title",
archive: archiveFilename{},
manifest: pkg.JavaManifest{
Main: map[string]string{
"Implementation-Title": "maven-wrapper",
},
},
expected: "maven-wrapper",
},
{
desc: "Implementation-Title does not override name from filename",
manifest: pkg.JavaManifest{
Main: map[string]string{
"Name": "foo",
"Implementation-Title": "maven-wrapper",
},
},
archive: newJavaArchiveFilename("/something/omg.jar"),
expected: "omg",
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
result := selectName(&test.manifest, test.archive)
if result != test.expected {
t.Errorf("mismatch in names: '%s' != '%s'", result, test.expected)
}
})
}
}
+1 -2
View File
@@ -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: %+v", err)
log.Errorf("unable to cleanup jar tempdir: %w", err)
}
}
@@ -35,7 +35,6 @@ func saveArchiveToTmp(reader io.Reader) (string, string, func(), error) {
if err != nil {
return contentDir, "", cleanupFn, fmt.Errorf("unable to create archive: %w", err)
}
defer archiveFile.Close()
_, err = io.Copy(archiveFile, reader)
if err != nil {
@@ -1,3 +1,4 @@
<?jelly escape-by-default='true'?>
<div>
TODO
</div>
@@ -2,7 +2,6 @@ Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven 3.6.3
Name: thing-1
Built-By: ?
Build-Jdk: 14.0.1
+28 -10
View File
@@ -4,24 +4,42 @@ Package javascript provides a concrete Cataloger implementation for JavaScript e
package javascript
import (
"io"
"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"
)
// NewJavascriptPackageCataloger returns a new JavaScript cataloger object based on detection of npm based packages.
func NewJavascriptPackageCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/package.json": parsePackageJSON,
}
return common.NewGenericCataloger(nil, globParsers, "javascript-package-cataloger")
// Cataloger catalogs pkg.YarnPkg and pkg.NpmPkg Package Types defined in package-lock.json and yarn.lock files.
type Cataloger struct {
cataloger common.GenericCataloger
}
// NewJavascriptLockCataloger returns a new Javascript cataloger object base on package lock files.
func NewJavascriptLockCataloger() *common.GenericCataloger {
// New returns a new JavaScript cataloger object.
func New() *Cataloger {
globParsers := map[string]common.ParserFn{
"**/package-lock.json": parsePackageLock,
"**/yarn.lock": parseYarnLock,
}
return common.NewGenericCataloger(nil, globParsers, "javascript-lock-cataloger")
return &Cataloger{
cataloger: common.NewGenericCataloger(nil, globParsers),
}
}
// Name returns a string that uniquely describes this cataloger.
func (c *Cataloger) Name() string {
return "javascript-cataloger"
}
// SelectFiles returns a set of discovered Javascript ecosystem files from the user content source.
func (c *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return c.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all Javascript ecosystem files discovered.
func (c *Cataloger) Catalog(contents map[file.Reference]io.Reader) ([]pkg.Package, error) {
return c.cataloger.Catalog(contents)
}
@@ -1,197 +0,0 @@
package javascript
import (
"encoding/json"
"errors"
"fmt"
"io"
"regexp"
"github.com/anchore/syft/internal"
"github.com/mitchellh/mapstructure"
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
)
// integrity check
var _ common.ParserFn = parsePackageLock
// PackageJSON represents a JavaScript package.json file
type PackageJSON struct {
Version string `json:"version"`
Latest []string `json:"latest"`
Author Author `json:"author"`
License json.RawMessage `json:"license"`
Licenses []license `json:"licenses,omitempty"`
Name string `json:"name"`
Homepage string `json:"homepage"`
Description string `json:"description"`
Dependencies map[string]string `json:"dependencies"`
Repository Repository `json:"repository"`
}
type Author struct {
Name string `json:"name" mapstruct:"name"`
Email string `json:"email" mapstruct:"email"`
URL string `json:"url" mapstruct:"url"`
}
type Repository struct {
Type string `json:"type" mapstructure:"type"`
URL string `json:"url" mapstructure:"url"`
}
// match example: "author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)"
// ---> name: "Isaac Z. Schlueter" email: "i@izs.me" url: "http://blog.izs.me"
var authorPattern = regexp.MustCompile(`^\s*(?P<name>[^<(]*)(\s+<(?P<email>.*)>)?(\s\((?P<url>.*)\))?\s*$`)
// Exports Author.UnmarshalJSON interface to help normalize the json structure.
func (a *Author) UnmarshalJSON(b []byte) error {
var authorStr string
var fields map[string]string
var author Author
if err := json.Unmarshal(b, &authorStr); err != nil {
// string parsing did not work, assume a map was given
// for more information: https://docs.npmjs.com/files/package.json#people-fields-author-contributors
if err := json.Unmarshal(b, &fields); err != nil {
return fmt.Errorf("unable to parse package.json author: %w", err)
}
} else {
// parse out "name <email> (url)" into an Author struct
fields = internal.MatchCaptureGroups(authorPattern, authorStr)
}
// translate the map into a structure
if err := mapstructure.Decode(fields, &author); err != nil {
return fmt.Errorf("unable to decode package.json author: %w", err)
}
*a = author
return nil
}
func (a *Author) AuthorString() string {
result := a.Name
if a.Email != "" {
result += fmt.Sprintf(" <%s>", a.Email)
}
if a.URL != "" {
result += fmt.Sprintf(" (%s)", a.URL)
}
return result
}
func (r *Repository) UnmarshalJSON(b []byte) error {
var repositoryStr string
var fields map[string]string
var repository Repository
if err := json.Unmarshal(b, &repositoryStr); err != nil {
// string parsing did not work, assume a map was given
// for more information: https://docs.npmjs.com/files/package.json#people-fields-author-contributors
if err := json.Unmarshal(b, &fields); err != nil {
return fmt.Errorf("unable to parse package.json author: %w", err)
}
// translate the map into a structure
if err := mapstructure.Decode(fields, &repository); err != nil {
return fmt.Errorf("unable to decode package.json author: %w", err)
}
*r = repository
} else {
r.URL = repositoryStr
}
return nil
}
type license struct {
Type string `json:"type"`
URL string `json:"url"`
}
func licenseFromJSON(b []byte) (string, error) {
// first try as string
var licenseString string
err := json.Unmarshal(b, &licenseString)
if err == nil {
return licenseString, nil
}
// then try as object (this format is deprecated)
var licenseObject license
err = json.Unmarshal(b, &licenseObject)
if err == nil {
return licenseObject.Type, nil
}
return "", errors.New("unable to unmarshal license field as either string or object")
}
func licensesFromJSON(p PackageJSON) ([]string, error) {
if p.License == nil && p.Licenses == nil {
// This package.json doesn't specify any licenses whatsoever
return []string{}, nil
}
singleLicense, err := licenseFromJSON(p.License)
if err == nil {
return []string{singleLicense}, nil
}
// The "licenses" field is deprecated. It should be inspected as a last resort.
if p.Licenses != nil {
mapLicenses := func(licenses []license) []string {
mappedLicenses := make([]string, len(licenses))
for i, l := range licenses {
mappedLicenses[i] = l.Type
}
return mappedLicenses
}
return mapLicenses(p.Licenses), nil
}
return nil, fmt.Errorf("unable to parse license field: %w", err)
}
// parsePackageJson parses a package.json and returns the discovered JavaScript packages.
func parsePackageJSON(_ string, reader io.Reader) ([]pkg.Package, error) {
packages := make([]pkg.Package, 0)
dec := json.NewDecoder(reader)
for {
var p PackageJSON
if err := dec.Decode(&p); err == io.EOF {
break
} else if err != nil {
return nil, fmt.Errorf("failed to parse package.json file: %w", err)
}
licenses, err := licensesFromJSON(p)
if err != nil {
return nil, fmt.Errorf("failed to parse package.json file: %w", err)
}
packages = append(packages, pkg.Package{
Name: p.Name,
Version: p.Version,
Licenses: licenses,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
MetadataType: pkg.NpmPackageJSONMetadataType,
Metadata: pkg.NpmPackageJSONMetadata{
Author: p.Author.AuthorString(),
Homepage: p.Homepage,
URL: p.Repository.URL,
Licenses: licenses,
},
})
}
return packages, nil
}
@@ -1,144 +0,0 @@
package javascript
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
)
func TestParsePackageJSON(t *testing.T) {
tests := []struct {
Fixture string
ExpectedPkg pkg.Package
}{
{
Fixture: "test-fixtures/pkg-json/package.json",
ExpectedPkg: pkg.Package{
Name: "npm",
Version: "6.14.6",
Type: pkg.NpmPkg,
Licenses: []string{"Artistic-2.0"},
Language: pkg.JavaScript,
MetadataType: pkg.NpmPackageJSONMetadataType,
Metadata: pkg.NpmPackageJSONMetadata{
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
Homepage: "https://docs.npmjs.com/",
URL: "https://github.com/npm/cli",
Licenses: []string{"Artistic-2.0"},
},
},
},
{
Fixture: "test-fixtures/pkg-json/package-license-object.json",
ExpectedPkg: pkg.Package{
Name: "npm",
Version: "6.14.6",
Type: pkg.NpmPkg,
Licenses: []string{"ISC"},
Language: pkg.JavaScript,
MetadataType: pkg.NpmPackageJSONMetadataType,
Metadata: pkg.NpmPackageJSONMetadata{
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
Homepage: "https://docs.npmjs.com/",
URL: "https://github.com/npm/cli",
Licenses: []string{"ISC"},
},
},
},
{
Fixture: "test-fixtures/pkg-json/package-license-objects.json",
ExpectedPkg: pkg.Package{
Name: "npm",
Version: "6.14.6",
Type: pkg.NpmPkg,
Licenses: []string{"MIT", "Apache-2.0"},
Language: pkg.JavaScript,
MetadataType: pkg.NpmPackageJSONMetadataType,
Metadata: pkg.NpmPackageJSONMetadata{
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
Homepage: "https://docs.npmjs.com/",
URL: "https://github.com/npm/cli",
Licenses: []string{"MIT", "Apache-2.0"},
},
},
},
{
Fixture: "test-fixtures/pkg-json/package-no-license.json",
ExpectedPkg: pkg.Package{
Name: "npm",
Version: "6.14.6",
Type: pkg.NpmPkg,
Licenses: []string{},
Language: pkg.JavaScript,
MetadataType: pkg.NpmPackageJSONMetadataType,
Metadata: pkg.NpmPackageJSONMetadata{
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
Homepage: "https://docs.npmjs.com/",
URL: "https://github.com/npm/cli",
Licenses: []string{},
},
},
},
{
Fixture: "test-fixtures/pkg-json/package-nested-author.json",
ExpectedPkg: pkg.Package{
Name: "npm",
Version: "6.14.6",
Type: pkg.NpmPkg,
Licenses: []string{"Artistic-2.0"},
Language: pkg.JavaScript,
MetadataType: pkg.NpmPackageJSONMetadataType,
Metadata: pkg.NpmPackageJSONMetadata{
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
Homepage: "https://docs.npmjs.com/",
URL: "https://github.com/npm/cli",
Licenses: []string{"Artistic-2.0"},
},
},
},
{
Fixture: "test-fixtures/pkg-json/package-repo-string.json",
ExpectedPkg: pkg.Package{
Name: "function-bind",
Version: "1.1.1",
Type: pkg.NpmPkg,
Licenses: []string{"MIT"},
Language: pkg.JavaScript,
MetadataType: pkg.NpmPackageJSONMetadataType,
Metadata: pkg.NpmPackageJSONMetadata{
Author: "Raynos <raynos2@gmail.com>",
Homepage: "https://github.com/Raynos/function-bind",
URL: "git://github.com/Raynos/function-bind.git",
Licenses: []string{"MIT"},
},
},
},
}
for _, test := range tests {
t.Run(test.Fixture, func(t *testing.T) {
fixture, err := os.Open(test.Fixture)
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
actual, err := parsePackageJSON("", fixture)
if err != nil {
t.Fatalf("failed to parse package-lock.json: %+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], test.ExpectedPkg) {
t.Errorf("diff: %+v", d)
}
})
}
}
+2 -2
View File
@@ -40,7 +40,7 @@ func parseYarnLock(_ string, reader io.Reader) ([]pkg.Package, error) {
case composedNameExp.MatchString(line):
name := composedNameExp.FindString(line)
if len(name) == 0 {
log.Warnf("unable to parse yarn.lock line: %q", line)
log.Errorf("unable to parse line: '%s'", line)
}
currentName = strings.TrimLeft(name, "\"")
case simpleNameExp.MatchString(line):
@@ -66,7 +66,7 @@ func parseYarnLock(_ string, reader io.Reader) ([]pkg.Package, error) {
Name: currentName,
Version: strings.Trim(version, "\""),
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Type: pkg.YarnPkg,
})
}
}
@@ -13,43 +13,43 @@ func TestParseYarnLock(t *testing.T) {
Name: "@babel/code-frame",
Version: "7.10.4",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Type: pkg.YarnPkg,
},
"@types/minimatch": {
Name: "@types/minimatch",
Version: "3.0.3",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Type: pkg.YarnPkg,
},
"@types/qs": {
Name: "@types/qs",
Version: "6.9.4",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Type: pkg.YarnPkg,
},
"ajv": {
Name: "ajv",
Version: "6.12.3",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Type: pkg.YarnPkg,
},
"atob": {
Name: "atob",
Version: "2.1.2",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Type: pkg.YarnPkg,
},
"aws-sdk": {
Name: "aws-sdk",
Version: "2.706.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Type: pkg.YarnPkg,
},
"jhipster-core": {
Name: "jhipster-core",
Version: "7.3.4",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Type: pkg.YarnPkg,
},
}
fixture, err := os.Open("test-fixtures/yarn/yarn.lock")
@@ -1,22 +0,0 @@
{
"version": "6.14.6",
"name": "npm",
"description": "a package manager for JavaScript",
"homepage": "https://docs.npmjs.com/",
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
"repository": {
"type": "git",
"url": "https://github.com/npm/cli"
},
"bugs": {
"url": "https://npm.community/c/bugs"
},
"main": "./lib/npm.js",
"license": {
"type" : "ISC",
"url" : "https://opensource.org/licenses/ISC"
},
"engines": {
"node": "6 >=6.2.0 || 8 || >=9.3.0"
}
}
@@ -1,26 +0,0 @@
{
"version": "6.14.6",
"name": "npm",
"description": "a package manager for JavaScript",
"homepage": "https://docs.npmjs.com/",
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
"repository": {
"type": "git",
"url": "https://github.com/npm/cli"
},
"bugs": {
"url": "https://npm.community/c/bugs"
},
"main": "./lib/npm.js",
"licenses": [
{ "type": "MIT",
"url": "https://www.opensource.org/licenses/mit-license.php"
},
{ "type": "Apache-2.0",
"url": "https://opensource.org/licenses/apache2.0.php"
}
],
"engines": {
"node": "6 >=6.2.0 || 8 || >=9.3.0"
}
}
@@ -1,16 +0,0 @@
{
"version": "6.14.6",
"name": "npm",
"description": "a package manager for JavaScript",
"homepage": "https://docs.npmjs.com/",
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me"
},
"repository": {
"type": "git",
"url": "https://github.com/npm/cli"
},
"license": "Artistic-2.0"
}

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