mirror of
https://github.com/anchore/syft.git
synced 2026-10-27 20:42:24 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3098f3423 | ||
|
|
c2cf4eb7b0 | ||
|
|
1e79986188 | ||
|
|
3699a917fd | ||
|
|
501870f89f | ||
|
|
c878cc361c | ||
|
|
8fb5b17dbd | ||
|
|
b6eb589b78 | ||
|
|
6aba2f48d4 | ||
|
|
0205e72be9 | ||
|
|
8095cd9980 | ||
|
|
ecfc471ce5 | ||
|
|
a52750bdd3 |
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/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}"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/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_DMG_NAME="$3" # e.g. "./dist/syft-0.1.0.dmg"
|
||||
ORIGINAL_DMG_NAME="./dist/output.dmg" # This should match dmg output_path in the gon config file.
|
||||
|
||||
gon "${GON_CONFIG}"
|
||||
mv -v "${ORIGINAL_DMG_NAME}" "${NEW_DMG_NAME}"
|
||||
@@ -15,14 +15,9 @@ fi
|
||||
echo "creating and publishing version file"
|
||||
|
||||
# create a version file for version-update checks
|
||||
echo "${VERSION}" | tee ${DISTDIR}/VERSION
|
||||
VERSION_FILE="${DISTDIR}/VERSION"
|
||||
echo "${VERSION}" | tee "${VERSION_FILE}"
|
||||
|
||||
# upload the version file that supports the application version update check
|
||||
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
|
||||
export AWS_DEFAULT_REGION=us-west-2
|
||||
aws s3 cp "${VERSION_FILE}" s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION
|
||||
|
||||
@@ -14,8 +14,7 @@ env:
|
||||
|
||||
jobs:
|
||||
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: 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).
|
||||
steps:
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
@@ -40,9 +39,6 @@ jobs:
|
||||
if: steps.bootstrap-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
|
||||
- name: Bootstrap CI dependencies
|
||||
run: make ci-bootstrap
|
||||
|
||||
- name: Import GPG key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@v2
|
||||
@@ -162,4 +158,4 @@ jobs:
|
||||
text: The syft acceptance tests have failed tragically!
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
if: ${{ failure() }}
|
||||
if: ${{ failure() }}
|
||||
|
||||
@@ -13,7 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
wait-for-checks:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest # This OS choice is arbitrary. None of the steps in this job are specific to either Linux or macOS.
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
|
||||
release:
|
||||
needs: [ wait-for-checks ]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-latest # Due to our code signing process, it's vital that we run our release steps on macOS.
|
||||
steps:
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
@@ -93,6 +93,7 @@ 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
|
||||
@@ -105,10 +106,6 @@ 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
|
||||
@@ -132,6 +129,10 @@ 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:
|
||||
@@ -145,4 +146,4 @@ jobs:
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: artifacts
|
||||
path: dist/**/*
|
||||
path: dist/**/*
|
||||
|
||||
+30
-4
@@ -6,18 +6,37 @@ release:
|
||||
# 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}"]
|
||||
# This ensures any macOS signed artifacts get included with the release.
|
||||
extra_files:
|
||||
- glob: "./dist/*.dmg"
|
||||
|
||||
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
|
||||
@@ -31,6 +50,12 @@ builds:
|
||||
-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}}
|
||||
hooks:
|
||||
post: ./.github/scripts/mac-sign-and-notarize.sh "{{.IsSnapshot}}" "gon.hcl" "./dist/syft_{{.Version}}_{{.Target}}.dmg"
|
||||
|
||||
signs:
|
||||
- artifacts: checksum
|
||||
args: ["--output", "${signature}", "--detach-sign", "${artifact}"]
|
||||
|
||||
nfpms:
|
||||
- license: "Apache 2.0"
|
||||
@@ -50,7 +75,8 @@ brews:
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
builds:
|
||||
- syft # i.e. Linux only
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
|
||||
@@ -74,6 +74,10 @@ help:
|
||||
ci-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)
|
||||
@@ -226,8 +230,7 @@ acceptance-test-rpm-package-install: $(SNAPSHOTDIR)
|
||||
changelog-release:
|
||||
@echo "Last tag: $(SECOND_TO_LAST_TAG)"
|
||||
@echo "Current tag: $(VERSION)"
|
||||
@docker run -i --rm \
|
||||
-v "$(shell pwd)":/usr/local/src/your-app ferrarimarco/github-changelog-generator \
|
||||
@github_changelog_generator \
|
||||
--user anchore \
|
||||
--project $(BIN) \
|
||||
-t ${GITHUB_TOKEN} \
|
||||
@@ -241,8 +244,9 @@ changelog-release:
|
||||
|
||||
.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 \
|
||||
@docker run -it --rm \
|
||||
-v "$(shell pwd)":/usr/local/src/your-app \
|
||||
ferrarimarco/github-changelog-generator \
|
||||
--user anchore \
|
||||
--project $(BIN) \
|
||||
-t ${GITHUB_TOKEN} \
|
||||
@@ -258,8 +262,12 @@ changelog-unreleased: ## show the current changelog that will be produced on the
|
||||
/CHANGELOG.md
|
||||
|
||||
.PHONY: release
|
||||
release: clean-dist changelog-release ## Build and publish final binaries and packages
|
||||
release: clean-dist ci-bootstrap-mac changelog-release ## Build and publish final binaries and packages. Intended to be run only on macOS.
|
||||
$(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
|
||||
@@ -290,4 +298,4 @@ clean-dist:
|
||||
|
||||
.PHONY: clean-json-schema-examples
|
||||
clean-json-schema-examples:
|
||||
rm -f schema/json/examples/*
|
||||
rm -f schema/json/examples/*
|
||||
|
||||
@@ -54,7 +54,7 @@ Where the `format`s available are:
|
||||
|
||||
## Installation
|
||||
|
||||
**Recommended**
|
||||
**Recommended (macOS and Linux)**
|
||||
```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
|
||||
@@ -63,17 +63,12 @@ curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <SOME_BIN_PATH> <RELEASE_VERSION>
|
||||
```
|
||||
|
||||
**macOS**
|
||||
**Homebrew (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:
|
||||
|
||||
@@ -4,14 +4,14 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.2.1
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e
|
||||
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/stereoscope v0.0.0-20200925184903-c82da54e98fe
|
||||
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/dustin/go-humanize v1.0.0
|
||||
github.com/go-test/deep v1.0.6
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gookit/color v1.2.7
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
|
||||
@@ -126,12 +126,16 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e h1:kty6r0R2JeaNPeWKSYDC+HW3hkqwFh4PP5TQ8pUPYFw=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e/go.mod h1:iYuIG0Nai8dR0ri3LhZQKUyO1loxUWAGvoWhXDmjy1A=
|
||||
github.com/anchore/go-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-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-20200925184903-c82da54e98fe h1:m4NSyTo2fVUoUHAV/ZVqE/PFMr/y8oz9HRrhWLk9It0=
|
||||
github.com/anchore/stereoscope v0.0.0-20200925184903-c82da54e98fe/go.mod h1:2Jja/4l0zYggW52og+nn0rut4i+OYjCf9vTyrM8RT4E=
|
||||
github.com/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/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=
|
||||
@@ -299,6 +303,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
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-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-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=
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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"
|
||||
}
|
||||
+27
-13
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2020-08-10T20:55:46Z. DO NOT EDIT.
|
||||
# Code generated by godownloader on 2020-08-10T20:55:46Z.
|
||||
#
|
||||
|
||||
usage() {
|
||||
@@ -45,11 +45,16 @@ parse_args() {
|
||||
execute() {
|
||||
tmpdir=$(mktemp -d)
|
||||
log_debug "downloading files into ${tmpdir}"
|
||||
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
||||
http_download "${tmpdir}/${ARCHIVE}" "${ARCHIVE_URL}"
|
||||
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
||||
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
||||
|
||||
# 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
|
||||
|
||||
srcdir="${tmpdir}"
|
||||
(cd "${tmpdir}" && untar "${TARBALL}")
|
||||
(cd "${tmpdir}" && unpack "${ARCHIVE}")
|
||||
test ! -d "${BINDIR}" && install -d "${BINDIR}"
|
||||
for binexe in $BINARIES; do
|
||||
if [ "$OS" = "windows" ]; then
|
||||
@@ -89,6 +94,7 @@ 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
|
||||
@@ -221,18 +227,26 @@ 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
|
||||
}
|
||||
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}" ;;
|
||||
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}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
log_err "unpack unknown archive format for ${archive}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
extract_from_dmg() {
|
||||
dmg_file=$1
|
||||
mount_point="/Volumes/tmp-dmg"
|
||||
hdiutil attach -quiet -mountpoint "${mount_point}" "${dmg_file}"
|
||||
cp -fR "${mount_point}/" ./
|
||||
hdiutil detach -quiet -force "${mount_point}"
|
||||
}
|
||||
http_download_curl() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
@@ -366,8 +380,8 @@ adjust_arch
|
||||
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
||||
|
||||
NAME=${PROJECT_NAME}_${VERSION}_${OS}_${ARCH}
|
||||
TARBALL=${NAME}.${FORMAT}
|
||||
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
||||
ARCHIVE=${NAME}.${FORMAT}
|
||||
ARCHIVE_URL=${GITHUB_DOWNLOAD}/${TAG}/${ARCHIVE}
|
||||
CHECKSUM=${PROJECT_NAME}_${VERSION}_checksums.txt
|
||||
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
||||
|
||||
|
||||
@@ -89,7 +89,16 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
|
||||
|
||||
switch key {
|
||||
case "F":
|
||||
lastFile = "/" + value
|
||||
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
|
||||
continue
|
||||
case "R":
|
||||
newFileRecord := pkg.ApkFileRecord{
|
||||
@@ -97,7 +106,7 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
|
||||
}
|
||||
files = append(files, newFileRecord)
|
||||
fileRecord = &files[len(files)-1]
|
||||
case "a":
|
||||
case "a", "M":
|
||||
ownershipFields := strings.Split(value, ":")
|
||||
if len(ownershipFields) < 3 {
|
||||
log.Errorf("unexpected APK ownership field: %q", value)
|
||||
@@ -108,7 +117,7 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
|
||||
continue
|
||||
}
|
||||
fileRecord.OwnerUID = ownershipFields[0]
|
||||
fileRecord.OwnerGUI = ownershipFields[1]
|
||||
fileRecord.OwnerGID = 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="
|
||||
|
||||
@@ -19,10 +19,25 @@ func TestExtraFileAttributes(t *testing.T) {
|
||||
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",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1M0C9qfC/+kdRiOodeihG2GMRtkE=",
|
||||
},
|
||||
@@ -60,13 +75,13 @@ func TestExtraFileAttributes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSinglePackage(t *testing.T) {
|
||||
func TestSinglePackageDetails(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expected pkg.ApkMetadata
|
||||
}{
|
||||
{
|
||||
name: "Test Single Package",
|
||||
fixture: "test-fixtures/single",
|
||||
expected: pkg.ApkMetadata{
|
||||
Package: "musl-utils",
|
||||
OriginPackage: "musl",
|
||||
@@ -82,51 +97,428 @@ func TestSinglePackage(t *testing.T) {
|
||||
PullChecksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
|
||||
GitCommitOfAport: "4024cc3b29ad4c65544ad068b8f59172b5494306",
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{
|
||||
Path: "/sbin",
|
||||
},
|
||||
{
|
||||
Path: "/sbin/ldconfig",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
|
||||
},
|
||||
{
|
||||
Path: "/usr",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/iconv",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/ldd",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/getconf",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/getent",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "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.name, func(t *testing.T) {
|
||||
file, err := os.Open("test-fixtures/single")
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
file, err := os.Open(test.fixture)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to read test_fixtures/single: ", err)
|
||||
t.Fatal("Unable to read fixture: ", err)
|
||||
}
|
||||
defer func() {
|
||||
err := file.Close()
|
||||
@@ -203,38 +595,47 @@ func TestMultiplePackages(t *testing.T) {
|
||||
PullChecksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
|
||||
GitCommitOfAport: "4024cc3b29ad4c65544ad068b8f59172b5494306",
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{
|
||||
Path: "/sbin",
|
||||
},
|
||||
{
|
||||
Path: "/sbin/ldconfig",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
|
||||
},
|
||||
{
|
||||
Path: "/usr",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/iconv",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/ldd",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/getconf",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
|
||||
},
|
||||
{
|
||||
Path: "/usr/bin/getent",
|
||||
OwnerUID: "0",
|
||||
OwnerGUI: "0",
|
||||
OwnerGID: "0",
|
||||
Permissions: "755",
|
||||
Checksum: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
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
|
||||
@@ -4,13 +4,47 @@ Package rpmdb provides a concrete Cataloger implementation for RPM "Package" DB
|
||||
package rpmdb
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
const (
|
||||
packagesGlob = "**/var/lib/rpm/Packages"
|
||||
)
|
||||
|
||||
type Cataloger struct{}
|
||||
|
||||
// NewRpmdbCataloger returns a new RPM DB cataloger object.
|
||||
func NewRpmdbCataloger() *common.GenericCataloger {
|
||||
globParsers := map[string]common.ParserFn{
|
||||
"**/var/lib/rpm/Packages": parseRpmDB,
|
||||
func NewRpmdbCataloger() *Cataloger {
|
||||
return &Cataloger{}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes a cataloger
|
||||
func (c *Cataloger) Name() string {
|
||||
return "rpmdb-cataloger"
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||
func (c *Cataloger) Catalog(resolver scope.Resolver) ([]pkg.Package, error) {
|
||||
fileMatches, err := resolver.FilesByGlob(packagesGlob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find rpmdb's by glob: %w", err)
|
||||
}
|
||||
return common.NewGenericCataloger(nil, globParsers, "rpmdb-cataloger")
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, ref := range fileMatches {
|
||||
dbContents, err := resolver.FileContentsByRef(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkgs, err = parseRpmDB(resolver, strings.NewReader(dbContents))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to catalog rpmdb package=%+v: %w", ref.Path, err)
|
||||
}
|
||||
}
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
@@ -6,18 +6,18 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
|
||||
rpmdb "github.com/anchore/go-rpmdb/pkg"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cataloger/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// integrity check
|
||||
var _ common.ParserFn = parseRpmDB
|
||||
|
||||
// parseApkDb parses an "Packages" RPM DB and returns the Packages listed within it.
|
||||
func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseRpmDB(resolver scope.FileResolver, reader io.Reader) ([]pkg.Package, error) {
|
||||
f, err := ioutil.TempFile("", internal.ApplicationName+"-rpmdb")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp rpmdb file: %w", err)
|
||||
@@ -48,6 +48,11 @@ func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
allPkgs := make([]pkg.Package, 0)
|
||||
|
||||
for _, entry := range pkgList {
|
||||
records, err := extractRpmdbFileRecords(resolver, entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: entry.Name,
|
||||
Version: fmt.Sprintf("%s-%s", entry.Version, entry.Release), // this is what engine does
|
||||
@@ -64,6 +69,7 @@ func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
Vendor: entry.Vendor,
|
||||
License: entry.License,
|
||||
Size: entry.Size,
|
||||
Files: records,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -72,3 +78,26 @@ func parseRpmDB(_ string, reader io.Reader) ([]pkg.Package, error) {
|
||||
|
||||
return allPkgs, nil
|
||||
}
|
||||
|
||||
func extractRpmdbFileRecords(resolver scope.FileResolver, entry *rpmdb.PackageInfo) ([]pkg.RpmdbFileRecord, error) {
|
||||
var records = make([]pkg.RpmdbFileRecord, 0)
|
||||
|
||||
for _, record := range entry.Files {
|
||||
refs, err := resolver.FilesByPath(file.Path(record.Path))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve path=%+v: %w", record.Path, err)
|
||||
}
|
||||
//only persist RPMDB file records which exist in the image/directory, otherwise ignore them
|
||||
if len(refs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
records = append(records, pkg.RpmdbFileRecord{
|
||||
Path: record.Path,
|
||||
Mode: pkg.RpmdbFileMode(record.Mode),
|
||||
Size: int(record.Size),
|
||||
SHA256: record.SHA256,
|
||||
})
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
@@ -1,58 +1,141 @@
|
||||
package rpmdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
type rpmdbTestFileResolverMock struct {
|
||||
ignorePaths bool
|
||||
}
|
||||
|
||||
func newTestFileResolver(ignorePaths bool) *rpmdbTestFileResolverMock {
|
||||
return &rpmdbTestFileResolverMock{
|
||||
ignorePaths: ignorePaths,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpmdbTestFileResolverMock) FilesByPath(paths ...file.Path) ([]file.Reference, error) {
|
||||
if r.ignorePaths {
|
||||
// act as if no paths exist
|
||||
return nil, nil
|
||||
}
|
||||
// act as if all files exist
|
||||
var refs = make([]file.Reference, len(paths))
|
||||
for i, p := range paths {
|
||||
refs[i] = file.NewFileReference(p)
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
func (r *rpmdbTestFileResolverMock) FilesByGlob(...string) ([]file.Reference, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
func (r *rpmdbTestFileResolverMock) RelativeFileByPath(file.Reference, string) (*file.Reference, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func TestParseRpmDB(t *testing.T) {
|
||||
expected := map[string]pkg.Package{
|
||||
"dive": {
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Name: "dive",
|
||||
Epoch: 0,
|
||||
Arch: "x86_64",
|
||||
Release: "1",
|
||||
Version: "0.9.2",
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
License: "MIT",
|
||||
Vendor: "",
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected map[string]pkg.Package
|
||||
ignorePaths bool
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/Packages",
|
||||
// we only surface package paths for files that exist (here we DO NOT expect a path)
|
||||
ignorePaths: true,
|
||||
expected: map[string]pkg.Package{
|
||||
"dive": {
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Name: "dive",
|
||||
Epoch: 0,
|
||||
Arch: "x86_64",
|
||||
Release: "1",
|
||||
Version: "0.9.2",
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
License: "MIT",
|
||||
Vendor: "",
|
||||
Files: []pkg.RpmdbFileRecord{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/Packages",
|
||||
// we only surface package paths for files that exist (here we expect a path)
|
||||
ignorePaths: false,
|
||||
expected: map[string]pkg.Package{
|
||||
"dive": {
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Name: "dive",
|
||||
Epoch: 0,
|
||||
Arch: "x86_64",
|
||||
Release: "1",
|
||||
Version: "0.9.2",
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
License: "MIT",
|
||||
Vendor: "",
|
||||
Files: []pkg.RpmdbFileRecord{
|
||||
{
|
||||
Path: "/usr/local/bin/dive",
|
||||
Mode: 33261,
|
||||
Size: 12406784,
|
||||
SHA256: "81d29f327ba23096b3c52ff6fe1c425641e618bc87b5c05ee377edc650afaa55",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fixture, err := os.Open("test-fixtures/Packages")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
actual, err := parseRpmDB(fixture.Name(), fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse rpmdb: %+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 {
|
||||
e := expected[a.Name]
|
||||
diffs := deep.Equal(a, e)
|
||||
if len(diffs) > 0 {
|
||||
for _, d := range diffs {
|
||||
t.Errorf("diff: %+v", d)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fileResolver := newTestFileResolver(test.ignorePaths)
|
||||
|
||||
actual, err := parseRpmDB(fileResolver, fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse rpmdb: %+v", err)
|
||||
}
|
||||
|
||||
if len(actual) != len(test.expected) {
|
||||
for _, a := range actual {
|
||||
t.Log(" ", a)
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
e := test.expected[a.Name]
|
||||
diffs := deep.Equal(a, e)
|
||||
if len(diffs) > 0 {
|
||||
for _, d := range diffs {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ type ApkMetadata struct {
|
||||
// ApkFileRecord represents a single file listing and metadata from a APK DB entry (which may have many of these file records).
|
||||
type ApkFileRecord struct {
|
||||
Path string `json:"path"`
|
||||
OwnerUID string `json:"ownerUid"`
|
||||
OwnerGUI string `json:"ownerGid"`
|
||||
Permissions string `json:"permissions"`
|
||||
Checksum string `json:"checksum"`
|
||||
OwnerUID string `json:"ownerUid,omitempty"`
|
||||
OwnerGID string `json:"ownerGid,omitempty"`
|
||||
Permissions string `json:"permissions,omitempty"`
|
||||
Checksum string `json:"checksum,omitempty"`
|
||||
}
|
||||
|
||||
func (m ApkMetadata) PackageURL() string {
|
||||
|
||||
@@ -9,17 +9,27 @@ import (
|
||||
|
||||
// RpmdbMetadata represents all captured data for a RPM DB package entry.
|
||||
type RpmdbMetadata struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Epoch int `json:"epoch"`
|
||||
Arch string `json:"architecture"`
|
||||
Release string `json:"release"`
|
||||
SourceRpm string `json:"sourceRpm"`
|
||||
Size int `json:"size"`
|
||||
License string `json:"license"`
|
||||
Vendor string `json:"vendor"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Epoch int `json:"epoch"`
|
||||
Arch string `json:"architecture"`
|
||||
Release string `json:"release"`
|
||||
SourceRpm string `json:"sourceRpm"`
|
||||
Size int `json:"size"`
|
||||
License string `json:"license"`
|
||||
Vendor string `json:"vendor"`
|
||||
Files []RpmdbFileRecord `json:"files"`
|
||||
}
|
||||
|
||||
type RpmdbFileRecord struct {
|
||||
Path string `json:"path"`
|
||||
Mode RpmdbFileMode `json:"mode"`
|
||||
Size int `json:"size"`
|
||||
SHA256 string `json:"sha256"`
|
||||
}
|
||||
|
||||
type RpmdbFileMode uint16
|
||||
|
||||
func (m RpmdbMetadata) PackageURL(d distro.Distro) string {
|
||||
pURL := packageurl.NewPackageURL(
|
||||
packageurl.TypeRPM,
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
type Document struct {
|
||||
Artifacts []Artifact `json:"artifacts"`
|
||||
Source Source `json:"source"`
|
||||
Artifacts []Artifact `json:"artifacts"`
|
||||
Source Source `json:"source"`
|
||||
Distro Distribution `json:"distro"`
|
||||
}
|
||||
|
||||
func NewDocument(catalog *pkg.Catalog, s scope.Scope) (Document, error) {
|
||||
// Distritbution provides information about a detected Linux Distribution
|
||||
type Distribution struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func NewDocument(catalog *pkg.Catalog, s scope.Scope, d distro.Distro) (Document, error) {
|
||||
doc := Document{
|
||||
Artifacts: make([]Artifact, 0),
|
||||
}
|
||||
@@ -20,6 +28,14 @@ func NewDocument(catalog *pkg.Catalog, s scope.Scope) (Document, error) {
|
||||
return Document{}, nil
|
||||
}
|
||||
doc.Source = src
|
||||
distroName := d.Name()
|
||||
if distroName == "UnknownDistroType" {
|
||||
distroName = ""
|
||||
}
|
||||
doc.Distro = Distribution{
|
||||
Name: distroName,
|
||||
Version: d.FullVersion(),
|
||||
}
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
art, err := NewArtifact(p, s)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package json
|
||||
|
||||
import "github.com/anchore/syft/syft/scope"
|
||||
import (
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
Layers []Layer `json:"layers"`
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
)
|
||||
@@ -11,17 +12,19 @@ import (
|
||||
type Presenter struct {
|
||||
catalog *pkg.Catalog
|
||||
scope scope.Scope
|
||||
distro distro.Distro
|
||||
}
|
||||
|
||||
func NewPresenter(catalog *pkg.Catalog, s scope.Scope) *Presenter {
|
||||
func NewPresenter(catalog *pkg.Catalog, s scope.Scope, d distro.Distro) *Presenter {
|
||||
return &Presenter{
|
||||
catalog: catalog,
|
||||
scope: s,
|
||||
distro: d,
|
||||
}
|
||||
}
|
||||
|
||||
func (pres *Presenter) Present(output io.Writer) error {
|
||||
doc, err := NewDocument(pres.catalog, pres.scope)
|
||||
doc, err := NewDocument(pres.catalog, pres.scope, pres.distro)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/scope"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
@@ -39,12 +40,12 @@ func TestJsonDirsPresenter(t *testing.T) {
|
||||
{Path: "/some/path/pkg1"},
|
||||
},
|
||||
})
|
||||
|
||||
d := distro.NewUnknownDistro()
|
||||
s, err := scope.NewScopeFromDir("/some/path")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pres := NewPresenter(catalog, s)
|
||||
pres := NewPresenter(catalog, s, d)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
@@ -100,7 +101,8 @@ func TestJsonImgsPresenter(t *testing.T) {
|
||||
})
|
||||
|
||||
s, err := scope.NewScopeFromImage(img, scope.AllLayersScope)
|
||||
pres := NewPresenter(catalog, s)
|
||||
d := distro.NewUnknownDistro()
|
||||
pres := NewPresenter(catalog, s, d)
|
||||
|
||||
// run presenter
|
||||
err = pres.Present(&buffer)
|
||||
|
||||
@@ -26,5 +26,9 @@
|
||||
"source": {
|
||||
"type": "directory",
|
||||
"target": "/some/path"
|
||||
},
|
||||
"distro": {
|
||||
"name": "",
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,26 +35,30 @@
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:e158b57d6f5a96ef5fd22f2fe76c70b5ba6ff5b2619f9d83125b2aad0492ac7b",
|
||||
"digest": "sha256:78783bfc74fef84f899b4977561ad1172f87753f82cc2157b06bf097e56dfbce",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:da21056e7bf4308ecea0c0836848a7fe92f38fdcf35bc09ee6d98e7ab7beeebf",
|
||||
"digest": "sha256:54ec7f643dafbf9f27032a5e60afe06248c0e99b50aed54bb0fe28ea4825ccaf",
|
||||
"size": 16
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:f0e18aa6032c24659a9c741fc36ca56f589782ea132061ccf6f52b952403da94",
|
||||
"digest": "sha256:ec4775a139c45b1ddf9ea8e1cb43385e92e5c0bf6ec2e3f4192372785b18c106",
|
||||
"size": 27
|
||||
}
|
||||
],
|
||||
"size": 65,
|
||||
"digest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"digest": "sha256:fedd7bcc0b90f071501b662d8e7c9ac7548b88daba6b3deedfdf33f22ed8d95b",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
"stereoscope-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"
|
||||
]
|
||||
}
|
||||
},
|
||||
"distro": {
|
||||
"name": "",
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -28,7 +28,7 @@ type Presenter interface {
|
||||
func GetPresenter(option Option, s scope.Scope, catalog *pkg.Catalog, d *distro.Distro) Presenter {
|
||||
switch option {
|
||||
case JSONPresenter:
|
||||
return json.NewPresenter(catalog, s)
|
||||
return json.NewPresenter(catalog, s, *d)
|
||||
case TextPresenter:
|
||||
return text.NewPresenter(catalog, s)
|
||||
case TablePresenter:
|
||||
|
||||
@@ -67,12 +67,26 @@ func (r *AllLayersResolver) FilesByPath(paths ...file.Path) ([]file.Reference, e
|
||||
|
||||
for _, path := range paths {
|
||||
for idx, layerIdx := range r.layers {
|
||||
ref := r.img.Layers[layerIdx].Tree.File(path)
|
||||
tree := r.img.Layers[layerIdx].Tree
|
||||
ref := tree.File(path)
|
||||
if ref == nil {
|
||||
// no file found, keep looking through layers
|
||||
continue
|
||||
}
|
||||
|
||||
// don't consider directories (special case: there is no path information for /)
|
||||
if ref.Path == "/" {
|
||||
continue
|
||||
} else if r.img.FileCatalog.Exists(*ref) {
|
||||
metadata, err := r.img.FileCatalog.Get(*ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.Path, err)
|
||||
}
|
||||
if metadata.Metadata.IsDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
results, err := r.fileByRef(*ref, uniqueFileIDs, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -97,6 +111,19 @@ func (r *AllLayersResolver) FilesByGlob(patterns ...string) ([]file.Reference, e
|
||||
}
|
||||
|
||||
for _, ref := range refs {
|
||||
// don't consider directories (special case: there is no path information for /)
|
||||
if ref.Path == "/" {
|
||||
continue
|
||||
} else if r.img.FileCatalog.Exists(ref) {
|
||||
metadata, err := r.img.FileCatalog.Get(ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.Path, err)
|
||||
}
|
||||
if metadata.Metadata.IsDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
results, err := r.fileByRef(ref, uniqueFileIDs, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
)
|
||||
|
||||
@@ -80,6 +81,11 @@ func TestAllLayersResolver_FilesByPath(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore directories",
|
||||
linkPath: "/bin",
|
||||
resolutions: []resolution{},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -188,6 +194,11 @@ func TestAllLayersResolver_FilesByGlob(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore directories",
|
||||
glob: "**/bin",
|
||||
resolutions: []resolution{},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
@@ -33,12 +33,18 @@ func (s DirectoryResolver) FilesByPath(userPaths ...file.Path) ([]file.Reference
|
||||
// a path relative to root should be prefixed with the resolvers directory path, otherwise it should be left as is
|
||||
userStrPath = path.Join(s.Path, userStrPath)
|
||||
}
|
||||
_, err := os.Stat(userStrPath)
|
||||
fileMeta, err := os.Stat(userStrPath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Errorf("path (%s) is not valid: %v", userStrPath, err)
|
||||
}
|
||||
|
||||
// don't consider directories
|
||||
if fileMeta.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
references = append(references, file.NewFileReference(file.Path(userStrPath)))
|
||||
}
|
||||
|
||||
@@ -69,9 +75,12 @@ func (s DirectoryResolver) FilesByGlob(patterns ...string) ([]file.Reference, er
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// don't consider directories
|
||||
if fileMeta.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
matchedPath := file.Path(match)
|
||||
result = append(result, file.NewFileReference(matchedPath))
|
||||
}
|
||||
|
||||
@@ -48,6 +48,12 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) {
|
||||
expected: "test-fixtures/image-symlinks/file-1.txt",
|
||||
refCount: 1,
|
||||
},
|
||||
{
|
||||
name: "directories ignored",
|
||||
root: "./test-fixtures/",
|
||||
input: "/image-symlinks",
|
||||
refCount: 0,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
|
||||
@@ -26,16 +26,32 @@ func (r *ImageSquashResolver) FilesByPath(paths ...file.Path) ([]file.Reference,
|
||||
uniqueFiles := make([]file.Reference, 0)
|
||||
|
||||
for _, path := range paths {
|
||||
ref := r.img.SquashedTree().File(path)
|
||||
tree := r.img.SquashedTree()
|
||||
ref := tree.File(path)
|
||||
if ref == nil {
|
||||
// no file found, keep looking through layers
|
||||
continue
|
||||
}
|
||||
|
||||
// don't consider directories (special case: there is no path information for /)
|
||||
if ref.Path == "/" {
|
||||
continue
|
||||
} else if r.img.FileCatalog.Exists(*ref) {
|
||||
metadata, err := r.img.FileCatalog.Get(*ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.Path, err)
|
||||
}
|
||||
if metadata.Metadata.IsDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// a file may be a symlink, process it as such and resolve it
|
||||
resolvedRef, err := r.img.ResolveLinkByImageSquash(*ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve link from img (ref=%+v): %w", ref, err)
|
||||
}
|
||||
|
||||
if resolvedRef != nil && !uniqueFileIDs.Contains(*resolvedRef) {
|
||||
uniqueFileIDs.Add(*resolvedRef)
|
||||
uniqueFiles = append(uniqueFiles, *resolvedRef)
|
||||
@@ -57,6 +73,19 @@ func (r *ImageSquashResolver) FilesByGlob(patterns ...string) ([]file.Reference,
|
||||
}
|
||||
|
||||
for _, ref := range refs {
|
||||
// don't consider directories (special case: there is no path information for /)
|
||||
if ref.Path == "/" {
|
||||
continue
|
||||
} else if r.img.FileCatalog.Exists(ref) {
|
||||
metadata, err := r.img.FileCatalog.Get(ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.Path, err)
|
||||
}
|
||||
if metadata.Metadata.IsDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
resolvedRefs, err := r.FilesByPath(ref.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find files by path (ref=%+v): %w", ref, err)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
)
|
||||
|
||||
@@ -44,6 +45,11 @@ func TestImageSquashResolver_FilesByPath(t *testing.T) {
|
||||
resolveLayer: 8,
|
||||
resolvePath: "/link-dead",
|
||||
},
|
||||
{
|
||||
name: "ignore directories",
|
||||
linkPath: "/bin",
|
||||
resolvePath: "",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -60,10 +66,20 @@ func TestImageSquashResolver_FilesByPath(t *testing.T) {
|
||||
t.Fatalf("could not use resolver: %+v", err)
|
||||
}
|
||||
|
||||
if len(refs) != 1 {
|
||||
expectedRefs := 1
|
||||
if c.resolvePath == "" {
|
||||
expectedRefs = 0
|
||||
}
|
||||
|
||||
if len(refs) != expectedRefs {
|
||||
t.Fatalf("unexpected number of resolutions: %d", len(refs))
|
||||
}
|
||||
|
||||
if expectedRefs == 0 {
|
||||
// nothing else to assert
|
||||
return
|
||||
}
|
||||
|
||||
actual := refs[0]
|
||||
|
||||
if actual.Path != file.Path(c.resolvePath) {
|
||||
@@ -119,6 +135,11 @@ func TestImageSquashResolver_FilesByGlob(t *testing.T) {
|
||||
resolveLayer: 8,
|
||||
resolvePath: "/link-dead",
|
||||
},
|
||||
{
|
||||
name: "ignore directories",
|
||||
glob: "**/bin",
|
||||
resolvePath: "",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -135,10 +156,20 @@ func TestImageSquashResolver_FilesByGlob(t *testing.T) {
|
||||
t.Fatalf("could not use resolver: %+v", err)
|
||||
}
|
||||
|
||||
if len(refs) != 1 {
|
||||
expectedRefs := 1
|
||||
if c.resolvePath == "" {
|
||||
expectedRefs = 0
|
||||
}
|
||||
|
||||
if len(refs) != expectedRefs {
|
||||
t.Fatalf("unexpected number of resolutions: %d", len(refs))
|
||||
}
|
||||
|
||||
if expectedRefs == 0 {
|
||||
// nothing else to assert
|
||||
return
|
||||
}
|
||||
|
||||
actual := refs[0]
|
||||
|
||||
if actual.Path != file.Path(c.resolvePath) {
|
||||
|
||||
@@ -61,9 +61,15 @@ func TestDirectoryScope(t *testing.T) {
|
||||
{
|
||||
desc: "path detected",
|
||||
input: "test-fixtures",
|
||||
inputPaths: []file.Path{file.Path("test-fixtures/path-detected")},
|
||||
inputPaths: []file.Path{file.Path("test-fixtures/path-detected/.vimrc")},
|
||||
expRefs: 1,
|
||||
},
|
||||
{
|
||||
desc: "directory ignored",
|
||||
input: "test-fixtures",
|
||||
inputPaths: []file.Path{file.Path("test-fixtures/path-detected")},
|
||||
expRefs: 0,
|
||||
},
|
||||
{
|
||||
desc: "no files-by-path detected",
|
||||
input: "test-fixtures",
|
||||
|
||||
@@ -37,9 +37,10 @@ skopeo --override-os linux copy docker://docker.io/${TEST_IMAGE} docker-archive:
|
||||
ls -alh ${TEST_IMAGE_TAR}
|
||||
|
||||
# run syft
|
||||
chmod 755 ${DISTDIR}/syft_darwin_amd64/syft
|
||||
${DISTDIR}/syft_darwin_amd64/syft version
|
||||
SYFT_CHECK_FOR_APP_UPDATE=0 ${DISTDIR}/syft_darwin_amd64/syft docker-archive://${TEST_IMAGE_TAR} -vv -o json > ${REPORT}
|
||||
SYFT_PATH="${DISTDIR}/syft-macos_darwin_amd64/syft"
|
||||
chmod 755 "${SYFT_PATH}"
|
||||
"${SYFT_PATH}" version
|
||||
SYFT_CHECK_FOR_APP_UPDATE=0 "${SYFT_PATH}" docker-archive://${TEST_IMAGE_TAR} -vv -o json > "${REPORT}"
|
||||
|
||||
# keep the generated report around
|
||||
mkdir -p ${RESULTSDIR}
|
||||
|
||||
Reference in New Issue
Block a user