bootstrap within composite action (#1461)

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

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2023-01-17 10:04:22 -05:00 committed by GitHub
parent 934644232a
commit 05611c283d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 550 additions and 635 deletions

81
.github/actions/bootstrap/action.yaml vendored Normal file
View file

@ -0,0 +1,81 @@
name: "Bootstrap"
description: "Bootstrap all tools and dependencies"
inputs:
go-version:
description: "Go version to install"
required: true
default: "1.18.x"
use-go-cache:
description: "Restore go cache"
required: true
default: "true"
cache-key-prefix:
description: "Prefix all cache keys with this value"
required: true
default: "831180ac25"
build-cache-key-prefix:
description: "Prefix build cache key with this value"
required: true
default: "f8b6d31dea"
bootstrap-apt-packages:
description: "Space delimited list of tools to install via apt"
default: "libxml2-utils"
runs:
using: "composite"
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ inputs.go-version }}
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v3
with:
path: ${{ github.workspace }}/.tmp
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
# note: we need to keep restoring the go mod cache before bootstrapping tools since `go install` is used in
# some installations of project tools.
- name: Restore go module cache
id: go-mod-cache
if: inputs.use-go-cache == 'true'
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-
- name: (cache-miss) Bootstrap project tools
shell: bash
if: steps.tool-cache.outputs.cache-hit != 'true'
run: make bootstrap-tools
- name: Restore go build cache
id: go-cache
if: inputs.use-go-cache == 'true'
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
key: ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-
- name: (cache-miss) Bootstrap go dependencies
shell: bash
if: steps.go-mod-cache.outputs.cache-hit != 'true' && inputs.use-go-cache == 'true'
run: make bootstrap-go
- name: Install apt packages
if: inputs.bootstrap-apt-packages != ''
shell: bash
run: |
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y ${{ inputs.bootstrap-apt-packages }}
- name: Create all cache fingerprints
shell: bash
run: make fingerprints

11
.github/scripts/ci-check.sh vendored Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
red=$(tput setaf 1)
bold=$(tput bold)
normal=$(tput sgr0)
# assert we are running in CI (or die!)
if [[ -z "$CI" ]]; then
echo "${bold}${red}This script should ONLY be run in CI. Exiting...${normal}"
exit 1
fi

36
.github/scripts/coverage.py vendored Executable file
View file

@ -0,0 +1,36 @@
#!/usr/bin/env python3
import subprocess
import sys
import shlex
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
if len(sys.argv) < 3:
print("Usage: coverage.py [threshold] [go-coverage-report]")
sys.exit(1)
threshold = float(sys.argv[1])
report = sys.argv[2]
args = shlex.split(f"go tool cover -func {report}")
p = subprocess.run(args, capture_output=True, text=True)
percent_coverage = float(p.stdout.splitlines()[-1].split()[-1].replace("%", ""))
print(f"{bcolors.BOLD}Coverage: {percent_coverage}%{bcolors.ENDC}")
if percent_coverage < threshold:
print(f"{bcolors.BOLD}{bcolors.FAIL}Coverage below threshold of {threshold}%{bcolors.ENDC}")
sys.exit(1)

View file

@ -4,19 +4,18 @@ set -eu
ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX") ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX")
TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX") TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX")
trap "cp -v ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT trap "cp ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
echo "Capturing original state of files..." # capturing original state of files...
cp -v go.mod go.sum "${ORIGINAL_STATE_DIR}" cp go.mod go.sum "${ORIGINAL_STATE_DIR}"
echo "Capturing state of go.mod and go.sum after running go mod tidy..." # capturing state of go.mod and go.sum after running go mod tidy...
go mod tidy go mod tidy
cp -v go.mod go.sum "${TIDY_STATE_DIR}" cp go.mod go.sum "${TIDY_STATE_DIR}"
echo ""
set +e set +e
# Detect difference between the git HEAD state and the go mod tidy state # detect difference between the git HEAD state and the go mod tidy state
DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod") DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod")
DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum") DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum")

View file

