mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
Refactor install.sh (#765)
* [wip] get assets based on gh api Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * put install.sh download_asset fn under test Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * put install.sh install_asset fn under test Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * use zip for darwin installs Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * fix install.sh negative test cases Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * allow errors to propagate in install.sh Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * remove exit on error from install.sh tests Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add more docs around install.sh helpers Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add integration tests for install.sh Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add install.sh testing to pipeline Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * add install test cache to CI Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * make colors globally available Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * test download against github release Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * always test release-based install against latest release Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * use better install.sh test names Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import json
|
||||
import collections
|
||||
|
||||
INDENT = " "
|
||||
|
||||
|
||||
Metadata = collections.namedtuple("Metadata", "metadata sources")
|
||||
Package = collections.namedtuple("Package", "name type version")
|
||||
|
||||
|
||||
class Syft:
|
||||
def __init__(self, report_path):
|
||||
self.report_path = report_path
|
||||
|
||||
def _enumerate_section(self, section):
|
||||
with open(self.report_path) as json_file:
|
||||
data = json.load(json_file)
|
||||
for entry in data[section]:
|
||||
yield entry
|
||||
|
||||
def packages(self):
|
||||
packages = set()
|
||||
metadata = collections.defaultdict(dict)
|
||||
for entry in self._enumerate_section(section="artifacts"):
|
||||
package = Package(
|
||||
name=entry["name"], type=entry["type"], version=entry["version"]
|
||||
)
|
||||
|
||||
packages.add(package)
|
||||
metadata[package.type][package] = Metadata(
|
||||
# note: the metadata entry is optional
|
||||
metadata=repr(entry.get("metadata", "")), sources=repr(entry["locations"])
|
||||
)
|
||||
return packages, metadata
|
||||
|
||||
|
||||
def print_rows(rows):
|
||||
if not rows:
|
||||
return
|
||||
widths = []
|
||||
for col, _ in enumerate(rows[0]):
|
||||
width = max(len(row[col]) for row in rows) + 2 # padding
|
||||
widths.append(width)
|
||||
for row in rows:
|
||||
print("".join(word.ljust(widths[col_idx]) for col_idx, word in enumerate(row)))
|
||||
|
||||
|
||||
def main(baseline_report, new_report):
|
||||
report1_obj = Syft(report_path=baseline_report)
|
||||
report1_packages, report1_metadata = report1_obj.packages()
|
||||
|
||||
report2_obj = Syft(report_path=new_report)
|
||||
report2_packages, report2_metadata = report2_obj.packages()
|
||||
|
||||
if len(report2_packages) == 0 or len(report1_packages) == 0:
|
||||
# we are purposefully selecting test images that are guaranteed to have packages, so this should never happen
|
||||
print(colors.bold + colors.fg.red + "no packages found!", colors.reset)
|
||||
return 1
|
||||
|
||||
same_packages = report2_packages & report1_packages
|
||||
percent_overlap_packages = (
|
||||
float(len(same_packages)) / float(len(report1_packages))
|
||||
) * 100.0
|
||||
|
||||
extra_packages = report2_packages - report1_packages
|
||||
missing_packages = report1_packages - report2_packages
|
||||
|
||||
report1_metadata_set = set()
|
||||
for package in report1_packages:
|
||||
metadata = report1_metadata[package.type][package]
|
||||
report1_metadata_set.add((package, metadata))
|
||||
|
||||
report2_metadata_set = set()
|
||||
for package in report2_packages:
|
||||
metadata = report2_metadata[package.type][package]
|
||||
report2_metadata_set.add((package, metadata))
|
||||
|
||||
same_metadata = report2_metadata_set & report1_metadata_set
|
||||
percent_overlap_metadata = 0
|
||||
if len(report1_metadata_set) > 0:
|
||||
percent_overlap_metadata = (
|
||||
float(len(same_metadata)) / float(len(report1_metadata_set))
|
||||
) * 100.0
|
||||
|
||||
if extra_packages:
|
||||
rows = []
|
||||
print(colors.bold + "Extra packages:", colors.reset)
|
||||
for package in sorted(list(extra_packages)):
|
||||
rows.append([INDENT, repr(package)])
|
||||
print_rows(rows)
|
||||
print()
|
||||
|
||||
if missing_packages:
|
||||
rows = []
|
||||
print(colors.bold + "Missing packages:", colors.reset)
|
||||
for package in sorted(list(missing_packages)):
|
||||
rows.append([INDENT, repr(package)])
|
||||
print_rows(rows)
|
||||
print()
|
||||
|
||||
print(colors.bold+"Summary:", colors.reset)
|
||||
print(" Baseline Packages: %d" % len(report1_packages))
|
||||
print(" New Packages: %d" % len(report2_packages))
|
||||
print(
|
||||
" Baseline Packages Matched: %.2f %% (%d/%d packages)"
|
||||
% (percent_overlap_packages, len(same_packages), len(report1_packages))
|
||||
)
|
||||
print(
|
||||
" Baseline Metadata Matched: %.2f %% (%d/%d metadata)"
|
||||
% (percent_overlap_metadata, len(same_metadata), len(report1_metadata_set))
|
||||
)
|
||||
|
||||
if len(report1_packages) != len(report2_packages):
|
||||
print(colors.bold + " Quality Gate: " + colors.fg.red + "FAILED (requires exact name & version match)\n", colors.reset)
|
||||
return 1
|
||||
else:
|
||||
print(colors.bold + " Quality Gate: " + colors.fg.green + "pass\n", colors.reset)
|
||||
return 0
|
||||
|
||||
|
||||
class colors:
|
||||
reset='\033[0m'
|
||||
bold='\033[01m'
|
||||
disable='\033[02m'
|
||||
underline='\033[04m'
|
||||
reverse='\033[07m'
|
||||
strikethrough='\033[09m'
|
||||
invisible='\033[08m'
|
||||
class fg:
|
||||
black='\033[30m'
|
||||
red='\033[31m'
|
||||
green='\033[32m'
|
||||
orange='\033[33m'
|
||||
blue='\033[34m'
|
||||
purple='\033[35m'
|
||||
cyan='\033[36m'
|
||||
lightgrey='\033[37m'
|
||||
darkgrey='\033[90m'
|
||||
lightred='\033[91m'
|
||||
lightgreen='\033[92m'
|
||||
yellow='\033[93m'
|
||||
lightblue='\033[94m'
|
||||
pink='\033[95m'
|
||||
lightcyan='\033[96m'
|
||||
class bg:
|
||||
black='\033[40m'
|
||||
red='\033[41m'
|
||||
green='\033[42m'
|
||||
orange='\033[43m'
|
||||
blue='\033[44m'
|
||||
purple='\033[45m'
|
||||
cyan='\033[46m'
|
||||
lightgrey='\033[47m'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\nComparing two Syft reports...\n")
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit("please provide two Syft json files")
|
||||
|
||||
rc = main(sys.argv[1], sys.argv[2])
|
||||
sys.exit(rc)
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
DISTDIR=$1
|
||||
ACC_DIR=$2
|
||||
TEST_IMAGE=$3
|
||||
RESULTSDIR=$4
|
||||
|
||||
TEST_TYPE=deb
|
||||
WORK_DIR=`mktemp -d -t "syft-acceptance-test-${TEST_TYPE}-XXXXXX"`
|
||||
NORMAL_TEST_IMAGE=$(echo ${TEST_IMAGE} | tr ':' '-' )
|
||||
REPORT=${WORK_DIR}/acceptance-${TEST_TYPE}-${NORMAL_TEST_IMAGE}.json
|
||||
GOLDEN_REPORT=${ACC_DIR}/test-fixtures/acceptance-${NORMAL_TEST_IMAGE}.json
|
||||
|
||||
# check if tmp dir was created
|
||||
if [[ ! "${WORK_DIR}" || ! -d "${WORK_DIR}" ]]; then
|
||||
echo "Could not create temp dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function cleanup {
|
||||
# we should still preserve previous failures
|
||||
exit_code=$?
|
||||
rm -rf "${WORK_DIR}"
|
||||
exit ${exit_code}
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# fetch test image
|
||||
docker pull ${TEST_IMAGE}
|
||||
|
||||
# install and run syft
|
||||
docker run --rm \
|
||||
-v /var/run/docker.sock://var/run/docker.sock \
|
||||
-v /${PWD}:/src \
|
||||
-v ${WORK_DIR}:${WORK_DIR} \
|
||||
-e SYFT_CHECK_FOR_APP_UPDATE=0 \
|
||||
-w /src \
|
||||
ubuntu:latest \
|
||||
/bin/bash -x -c "\
|
||||
DEBIAN_FRONTEND=noninteractive apt install ${DISTDIR}/syft_*_linux_amd64.deb -y && \
|
||||
syft version && \
|
||||
syft packages ${TEST_IMAGE} -vv -o json > ${REPORT} \
|
||||
"
|
||||
|
||||
# keep the generated report around
|
||||
mkdir -p ${RESULTSDIR}
|
||||
cp ${REPORT} ${RESULTSDIR}
|
||||
|
||||
# compare the results to a known good output
|
||||
${ACC_DIR}/compare.py \
|
||||
${GOLDEN_REPORT} \
|
||||
${REPORT} | tee ${RESULTSDIR}/acceptance-${TEST_TYPE}.txt
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
DISTDIR=$1
|
||||
ACC_DIR=$2
|
||||
TEST_IMAGE=$3
|
||||
RESULTSDIR=$4
|
||||
|
||||
TEST_IMAGE_TAR=image.tar
|
||||
TEST_TYPE=mac
|
||||
WORK_DIR=`mktemp -d -t "syft-acceptance-test-${TEST_TYPE}-XXXXXX"`
|
||||
NORMAL_TEST_IMAGE=$(echo ${TEST_IMAGE} | tr ':' '-' )
|
||||
REPORT=${WORK_DIR}/acceptance-${TEST_TYPE}-${NORMAL_TEST_IMAGE}.json
|
||||
GOLDEN_REPORT=${ACC_DIR}/test-fixtures/acceptance-${NORMAL_TEST_IMAGE}.json
|
||||
|
||||
# check if tmp dir was created
|
||||
if [[ ! "${WORK_DIR}" || ! -d "${WORK_DIR}" ]]; then
|
||||
echo "Could not create temp dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function cleanup {
|
||||
# we should still preserve previous failures
|
||||
exit_code=$?
|
||||
rm -rf "${WORK_DIR}"
|
||||
exit ${exit_code}
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# fetch test image
|
||||
if [[ -f ${TEST_IMAGE_TAR} ]]
|
||||
then
|
||||
echo "using existing image"
|
||||
else
|
||||
skopeo --version || brew install skopeo
|
||||
skopeo --override-os linux copy "docker://docker.io/${TEST_IMAGE}" "docker-archive:${TEST_IMAGE_TAR}"
|
||||
fi
|
||||
|
||||
# run syft
|
||||
SYFT_PATH="${DISTDIR}/syft-macos_darwin_amd64/syft"
|
||||
chmod 755 "${SYFT_PATH}"
|
||||
"${SYFT_PATH}" version
|
||||
SYFT_CHECK_FOR_APP_UPDATE=0 "${SYFT_PATH}" packages docker-archive:${TEST_IMAGE_TAR} -vv -o json > "${REPORT}"
|
||||
|
||||
# keep the generated report around
|
||||
mkdir -p ${RESULTSDIR}
|
||||
cp ${REPORT} ${RESULTSDIR}
|
||||
|
||||
# compare the results to a known good output
|
||||
${ACC_DIR}/compare.py \
|
||||
${GOLDEN_REPORT} \
|
||||
${REPORT} | tee ${RESULTSDIR}/acceptance-${TEST_TYPE}.txt
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
DISTDIR=$1
|
||||
ACC_DIR=$2
|
||||
TEST_IMAGE=$3
|
||||
RESULTSDIR=$4
|
||||
|
||||
EXIT_CODE=1
|
||||
TEST_TYPE=rpm
|
||||
WORK_DIR=`mktemp -d -t "syft-acceptance-test-${TEST_TYPE}-XXXXXX"`
|
||||
NORMAL_TEST_IMAGE=$(echo ${TEST_IMAGE} | tr ':' '-' )
|
||||
REPORT=${WORK_DIR}/acceptance-${TEST_TYPE}-${NORMAL_TEST_IMAGE}.json
|
||||
GOLDEN_REPORT=${ACC_DIR}/test-fixtures/acceptance-${NORMAL_TEST_IMAGE}.json
|
||||
|
||||
# check if tmp dir was created
|
||||
if [[ ! "${WORK_DIR}" || ! -d "${WORK_DIR}" ]]; then
|
||||
echo "Could not create temp dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function cleanup {
|
||||
rm -rf "${WORK_DIR}"
|
||||
exit ${EXIT_CODE}
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
# fetch test image
|
||||
docker pull ${TEST_IMAGE}
|
||||
|
||||
# install and run syft
|
||||
docker run --rm \
|
||||
-v /var/run/docker.sock://var/run/docker.sock \
|
||||
-v /${PWD}:/src \
|
||||
-v ${WORK_DIR}:${WORK_DIR} \
|
||||
-e SYFT_CHECK_FOR_APP_UPDATE=0 \
|
||||
-w /src \
|
||||
centos:latest \
|
||||
/bin/bash -x -c "\
|
||||
rpm -ivh ${DISTDIR}/syft_*_linux_amd64.rpm && \
|
||||
syft version && \
|
||||
syft packages ${TEST_IMAGE} -vv -o json > ${REPORT} \
|
||||
"
|
||||
|
||||
# keep the generated report around
|
||||
mkdir -p ${RESULTSDIR}
|
||||
cp ${REPORT} ${RESULTSDIR}
|
||||
|
||||
# compare the results to a known good output
|
||||
${ACC_DIR}/compare.py \
|
||||
${GOLDEN_REPORT} \
|
||||
${REPORT} | tee ${RESULTSDIR}/acceptance-${TEST_TYPE}.txt
|
||||
|
||||
EXIT_CODE=0
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user