@ -0,0 +1,58 @@
name: "Benchmark testing"
on:
workflow_dispatch:
pull_request:
jobs:
Benchmark-Test:
name: "Benchmark tests"
runs-on: ubuntu-20.04
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
# the job by event.
steps:
- uses: actions/checkout@v3
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore base benchmark result
uses: actions/cache@v3
with:
path: test/results/benchmark-main.txt
# use base sha for PR or new commit hash for main push in benchmark result key
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
- name: Run benchmark tests
id: benchmark
run: |
REF_NAME=${GITHUB_REF##*/} make benchmark
OUTPUT=$(make show-benchstat)
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
echo "::set-output name=result::$OUTPUT"
- uses: actions/upload-artifact@v3
with:
name: benchmark-test-results
path: test/results/**/*
- name: Update PR benchmark results comment
uses: marocchino/sticky-pull-request-comment@v2
continue-on-error: true
with:
header: benchmark
message: |
### Benchmark Test Results
<details>
<summary>Benchmark results from the latest changes vs base branch</summary>
```
${{ steps.benchmark.outputs.result }}
```
</details>

View file

@ -16,7 +16,7 @@ jobs:
environment: release environment: release
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
# we don't want to release commits that have been pushed and tagged, but not necessarily merged onto main # we don't want to release commits that have been pushed and tagged, but not necessarily merged onto main
- name: Ensure tagged commit is on main - name: Ensure tagged commit is on main
@ -26,7 +26,7 @@ jobs:
git merge-base --is-ancestor ${GITHUB_REF##*/} origin/main && echo "${GITHUB_REF##*/} is a commit on 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 analysis results
uses: fountainhead/action-wait-for-check@v1.0.0 uses: fountainhead/action-wait-for-check@v1.1.0
id: static-analysis id: static-analysis
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@ -35,7 +35,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check unit test results - name: Check unit test results
uses: fountainhead/action-wait-for-check@v1.0.0 uses: fountainhead/action-wait-for-check@v1.1.0
id: unit id: unit
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@ -44,7 +44,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check integration test results - name: Check integration test results
uses: fountainhead/action-wait-for-check@v1.0.0 uses: fountainhead/action-wait-for-check@v1.1.0
id: integration id: integration
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@ -53,7 +53,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (linux) - name: Check acceptance test results (linux)
uses: fountainhead/action-wait-for-check@v1.0.0 uses: fountainhead/action-wait-for-check@v1.1.0
id: acceptance-linux id: acceptance-linux
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@ -62,7 +62,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (mac) - name: Check acceptance test results (mac)
uses: fountainhead/action-wait-for-check@v1.0.0 uses: fountainhead/action-wait-for-check@v1.1.0
id: acceptance-mac id: acceptance-mac
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@ -71,7 +71,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cli test results (linux) - name: Check cli test results (linux)
uses: fountainhead/action-wait-for-check@v1.0.0 uses: fountainhead/action-wait-for-check@v1.1.0
id: cli-linux id: cli-linux
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@ -97,33 +97,13 @@ jobs:
contents: write contents: write
packages: write packages: write
steps: steps:
- uses: actions/setup-go@v2 - uses: actions/checkout@v3
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2 - name: Bootstrap environment
uses: ./.github/actions/bootstrap
with: with:
fetch-depth: 0 # use the same cache we used for building snapshots
build-cache-key-prefix: "snapshot"
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/.tmp
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
- name: Restore go cache
id: go-cache
uses: actions/cache@v2.1.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: (cache-miss) Bootstrap all project dependencies
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v2

View file

@ -1,14 +1,11 @@
name: "Validations" name: "Validations"
on: on:
workflow_dispatch: workflow_dispatch:
pull_request:
push: push:
branches: branches:
- main - main
pull_request:
env:
GO_VERSION: "1.18.x"
GO_STABLE_VERSION: true
jobs: jobs:
@ -17,100 +14,42 @@ jobs:
name: "Static analysis" name: "Static analysis"
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/setup-go@v2 - uses: actions/checkout@v3
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
- uses: actions/checkout@v2 - name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/.tmp
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
- name: Restore go cache
id: go-cache
uses: actions/cache@v2.1.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: (cache-miss) Bootstrap all project dependencies
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Bootstrap CI environment dependencies
run: make ci-bootstrap
- name: Run static analysis - name: Run static analysis
run: make static-analysis run: make static-analysis
Unit-Test: Unit-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Unit tests" name: "Unit tests"
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/setup-go@v2 - uses: actions/checkout@v3
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
- uses: actions/checkout@v2 - name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/.tmp
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
- name: Restore go cache
id: go-cache
uses: actions/cache@v2.1.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: (cache-miss) Bootstrap all project dependencies
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Bootstrap CI environment 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 - name: Restore Java test-fixture cache
id: unit-java-cache id: unit-java-cache
uses: actions/cache@v2.1.3 uses: actions/cache@v3
with: with:
path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }} key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }}
- name: Build cache key for rpm test-fixture blobs (for unit tests)
run: make rpm-binaries-fingerprint
- name: Restore RPM test-fixture cache - name: Restore RPM test-fixture cache
id: unit-rpm-cache id: unit-rpm-cache
uses: actions/cache@v2.1.3 uses: actions/cache@v3
with: with:
path: syft/pkg/cataloger/rpm/test-fixtures/rpms path: syft/pkg/cataloger/rpm/test-fixtures/rpms
key: ${{ runner.os }}-unit-rpm-cache-${{ hashFiles( 'syft/pkg/cataloger/rpm/test-fixtures/rpms.fingerprint' ) }} key: ${{ runner.os }}-unit-rpm-cache-${{ hashFiles( 'syft/pkg/cataloger/rpm/test-fixtures/rpms.fingerprint' ) }}
- name: Build cache key for go binary test-fixture blobs (for unit tests) - name: Restore go binary test-fixture cache
run: make go-binaries-fingerprint
- name: Restore Go binary test-fixture cache
id: unit-go-binary-cache id: unit-go-binary-cache
uses: actions/cache@v2.1.3 uses: actions/cache@v3
with: with:
path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries
key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }} key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }}
@ -118,54 +57,22 @@ jobs:
- name: Run unit tests - name: Run unit tests
run: make unit run: make unit
- uses: actions/upload-artifact@v2
with:
name: unit-test-results
path: test/results/**/*
Integration-Test: Integration-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Integration tests" name: "Integration tests"
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/setup-go@v2 - uses: actions/checkout@v3
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
- uses: actions/checkout@v2 - name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/.tmp
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
- name: Restore go cache
id: go-cache
uses: actions/cache@v2.1.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: (cache-miss) Bootstrap all project dependencies
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Bootstrap CI environment dependencies
run: make ci-bootstrap
- name: Validate syft output against the CycloneDX schema - name: Validate syft output against the CycloneDX schema
run: make validate-cyclonedx-schema run: make validate-cyclonedx-schema
- name: Build key for tar cache
run: make integration-fingerprint
- name: Restore integration test cache - name: Restore integration test cache
uses: actions/cache@v2.1.3 uses: actions/cache@v3
with: with:
path: ${{ github.workspace }}/test/integration/test-fixtures/cache path: ${{ github.workspace }}/test/integration/test-fixtures/cache
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('test/integration/test-fixtures/cache.fingerprint') }} key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('test/integration/test-fixtures/cache.fingerprint') }}
@ -173,123 +80,35 @@ jobs:
- name: Run integration tests - name: Run integration tests
run: make integration run: make integration
Benchmark-Test:
name: "Benchmark tests"
runs-on: ubuntu-20.04
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
# the job by event.
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
- uses: actions/checkout@v2
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/.tmp
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
- name: Restore go cache
id: go-cache
uses: actions/cache@v2.1.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: (cache-miss) Bootstrap all project dependencies
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Bootstrap CI environment dependencies
run: make ci-bootstrap
- name: Restore base benchmark result
uses: actions/cache@v2
with:
path: test/results/benchmark-main.txt
# use base sha for PR or new commit hash for main push in benchmark result key
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
- name: Run benchmark tests
id: benchmark
run: |
REF_NAME=${GITHUB_REF##*/} make benchmark
OUTPUT=$(make show-benchstat)
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
echo "::set-output name=result::$OUTPUT"
- uses: actions/upload-artifact@v2
with:
name: benchmark-test-results
path: test/results/**/*
- name: Update PR benchmark results comment
uses: marocchino/sticky-pull-request-comment@v2
continue-on-error: true
with:
header: benchmark
message: |
### Benchmark Test Results
<details>
<summary>Benchmark results from the latest changes vs base branch</summary>
```
${{ steps.benchmark.outputs.result }}
```
</details>
Build-Snapshot-Artifacts: Build-Snapshot-Artifacts:
name: "Build snapshot artifacts" name: "Build snapshot artifacts"
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/setup-go@v2 - uses: actions/checkout@v3
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
with: with:
go-version: ${{ env.GO_VERSION }} # why have another build cache key? We don't want unit/integration/etc test build caches to replace
stable: ${{ env.GO_STABLE_VERSION }} # the snapshot build cache, which includes builds for all OSs and architectures. As long as this key is
# unique from the build-cache-key-prefix in other CI jobs, we should be fine.
- uses: actions/checkout@v2 #
# note: ideally this value should match what is used in release (just to help with build times).
- name: Set up QEMU build-cache-key-prefix: "snapshot"
uses: docker/setup-qemu-action@v1 bootstrap-apt-packages: ""
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/.tmp
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
- name: Restore go cache
id: go-cache
uses: actions/cache@v2.1.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: (cache-miss) Bootstrap all project dependencies
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Build snapshot artifacts - name: Build snapshot artifacts
run: make snapshot run: make snapshot
- uses: actions/upload-artifact@v2 # why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
# see https://github.com/actions/upload-artifact/issues/199 for more info
- name: Upload snapshot artifacts
uses: actions/cache/save@v3
with: with:
name: artifacts path: snapshot
path: snapshot/**/* key: snapshot-build-${{ github.run_id }}
Acceptance-Linux: Acceptance-Linux:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
@ -297,22 +116,20 @@ jobs:
needs: [Build-Snapshot-Artifacts] needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/download-artifact@v2 - name: Download snapshot build
uses: actions/cache/restore@v3
with: with:
name: artifacts
path: snapshot path: snapshot
key: snapshot-build-${{ github.run_id }}
- name: Run comparison tests (Linux) - name: Run comparison tests (Linux)
run: make compare-linux run: make compare-linux
- name: Build key for image cache
run: make install-fingerprint
- name: Restore install.sh test image cache - name: Restore install.sh test image cache
id: install-test-image-cache id: install-test-image-cache
uses: actions/cache@v2.1.3 uses: actions/cache@v3
with: with:
path: ${{ github.workspace }}/test/install/cache path: ${{ github.workspace }}/test/install/cache
key: ${{ runner.os }}-install-test-image-cache-${{ hashFiles('test/install/cache.fingerprint') }} key: ${{ runner.os }}-install-test-image-cache-${{ hashFiles('test/install/cache.fingerprint') }}
@ -335,16 +152,17 @@ jobs:
needs: [Build-Snapshot-Artifacts] needs: [Build-Snapshot-Artifacts]
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/download-artifact@v2 - name: Download snapshot build
uses: actions/cache/restore@v3
with: with:
name: artifacts
path: snapshot path: snapshot
key: snapshot-build-${{ github.run_id }}
- name: Restore docker image cache for compare testing - name: Restore docker image cache for compare testing
id: mac-compare-testing-cache id: mac-compare-testing-cache
uses: actions/cache@v2.1.3 uses: actions/cache@v3
with: with:
path: image.tar path: image.tar
key: ${{ runner.os }}-${{ hashFiles('test/compare/mac.sh') }} key: ${{ runner.os }}-${{ hashFiles('test/compare/mac.sh') }}
@ -355,52 +173,29 @@ jobs:
- name: Run install.sh tests (Mac) - name: Run install.sh tests (Mac)
run: make install-test-ci-mac run: make install-test-ci-mac
Cli-Linux: Cli-Linux:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "CLI tests (Linux)" name: "CLI tests (Linux)"
needs: [Build-Snapshot-Artifacts] needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/setup-go@v2 - uses: actions/checkout@v3
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
- uses: actions/checkout@v2 - name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore go cache - name: Restore CLI test-fixture cache
id: go-cache uses: actions/cache@v3
uses: actions/cache@v2.1.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v2.1.3
with:
path: ${{ github.workspace }}/.tmp
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
- name: (cache-miss) Bootstrap all project dependencies
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Build key for tar cache
run: make cli-fingerprint
- name: Restore CLI test cache
uses: actions/cache@v2.1.3
with: with:
path: ${{ github.workspace }}/test/cli/test-fixtures/cache path: ${{ github.workspace }}/test/cli/test-fixtures/cache
key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }} key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }}
- uses: actions/download-artifact@v2 - name: Download snapshot build
uses: actions/cache/restore@v3
with: with:
name: artifacts
path: snapshot path: snapshot
key: snapshot-build-${{ github.run_id }}
- name: Run CLI Tests (Linux) - name: Run CLI Tests (Linux)
run: make cli run: make cli

View file

@ -5,6 +5,7 @@ release:
env: env:
# required to support multi architecture docker builds # required to support multi architecture docker builds
- DOCKER_CLI_EXPERIMENTAL=enabled - DOCKER_CLI_EXPERIMENTAL=enabled
- CGO_ENABLED=0
builds: builds:
- id: linux-build - id: linux-build
@ -19,8 +20,6 @@ builds:
- s390x - s390x
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build # set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}' mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
env: &build-env
- CGO_ENABLED=0
ldflags: &build-ldflags | ldflags: &build-ldflags |
-w -w
-s -s
@ -39,7 +38,6 @@ builds:
- amd64 - amd64
- arm64 - arm64
mod_timestamp: *build-timestamp mod_timestamp: *build-timestamp
env: *build-env
ldflags: *build-ldflags ldflags: *build-ldflags
hooks: hooks:
post: post:
@ -55,7 +53,6 @@ builds:
goarch: goarch:
- amd64 - amd64
mod_timestamp: *build-timestamp mod_timestamp: *build-timestamp
env: *build-env
ldflags: *build-ldflags ldflags: *build-ldflags
archives: archives:

View file

@ -15,37 +15,16 @@ After cloning the following step can help you get setup:
- this command `go run cmd/syft/main.go alpine:latest` will compile and run syft against `alpine:latest` - this command `go run cmd/syft/main.go alpine:latest` will compile and run syft against `alpine:latest`
5. view the README or syft help output for more output options 5. view the README or syft help output for more output options
#### Make output
The main make tasks for common static analysis and testing are `lint`, `lint-fix`, `unit`, `integration`, and `cli`. The main make tasks for common static analysis and testing are `lint`, `lint-fix`, `unit`, `integration`, and `cli`.
```
all Run all linux-based checks (linting, license check, unit, integration, and linux compare tests) See `make help` for all the current make tasks.
benchmark Run benchmark tests and compare against the baseline (if available)
bootstrap Download and install all tooling dependencies (+ prep tooling in the ./tmp dir)
build Build release snapshot binaries and packages
check-licenses Ensure transitive dependencies are compliant with the current license policy
clean-test-image-cache Clean test image cache
clean Remove previous builds, result reports, and test cache
cli Run CLI tests
compare-linux Run compare tests on build snapshot binaries and packages (Linux)
compare-mac Run compare tests on build snapshot binaries and packages (Mac)
generate-json-schema Generate a new json schema
generate-license-list Generate an updated spdx license list
help Display this help
integration Run integration tests
lint-fix Auto-format all source code + run golangci lint fixers
lint Run gofmt + golangci lint checks
show-test-image-cache Show all docker and image tar cache
show-test-snapshots Show all test snapshots
snapshot-with-signing Build snapshot release binaries and packages (with dummy signing)
test Run all tests (currently unit, integration, linux compare, and cli tests)
unit Run unit tests (with coverage)
```
## Architecture ## Architecture
Syft is used to generate a Software Bill of Materials (SBOM) from different kinds of input. Syft is used to generate a Software Bill of Materials (SBOM) from different kinds of input.
### Code organization for the cmd package ### Code organization for the cmd package
Syft's entrypoint can be found in the `cmd` package at `cmd/syft/main.go`. `main.go` builds a new syft `cli` via `cli.New()` Syft's entrypoint can be found in the `cmd` package at `cmd/syft/main.go`. `main.go` builds a new syft `cli` via `cli.New()`
and then executes the `cli` via `cli.Execute()`. The `cli` package is responsible for parsing command line arguments, and then executes the `cli` via `cli.Execute()`. The `cli` package is responsible for parsing command line arguments,
setting up the application context and configuration, and executing the application. Each of syft's commands setting up the application context and configuration, and executing the application. Each of syft's commands
@ -55,23 +34,24 @@ They are registered in `syft/cli/commands/go`.
. .
└── syft/ └── syft/
├── cli/ ├── cli/
   ├── attest/ ├── attest/
   ├── attest.go ├── attest.go
   ├── commands.go ├── commands.go
   ├── completion.go ├── completion.go
   ├── convert/ ├── convert/
   ├── convert.go ├── convert.go
   ├── eventloop/ ├── eventloop/
   ├── options/ ├── options/
   ├── packages/ ├── packages/
   ├── packages.go ├── packages.go
   ├── poweruser/ ├── poweruser/
   ├── poweruser.go ├── poweruser.go
   └── version.go └── version.go
└── main.go └── main.go
``` ```
#### Execution flow #### Execution flow
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant main as cmd/syft/main participant main as cmd/syft/main
@ -100,19 +80,19 @@ sequenceDiagram
Syft's core library (see, exported) functionality is implemented in the `syft` package. The `syft` package is responsible for organizing the core Syft's core library (see, exported) functionality is implemented in the `syft` package. The `syft` package is responsible for organizing the core
SBOM data model, it's translated output formats, and the core SBOM generation logic. SBOM data model, it's translated output formats, and the core SBOM generation logic.
#### Organization and design notes for the syft library
- analysis creates a static SBOM which can be encoded and decoded - analysis creates a static SBOM which can be encoded and decoded
- format objects, should strive to not add or enrich data in encoding that could otherwise be done during analysis - format objects, should strive to not add or enrich data in encoding that could otherwise be done during analysis
- package catalogers and their organization can be viewed/added to the `syft/pkg/cataloger` package - package catalogers and their organization can be viewed/added to the `syft/pkg/cataloger` package
- file catalogers and their organization can be viewed/added to the `syft/file` package - file catalogers and their organization can be viewed/added to the `syft/file` package
- The source package provides an abstraction to allow a user to loosely define a data source that can be cataloged - The source package provides an abstraction to allow a user to loosely define a data source that can be cataloged
- Logging Abstraction ...
#### Code example of syft as a library #### Code example of syft as a library
Here is a gist of using syft as a library to generate a SBOM from a docker image: [link](https://gist.github.com/wagoodman/57ed59a6d57600c23913071b8470175b).
Here is a gist of using syft as a library to generate a SBOM for a docker image: [link](https://gist.github.com/wagoodman/57ed59a6d57600c23913071b8470175b).
The execution flow for the example is detailed below. The execution flow for the example is detailed below.
#### Execution flow examples for the syft library #### Execution flow examples for the syft library
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant source as source.New(ubuntu:latest) participant source as source.New(ubuntu:latest)
@ -135,12 +115,14 @@ sequenceDiagram
### Syft Catalogers ### Syft Catalogers
##### Summary ##### Summary
Catalogers are the way in which syft is able to identify and construct packages given some amount of source metadata. Catalogers are the way in which syft is able to identify and construct packages given some amount of source metadata.
For example, Syft can locate and process `package-lock.json` files when performing filesystem scans. For example, Syft can locate and process `package-lock.json` files when performing filesystem scans.
See: [how to specify file globs](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/javascript/cataloger.go#L16-L21) See: [how to specify file globs](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/javascript/cataloger.go#L16-L21)
and an implementation of the [package-lock.json parser](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/javascript/cataloger.go#L16-L21) fora quick review. and an implementation of the [package-lock.json parser](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/javascript/cataloger.go#L16-L21) fora quick review.
#### Building a new Cataloger #### Building a new Cataloger
Catalogers must fulfill the interface [found here](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger.go). Catalogers must fulfill the interface [found here](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger.go).
This means that when building a new cataloger, the new struct must implement both method signatures of `Catalog` and `Name`. This means that when building a new cataloger, the new struct must implement both method signatures of `Catalog` and `Name`.

548
Makefile
View file

@ -1,24 +1,23 @@
BIN = syft BIN := syft
VERSION=$(shell git describe --dirty --always --tags) TEMP_DIR := ./.tmp
TEMPDIR = ./.tmp
# commands and versions # Command templates #################################
LINTCMD = $(TEMPDIR)/golangci-lint run --tests=false LINT_CMD := $(TEMP_DIR)/golangci-lint run --tests=false
GOIMPORTS_CMD = $(TEMPDIR)/gosimports -local github.com/anchore GOIMPORTS_CMD := $(TEMP_DIR)/gosimports -local github.com/anchore
RELEASE_CMD=$(TEMPDIR)/goreleaser release --rm-dist RELEASE_CMD := $(TEMP_DIR)/goreleaser release --rm-dist
SNAPSHOT_CMD=$(RELEASE_CMD) --skip-publish --snapshot SNAPSHOT_CMD := $(RELEASE_CMD) --skip-publish --skip-sign --snapshot
# tool versions # Tool versions #################################
GOLANGCILINT_VERSION = v1.50.1 GOLANGCILINT_VERSION := v1.50.1
GOSIMPORTS_VERSION = v0.3.5 GOSIMPORTS_VERSION := v0.3.5
BOUNCER_VERSION = v0.4.0 BOUNCER_VERSION := v0.4.0
CHRONICLE_VERSION = v0.4.2 CHRONICLE_VERSION := v0.4.2
GORELEASER_VERSION = v1.14.1 GORELEASER_VERSION := v1.14.1
YAJSV_VERSION = v1.4.1 YAJSV_VERSION := v1.4.1
COSIGN_VERSION = v1.13.1 COSIGN_VERSION := v1.13.1
QUILL_VERSION = v0.2.0 QUILL_VERSION := v0.2.0
# formatting variables # Formatting variables #################################
BOLD := $(shell tput -T linux bold) BOLD := $(shell tput -T linux bold)
PURPLE := $(shell tput -T linux setaf 5) PURPLE := $(shell tput -T linux setaf 5)
GREEN := $(shell tput -T linux setaf 2) GREEN := $(shell tput -T linux setaf 2)
@ -28,55 +27,23 @@ RESET := $(shell tput -T linux sgr0)
TITLE := $(BOLD)$(PURPLE) TITLE := $(BOLD)$(PURPLE)
SUCCESS := $(BOLD)$(GREEN) SUCCESS := $(BOLD)$(GREEN)
# test variables # Test variables #################################
RESULTSDIR = test/results COMPARE_DIR := ./test/compare
COMPARE_DIR = ./test/compare COMPARE_TEST_IMAGE := centos:8.2.2004
COMPARE_TEST_IMAGE = centos:8.2.2004 COVERAGE_THRESHOLD := 62 # the quality gate lower threshold for unit test total % coverage (by function statements)
COVER_REPORT = $(RESULTSDIR)/unit-coverage-details.txt
COVER_TOTAL = $(RESULTSDIR)/unit-coverage-summary.txt
# the quality gate lower threshold for unit test total % coverage (by function statements)
COVERAGE_THRESHOLD := 62
# CI cache busting values; change these if you want CI to not use previous stored cache ## Build variables #################################
INTEGRATION_CACHE_BUSTER="894d8ca" VERSION := $(shell git describe --dirty --always --tags)
CLI_CACHE_BUSTER="e5cdfd8" DIST_DIR := ./dist
BOOTSTRAP_CACHE="c7afb99ad" SNAPSHOT_DIR := ./snapshot
CHANGELOG := CHANGELOG.md
## Build variables OS := $(shell uname | tr '[:upper:]' '[:lower:]')
DISTDIR=./dist SNAPSHOT_BIN := $(realpath $(shell pwd)/$(SNAPSHOT_DIR)/$(OS)-build_$(OS)_amd64_v1/$(BIN))
SNAPSHOTDIR=./snapshot
OS=$(shell uname | tr '[:upper:]' '[:lower:]')
SNAPSHOT_BIN=$(realpath $(shell pwd)/$(SNAPSHOTDIR)/$(OS)-build_$(OS)_amd64_v1/$(BIN))
## Variable assertions
ifndef TEMPDIR
$(error TEMPDIR is not set)
endif
ifndef RESULTSDIR
$(error RESULTSDIR is not set)
endif
ifndef COMPARE_DIR
$(error COMPARE_DIR is not set)
endif
ifndef DISTDIR
$(error DISTDIR is not set)
endif
ifndef SNAPSHOTDIR
$(error SNAPSHOTDIR is not set)
endif
ifndef VERSION ifndef VERSION
$(error VERSION is not set) $(error VERSION is not set)
endif endif
ifndef REF_NAME
REF_NAME = $(VERSION)
endif
define title define title
@printf '$(TITLE)$(1)$(RESET)\n' @printf '$(TITLE)$(1)$(RESET)\n'
endef endef
@ -89,65 +56,58 @@ define safe_rm_rf_children
bash -c 'test -z "$(1)" && false || rm -rf $(1)/*' bash -c 'test -z "$(1)" && false || rm -rf $(1)/*'
endef endef
## Default Task
.DEFAULT_GOAL:=help .DEFAULT_GOAL:=help
## Tasks
.PHONY: all .PHONY: all
all: clean static-analysis test ## Run all linux-based checks (linting, license check, unit, integration, and linux compare tests) all: static-analysis test ## Run all linux-based checks (linting, license check, unit, integration, and linux compare tests)
@printf '$(SUCCESS)All checks pass!$(RESET)\n' @printf '$(SUCCESS)All checks pass!$(RESET)\n'
.PHONY: static-analysis
static-analysis: check-go-mod-tidy lint check-licenses check-json-schema-drift ## Run all static analysis checks
.PHONY: test .PHONY: test
test: unit validate-cyclonedx-schema integration benchmark compare-linux cli ## Run all tests (currently unit, integration, linux compare, and cli tests) test: unit integration validate-cyclonedx-schema benchmark cli ## Run all tests (currently unit, integration, linux compare, and cli tests)
.PHONY: ci-bootstrap
ci-bootstrap:
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y bc jq libxml2-utils
.PHONY: ## Bootstrapping targets #################################
ci-bootstrap-mac:
github_changelog_generator --version || sudo gem install github_changelog_generator
$(RESULTSDIR): .PHONY: bootstrap
mkdir -p $(RESULTSDIR) bootstrap: $(TEMP_DIR) bootstrap-go bootstrap-tools ## Download and install all tooling dependencies (+ prep tooling in the ./tmp dir)
$(call title,Bootstrapping dependencies)
$(TEMPDIR):
mkdir -p $(TEMPDIR)
.PHONY: bootstrap-tools .PHONY: bootstrap-tools
bootstrap-tools: $(TEMPDIR) bootstrap-tools: $(TEMP_DIR)
curl -sSfL https://raw.githubusercontent.com/anchore/quill/main/install.sh | sh -s -- -b $(TEMPDIR)/ $(QUILL_VERSION) curl -sSfL https://raw.githubusercontent.com/anchore/quill/main/install.sh | sh -s -- -b $(TEMP_DIR)/ $(QUILL_VERSION)
GO111MODULE=off GOBIN=$(realpath $(TEMPDIR)) go get -u golang.org/x/perf/cmd/benchstat GO111MODULE=off GOBIN=$(realpath $(TEMP_DIR)) go get -u golang.org/x/perf/cmd/benchstat
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ $(GOLANGCILINT_VERSION) curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMP_DIR)/ $(GOLANGCILINT_VERSION)
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ $(BOUNCER_VERSION) curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMP_DIR)/ $(BOUNCER_VERSION)
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ $(CHRONICLE_VERSION) curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMP_DIR)/ $(CHRONICLE_VERSION)
.github/scripts/goreleaser-install.sh -d -b $(TEMPDIR)/ $(GORELEASER_VERSION) .github/scripts/goreleaser-install.sh -d -b $(TEMP_DIR)/ $(GORELEASER_VERSION)
# the only difference between goimports and gosimports is that gosimports removes extra whitespace between import blocks (see https://github.com/golang/go/issues/20818) # the only difference between goimports and gosimports is that gosimports removes extra whitespace between import blocks (see https://github.com/golang/go/issues/20818)
GOBIN="$(realpath $(TEMPDIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION) GOBIN="$(realpath $(TEMP_DIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION)
GOBIN="$(realpath $(TEMPDIR))" go install github.com/neilpa/yajsv@$(YAJSV_VERSION) GOBIN="$(realpath $(TEMP_DIR))" go install github.com/neilpa/yajsv@$(YAJSV_VERSION)
GOBIN="$(realpath $(TEMPDIR))" go install github.com/sigstore/cosign/cmd/cosign@$(COSIGN_VERSION) GOBIN="$(realpath $(TEMP_DIR))" go install github.com/sigstore/cosign/cmd/cosign@$(COSIGN_VERSION)
.PHONY: bootstrap-go .PHONY: bootstrap-go
bootstrap-go: bootstrap-go:
go mod download go mod download
.PHONY: bootstrap $(TEMP_DIR):
bootstrap: $(RESULTSDIR) bootstrap-go bootstrap-tools ## Download and install all tooling dependencies (+ prep tooling in the ./tmp dir) mkdir -p $(TEMP_DIR)
$(call title,Bootstrapping dependencies)
.PHONY: static-analysis
static-analysis: check-go-mod-tidy check-licenses lint ## Static analysis targets #################################
.PHONY: lint .PHONY: lint
lint: ## Run gofmt + golangci lint checks lint: ## Run gofmt + golangci lint checks
$(call title,Running linters) $(call title,Running linters)
# ensure there are no go fmt differences # ensure there are no go fmt differences
@printf "files with gofmt issues: [$(shell gofmt -l -s .)]\n" @printf "files with gofmt issues: [$(shell gofmt -l -s .)]\n"
@test -z "$(shell gofmt -l -s .)" @test -z "$(shell gofmt -l -s .)"
# run all golangci-lint rules # run all golangci-lint rules
$(LINTCMD) $(LINT_CMD)
@[ -z "$(shell $(GOIMPORTS_CMD) -d .)" ] || (echo "goimports needs to be fixed" && false) @[ -z "$(shell $(GOIMPORTS_CMD) -d .)" ] || (echo "goimports needs to be fixed" && false)
# go tooling does not play well with certain filename characters, ensure the common cases don't result in future "go get" failures # go tooling does not play well with certain filename characters, ensure the common cases don't result in future "go get" failures
@ -155,243 +115,107 @@ lint: ## Run gofmt + golangci lint checks
@bash -c "[[ '$(MALFORMED_FILENAMES)' == '' ]] || (printf '\nfound unsupported filename characters:\n$(MALFORMED_FILENAMES)\n\n' && false)" @bash -c "[[ '$(MALFORMED_FILENAMES)' == '' ]] || (printf '\nfound unsupported filename characters:\n$(MALFORMED_FILENAMES)\n\n' && false)"
.PHONY: lint-fix .PHONY: lint-fix
lint-fix: ## Auto-format all source code + run golangci lint fixers lint-fix: ## Auto-format all source code + run golangci lint fixers
$(call title,Running lint fixers) $(call title,Running lint fixers)
gofmt -w -s . gofmt -w -s .
$(GOIMPORTS_CMD) -w . $(GOIMPORTS_CMD) -w .
$(LINTCMD) --fix $(LINT_CMD) --fix
go mod tidy go mod tidy
.PHONY: check-licenses .PHONY: check-licenses
check-licenses: ## Ensure transitive dependencies are compliant with the current license policy check-licenses: ## Ensure transitive dependencies are compliant with the current license policy
$(TEMPDIR)/bouncer check ./... $(call title,Checking for license compliance)
$(TEMP_DIR)/bouncer check ./...
check-go-mod-tidy: check-go-mod-tidy:
@ .github/scripts/go-mod-tidy-check.sh && echo "go.mod and go.sum are tidy!" @ .github/scripts/go-mod-tidy-check.sh && echo "go.mod and go.sum are tidy!"
check-json-schema-drift:
$(call title,Ensure there is no drift between the JSON schema and the code)
@git diff-index --quiet HEAD -- || (echo "there are uncommitted changes, please commit them before running this check" && false)
@make generate-json-schema || (echo "$(RED)$(BOLD)JSON schema drift detected!$(RESET)" && false)
@git diff-index --quiet HEAD -- || (echo "$(RED)$(BOLD)JSON schema drift detected!$(RESET)" && false)
## Testing targets #################################
.PHONY: unit
unit: $(TEMP_DIR) fixtures ## Run unit tests (with coverage)
$(call title,Running unit tests)
go test -coverprofile $(TEMP_DIR)/unit-coverage-details.txt $(shell go list ./... | grep -v anchore/syft/test)
@.github/scripts/coverage.py $(COVERAGE_THRESHOLD) $(TEMP_DIR)/unit-coverage-details.txt
.PHONY: integration
integration: ## Run integration tests
$(call title,Running integration tests)
go test -v ./test/integration
.PHONY: validate-cyclonedx-schema .PHONY: validate-cyclonedx-schema
validate-cyclonedx-schema: validate-cyclonedx-schema:
cd schema/cyclonedx && make cd schema/cyclonedx && make
.PHONY: unit .PHONY: cli
unit: $(RESULTSDIR) fixtures ## Run unit tests (with coverage) cli: $(SNAPSHOT_DIR) ## Run CLI tests
$(call title,Running unit tests) chmod 755 "$(SNAPSHOT_BIN)"
go test -coverprofile $(COVER_REPORT) $(shell go list ./... | grep -v anchore/syft/test) $(SNAPSHOT_BIN) version
@go tool cover -func $(COVER_REPORT) | grep total | awk '{print substr($$3, 1, length($$3)-1)}' > $(COVER_TOTAL) SYFT_BINARY_LOCATION='$(SNAPSHOT_BIN)' \
@echo "Coverage: $$(cat $(COVER_TOTAL))" go test -count=1 -timeout=15m -v ./test/cli
@if [ $$(echo "$$(cat $(COVER_TOTAL)) >= $(COVERAGE_THRESHOLD)" | bc -l) -ne 1 ]; then echo "$(RED)$(BOLD)Failed coverage quality gate (> $(COVERAGE_THRESHOLD)%)$(RESET)" && false; fi
## Benchmark test targets #################################
.PHONY: benchmark .PHONY: benchmark
benchmark: $(RESULTSDIR) ## Run benchmark tests and compare against the baseline (if available) benchmark: $(TEMP_DIR) ## Run benchmark tests and compare against the baseline (if available)
$(call title,Running benchmark tests) $(call title,Running benchmark tests)
go test -p 1 -run=^Benchmark -bench=. -count=5 -benchmem ./... | tee $(RESULTSDIR)/benchmark-$(REF_NAME).txt go test -p 1 -run=^Benchmark -bench=. -count=7 -benchmem ./... | tee $(TEMP_DIR)/benchmark-$(VERSION).txt
(test -s $(RESULTSDIR)/benchmark-main.txt && \ (test -s $(TEMP_DIR)/benchmark-main.txt && \
$(TEMPDIR)/benchstat $(RESULTSDIR)/benchmark-main.txt $(RESULTSDIR)/benchmark-$(REF_NAME).txt || \ $(TEMP_DIR)/benchstat $(TEMP_DIR)/benchmark-main.txt $(TEMP_DIR)/benchmark-$(VERSION).txt || \
$(TEMPDIR)/benchstat $(RESULTSDIR)/benchmark-$(REF_NAME).txt) \ $(TEMP_DIR)/benchstat $(TEMP_DIR)/benchmark-$(VERSION).txt) \
| tee $(RESULTSDIR)/benchstat.txt | tee $(TEMP_DIR)/benchstat.txt
.PHONY: show-benchstat .PHONY: show-benchstat
show-benchstat: show-benchstat:
@cat $(RESULTSDIR)/benchstat.txt @cat $(TEMP_DIR)/benchstat.txt
# note: this is used by CI to determine if the install test fixture cache (docker image tars) should be busted
install-fingerprint: ## Test-fixture-related targets #################################
cd test/install && \
# note: this is used by CI to determine if various test fixture cache should be restored or recreated
fingerprints:
$(call title,Creating all test cache input fingerprints)
# for IMAGE integration test fixtures
cd test/integration/test-fixtures && \
make cache.fingerprint make cache.fingerprint
install-test: $(SNAPSHOTDIR) # for JAVA BUILD test fixtures
cd test/install && \
make
install-test-cache-save: $(SNAPSHOTDIR)
cd test/install && \
make save
install-test-cache-load: $(SNAPSHOTDIR)
cd test/install && \
make load
install-test-ci-mac: $(SNAPSHOTDIR)
cd test/install && \
make ci-test-mac
.PHONY: integration
integration: ## Run integration tests
$(call title,Running integration tests)
go test -v ./test/integration
# note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted
integration-fingerprint:
$(call title,Integration test fixture fingerprint)
find test/integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | tee /dev/stderr | md5sum | tee test/integration/test-fixtures/cache.fingerprint && echo "$(INTEGRATION_CACHE_BUSTER)" >> test/integration/test-fixtures/cache.fingerprint
.PHONY: java-packages-fingerprint
java-packages-fingerprint:
$(call title,Java test fixture fingerprint)
cd syft/pkg/cataloger/java/test-fixtures/java-builds && \ cd syft/pkg/cataloger/java/test-fixtures/java-builds && \
make packages.fingerprint make packages.fingerprint
.PHONY: go-binaries-fingerprint # for GO BINARY test fixtures
go-binaries-fingerprint:
$(call title,Go binaries test fixture fingerprint)
cd syft/pkg/cataloger/golang/test-fixtures/archs && \ cd syft/pkg/cataloger/golang/test-fixtures/archs && \
make binaries.fingerprint make binaries.fingerprint
.PHONY: rpm-binaries-fingerprint # for RPM test fixtures
rpm-binaries-fingerprint:
$(call title,RPM binary test fixture fingerprint)
cd syft/pkg/cataloger/rpm/test-fixtures && \ cd syft/pkg/cataloger/rpm/test-fixtures && \
make rpms.fingerprint make rpms.fingerprint
# for INSTALL integration test fixtures
cd test/install && \
make cache.fingerprint
# for CLI test fixtures
cd test/cli/test-fixtures && \
make cache.fingerprint
.PHONY: fixtures .PHONY: fixtures
fixtures: fixtures:
$(call title,Generating test fixtures) $(call title,Generating test fixtures)
cd syft/pkg/cataloger/java/test-fixtures/java-builds && make cd syft/pkg/cataloger/java/test-fixtures/java-builds && make
cd syft/pkg/cataloger/rpm/test-fixtures && make cd syft/pkg/cataloger/rpm/test-fixtures && make
.PHONY: generate-json-schema
generate-json-schema: ## Generate a new json schema
cd schema/json && go run generate.go
.PHONY: generate-license-list
generate-license-list: ## Generate an updated spdx license list
go generate ./internal/spdxlicense/...
gofmt -s -w ./internal/spdxlicense
.PHONY: build
build: $(SNAPSHOTDIR) ## Build release snapshot binaries and packages
$(SNAPSHOTDIR): ## Build snapshot release binaries and packages
$(call title,Building snapshot artifacts)
# create a config with the dist dir overridden
echo "dist: $(SNAPSHOTDIR)" > $(TEMPDIR)/goreleaser.yaml
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
# build release snapshots
bash -c "SKIP_SIGNING=true $(SNAPSHOT_CMD) --skip-sign --config $(TEMPDIR)/goreleaser.yaml"
.PHONY: snapshot-with-signing
snapshot-with-signing: ## Build snapshot release binaries and packages (with dummy signing)
$(call title,Building snapshot artifacts (+ signing))
# create a config with the dist dir overridden
echo "dist: $(SNAPSHOTDIR)" > $(TEMPDIR)/goreleaser.yaml
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
# build release snapshots
bash -c "\
$(SNAPSHOT_CMD) \
--config $(TEMPDIR)/goreleaser.yaml \
|| (cat /tmp/quill-*.log && false)"
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
.PHONY: compare-mac
compare-mac: $(RESULTSDIR) $(SNAPSHOTDIR) ## Run compare tests on build snapshot binaries and packages (Mac)
$(call title,Running compare test: Run on Mac)
$(COMPARE_DIR)/mac.sh \
$(SNAPSHOTDIR) \
$(COMPARE_DIR) \
$(COMPARE_TEST_IMAGE) \
$(RESULTSDIR)
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
.PHONY: compare-linux
compare-linux: compare-test-deb-package-install compare-test-rpm-package-install ## Run compare tests on build snapshot binaries and packages (Linux)
.PHONY: compare-test-deb-package-install
compare-test-deb-package-install: $(RESULTSDIR) $(SNAPSHOTDIR)
$(call title,Running compare test: DEB install)
$(COMPARE_DIR)/deb.sh \
$(SNAPSHOTDIR) \
$(COMPARE_DIR) \
$(COMPARE_TEST_IMAGE) \
$(RESULTSDIR)
.PHONY: compare-test-rpm-package-install
compare-test-rpm-package-install: $(RESULTSDIR) $(SNAPSHOTDIR)
$(call title,Running compare test: RPM install)
$(COMPARE_DIR)/rpm.sh \
$(SNAPSHOTDIR) \
$(COMPARE_DIR) \
$(COMPARE_TEST_IMAGE) \
$(RESULTSDIR)
# note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted
cli-fingerprint:
$(call title,CLI test fixture fingerprint)
find test/cli/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee test/cli/test-fixtures/cache.fingerprint && echo "$(CLI_CACHE_BUSTER)" >> test/cli/test-fixtures/cache.fingerprint
.PHONY: cli
cli: $(SNAPSHOTDIR) ## Run CLI tests
chmod 755 "$(SNAPSHOT_BIN)"
$(SNAPSHOT_BIN) version
SYFT_BINARY_LOCATION='$(SNAPSHOT_BIN)' \
go test -count=1 -timeout=15m -v ./test/cli
.PHONY: changelog
changelog: clean-changelog CHANGELOG.md
@docker run -it --rm \
-v $(shell pwd)/CHANGELOG.md:/CHANGELOG.md \
rawkode/mdv \
-t 748.5989 \
/CHANGELOG.md
CHANGELOG.md:
$(TEMPDIR)/chronicle -vv > CHANGELOG.md
.PHONY: release
release: clean-dist CHANGELOG.md
$(call title,Publishing release artifacts)
# create a config with the dist dir overridden
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
# note: notarization cannot be done in parallel, thus --parallelism 1
bash -c "\
$(RELEASE_CMD) \
--config $(TEMPDIR)/goreleaser.yaml \
--parallelism 1 \
--release-notes <(cat CHANGELOG.md) \
|| (cat /tmp/quill-*.log && false)"
# TODO: turn this into a post-release hook
# upload the version file that supports the application version update check (excluding pre-releases)
.github/scripts/update-version-file.sh "$(DISTDIR)" "$(VERSION)"
.PHONY: clean
clean: clean-dist clean-snapshot clean-test-image-cache ## Remove previous builds, result reports, and test cache
$(call safe_rm_rf_children,$(RESULTSDIR))
.PHONY: clean-snapshot
clean-snapshot:
$(call safe_rm_rf,$(SNAPSHOTDIR))
rm -f $(TEMPDIR)/goreleaser.yaml
.PHONY: clean-dist
clean-dist: clean-changelog
$(call safe_rm_rf,$(DISTDIR))
rm -f $(TEMPDIR)/goreleaser.yaml
.PHONY: clean-changelog
clean-changelog:
rm -f CHANGELOG.md
clean-test-image-cache: clean-test-image-tar-cache clean-test-image-docker-cache ## Clean test image cache
.PHONY: clear-test-image-tar-cache
clean-test-image-tar-cache:
## Delete all test cache (built docker image tars)
find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" -delete
.PHONY: clear-test-image-docker-cache
clean-test-image-docker-cache:
## Purge all test docker images
docker images --format '{{.ID}} {{.Repository}}' | grep stereoscope-fixture- | awk '{print $$1}' | uniq | xargs -r docker rmi --force
.PHONY: show-test-image-cache .PHONY: show-test-image-cache
show-test-image-cache: ## Show all docker and image tar cache show-test-image-cache: ## Show all docker and image tar cache
$(call title,Docker daemon cache) $(call title,Docker daemon cache)
@docker images --format '{{.ID}} {{.Repository}}:{{.Tag}}' | grep stereoscope-fixture- | sort @docker images --format '{{.ID}} {{.Repository}}:{{.Tag}}' | grep stereoscope-fixture- | sort
@ -399,10 +223,150 @@ show-test-image-cache: ## Show all docker and image tar cache
@find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" | sort @find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" | sort
.PHONY: show-test-snapshots .PHONY: show-test-snapshots
show-test-snapshots: ## Show all test snapshots show-test-snapshots: ## Show all test snapshots
$(call title,Test snapshots) $(call title,Test snapshots)
@find . -type f -wholename "**/test-fixtures/snapshot/*" | sort @find . -type f -wholename "**/test-fixtures/snapshot/*" | sort
## install.sh testing targets #################################
install-test: $(SNAPSHOT_DIR)
cd test/install && \
make
install-test-cache-save: $(SNAPSHOT_DIR)
cd test/install && \
make save
install-test-cache-load: $(SNAPSHOT_DIR)
cd test/install && \
make load
install-test-ci-mac: $(SNAPSHOT_DIR)
cd test/install && \
make ci-test-mac
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
.PHONY: compare-mac
compare-mac: $(TEMP_DIR) $(SNAPSHOT_DIR) ## Run compare tests on build snapshot binaries and packages (Mac)
$(call title,Running compare test: Run on Mac)
$(COMPARE_DIR)/mac.sh \
$(SNAPSHOT_DIR) \
$(COMPARE_DIR) \
$(COMPARE_TEST_IMAGE) \
$(TEMP_DIR)
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
.PHONY: compare-linux
compare-linux: compare-test-deb-package-install compare-test-rpm-package-install ## Run compare tests on build snapshot binaries and packages (Linux)
.PHONY: compare-test-deb-package-install
compare-test-deb-package-install: $(TEMP_DIR) $(SNAPSHOT_DIR)
$(call title,Running compare test: DEB install)
$(COMPARE_DIR)/deb.sh \
$(SNAPSHOT_DIR) \
$(COMPARE_DIR) \
$(COMPARE_TEST_IMAGE) \
$(TEMP_DIR)
.PHONY: compare-test-rpm-package-install
compare-test-rpm-package-install: $(TEMP_DIR) $(SNAPSHOT_DIR)
$(call title,Running compare test: RPM install)
$(COMPARE_DIR)/rpm.sh \
$(SNAPSHOT_DIR) \
$(COMPARE_DIR) \
$(COMPARE_TEST_IMAGE) \
$(TEMP_DIR)
## Code generation targets #################################
.PHONY: generate-json-schema
generate-json-schema: ## Generate a new json schema
cd schema/json && go run generate.go
.PHONY: generate-license-list
generate-license-list: ## Generate an updated spdx license list
go generate ./internal/spdxlicense/...
gofmt -s -w ./internal/spdxlicense
## Build-related targets #################################
.PHONY: build
build: $(SNAPSHOT_DIR) ## Build release snapshot binaries and packages
$(SNAPSHOT_DIR): ## Build snapshot release binaries and packages
$(call title,Building snapshot artifacts)
# create a config with the dist dir overridden
echo "dist: $(SNAPSHOT_DIR)" > $(TEMP_DIR)/goreleaser.yaml
cat .goreleaser.yaml >> $(TEMP_DIR)/goreleaser.yaml
# build release snapshots
$(SNAPSHOT_CMD) --config $(TEMP_DIR)/goreleaser.yaml
.PHONY: changelog
changelog: clean-changelog $(CHANGELOG) ## Generate and show the changelog for the current unreleased version
@docker run -it --rm \
-v $(shell pwd)/$(CHANGELOG):/$(CHANGELOG) \
rawkode/mdv \
-t 748.5989 \
/$(CHANGELOG)
$(CHANGELOG):
$(TEMP_DIR)/chronicle -vv > $(CHANGELOG)
.PHONY: release
release: clean-dist $(CHANGELOG)
$(call title,Publishing release artifacts)
@.github/scripts/ci-check.sh
# create a config with the dist dir overridden
echo "dist: $(DIST_DIR)" > $(TEMP_DIR)/goreleaser.yaml
cat .goreleaser.yaml >> $(TEMP_DIR)/goreleaser.yaml
bash -c "\
$(RELEASE_CMD) \
--config $(TEMP_DIR)/goreleaser.yaml \
--release-notes <(cat $(CHANGELOG)) \
|| (cat /tmp/quill-*.log && false)"
# upload the version file that supports the application version update check (excluding pre-releases)
.github/scripts/update-version-file.sh "$(DIST_DIR)" "$(VERSION)"
## Cleanup targets #################################
.PHONY: clean
clean: clean-dist clean-snapshot clean-test-image-cache ## Remove previous builds, result reports, and test cache
$(call safe_rm_rf_children,$(TEMP_DIR))
.PHONY: clean-snapshot
clean-snapshot:
$(call safe_rm_rf,$(SNAPSHOT_DIR))
rm -f $(TEMP_DIR)/goreleaser.yaml
.PHONY: clean-dist
clean-dist: clean-changelog
$(call safe_rm_rf,$(DIST_DIR))
rm -f $(TEMP_DIR)/goreleaser.yaml
.PHONY: clean-changelog
clean-changelog:
rm -f $(CHANGELOG)
clean-test-image-cache: clean-test-image-tar-cache clean-test-image-docker-cache ## Clean test image cache
.PHONY: clear-test-image-tar-cache
clean-test-image-tar-cache: ## Delete all test cache (built docker image tars)
find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" -delete
.PHONY: clear-test-image-docker-cache
clean-test-image-docker-cache: ## Purge all test docker images
docker images --format '{{.ID}} {{.Repository}}' | grep stereoscope-fixture- | awk '{print $$1}' | uniq | xargs -r docker rmi --force
## Halp! #################################
.PHONY: help .PHONY: help
help: ## Display this help help: ## Display this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}' @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}'

View file

@ -71,5 +71,5 @@ $(PKGSDIR)/example-java-app: $(PKGSDIR)/example-java-app-maven-0.1.0.jar
# we need a way to determine if CI should bust the test cache based on the source material # we need a way to determine if CI should bust the test cache based on the source material
$(PKGSDIR).fingerprint: clean-examples $(PKGSDIR).fingerprint: clean-examples
mkdir -p $(PKGSDIR) mkdir -p $(PKGSDIR)
find example-* -type f -exec sha256sum {} \; | sort | tee /dev/stderr | tee $(PKGSDIR).fingerprint find example-* build-* Makefile -type f -exec sha256sum {} \; | sort | tee /dev/stderr | tee $(PKGSDIR).fingerprint
sha256sum $(PKGSDIR).fingerprint sha256sum $(PKGSDIR).fingerprint

View file

@ -0,0 +1,6 @@
# change these if you want CI to not use previous stored cache
CLI_CACHE_BUSTER := "e5cdfd8"
.PHONY: cache.fingerprint
cache.fingerprint:
find image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee cache.fingerprint && echo "$(CLI_CACHE_BUSTER)" >> cache.fingerprint

View file

@ -0,0 +1,6 @@
# change these if you want CI to not use previous stored cache
INTEGRATION_CACHE_BUSTER := "894d8ca"
.PHONY: cache.fingerprint
cache.fingerprint:
find image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | tee /dev/stderr | md5sum | tee cache.fingerprint && echo "$(INTEGRATION_CACHE_BUSTER)" >> cache.fingerprint