Skip to content

Commit

Permalink
[wip] pr adjustments
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman committed Dec 23, 2024
1 parent dfc73c8 commit 0d4a2fd
Show file tree
Hide file tree
Showing 15 changed files with 420 additions and 146 deletions.
20 changes: 17 additions & 3 deletions config/grype-db-manager/include.d/validate.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
# validate:

listing:
image: "centos:8.2.2004"
minimum-packages: 85
minimum-vulnerabilities: 400
image: "alpine:3.9.2"
minimum-packages: 10
minimum-vulnerabilities: 90

expected-providers:
- alpine
- amazon
- chainguard
- debian
- github
- mariner
- nvd
- oracle
- rhel
- sles
- ubuntu
- wolfi

default-max-year: 2021
gates:
Expand Down
2 changes: 1 addition & 1 deletion manager/src/grype_db_manager/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class Validate:
default_max_year: int = 2021
gates: list[ValidateDB] = field(default_factory=list)
listing: ValidateListing = field(default_factory=ValidateListing)

expected_providers: list[str] = field(default_factory=list)

@dataclass()
class ListingReplica:
Expand Down
30 changes: 18 additions & 12 deletions manager/src/grype_db_manager/cli/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def show_db(cfg: config.Application, db_uuid: str) -> None:
"--skip-namespace-check",
"skip_namespace_check",
is_flag=True,
help="do not ensure the minimum expected namespaces are present",
help="do not ensure the minimum expected namespaces are present (for v6+ this is a providers-based check)",
)
@click.argument("db-uuid")
@click.pass_obj
Expand Down Expand Up @@ -131,9 +131,8 @@ def validate_db(
# ensure the minimum number of namespaces are present
db_manager.validate_namespaces(db_uuid=db_uuid)
else:
# TODO: implement me
msg = "namespace validation for schema v6+ is not yet implemented"
raise NotImplementedError(msg)
# ensure the minimum number of namespaces are present
db_manager.validate_providers(db_uuid=db_uuid, expected=cfg.validate.expected_providers)

_validate_db(ctx, cfg, db_info, images, db_uuid, verbosity, recapture)

Expand All @@ -154,15 +153,15 @@ def _validate_db(
verbosity: int,
recapture: bool,
) -> None:
if db_info.schema_version >= 6:
# TODO: not implemented yet
msg = "validation for schema v6+ is not yet implemented"
raise NotImplementedError(msg)

# resolve tool versions and install them
yardstick.store.config.set_values(store_root=cfg.data.yardstick_root)

grype_version = db.schema.grype_version(db_info.schema_version)
basis_grype_version = grype_version

if db_info.schema_version >= 6:
# TODO: we don't have any published v6 grype databases yet
basis_grype_version = db.schema.grype_version(5)

result_sets = {}
for idx, rs in enumerate(cfg.validate.gates):
Expand All @@ -189,18 +188,25 @@ def _validate_db(
label="custom-db",
name="grype",
version=grype_version + f"+import-db={db_info.archive_path}",
profile="v6",
),
ycfg.Tool(
name="grype",
version=grype_version,
version=basis_grype_version,
),
],
),
)

yardstick_cfg = ycfg.Application(
profiles=ycfg.Profiles(
data={},
data={
"grype[custom-db]": {
"v6": {
"config_path": "./.grype-db-v6.yaml"
},
},
},
),
store_root=cfg.data.yardstick_root,
default_max_year=cfg.validate.default_max_year,
Expand Down Expand Up @@ -308,7 +314,7 @@ def upload_db(cfg: config.Application, db_uuid: str, ttl_seconds: int) -> None:
"--skip-namespace-check",
"skip_namespace_check",
is_flag=True,
help="do not ensure the minimum expected namespaces are present",
help="do not ensure the minimum expected namespaces are present (for v6+ this is a providers-based check)",
)
@click.option("--verbose", "-v", "verbosity", count=True, help="show details of all comparisons")
@click.pass_obj
Expand Down
2 changes: 1 addition & 1 deletion manager/src/grype_db_manager/data/schema-info.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
{
"schema": "6",
"grype-version": "initial-db-dir",
"grype-version": "feat/v6-query-api",
"supported": false
}
]
Expand Down
4 changes: 2 additions & 2 deletions manager/src/grype_db_manager/db/latest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import tempfile
import threading
from datetime import datetime
from dataclasses import dataclass
from http.server import HTTPServer, SimpleHTTPRequestHandler
from typing import TYPE_CHECKING
Expand All @@ -16,7 +17,6 @@
from grype_db_manager import grype

if TYPE_CHECKING:
import datetime
from collections.abc import Iterator

LATEST_FILENAME = "latest.json"
Expand All @@ -32,7 +32,7 @@ class Latest:
schema_version: str | None = None

# timestamp the database was built
built: datetime.datetime | None = None
built: datetime | None = None

# path to a DB archive relative to the listing file hosted location (NOT the absolute URL)
path: str = ""
Expand Down
27 changes: 26 additions & 1 deletion manager/src/grype_db_manager/grypedb.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@
"wolfi:rolling",
]


def expected_namespaces(schema_version: int) -> list[str]:
if schema_version <= 3:
return v3_expected_namespaces
Expand All @@ -259,6 +258,9 @@ class DBInvalidException(Exception):
class DBNamespaceException(Exception):
pass

class DBProviderException(Exception):
pass


class DBManager:
def __init__(self, root_dir: str):
Expand All @@ -285,6 +287,20 @@ def new_session(self) -> str:

return db_uuid

def list_providers(self, db_uuid: str) -> list[str]:
_, build_dir = self.db_paths(db_uuid=db_uuid)
# a sqlite3 db
db_path = os.path.join(build_dir, "vulnerability.db")

# select distinct values in the "namespace" column of the "vulnerability" table
con = sqlite3.connect(db_path)
crsr = con.cursor()
crsr.execute("SELECT DISTINCT id FROM providers")
result = crsr.fetchall()
con.close()

return sorted([r[0] for r in result])

def list_namespaces(self, db_uuid: str) -> list[str]:
_, build_dir = self.db_paths(db_uuid=db_uuid)
# a sqlite3 db
Expand All @@ -305,6 +321,15 @@ def list_namespaces(self, db_uuid: str) -> list[str]:

return sorted([r[0] for r in result])

def validate_providers(self, db_uuid: str, expected: list[str]) -> None:
missing_providers = set(expected) - set(self.list_providers(db_uuid=db_uuid))

if missing_providers:
msg = f"missing providers in DB {db_uuid!r}: {sorted(missing_providers)!r}"
raise DBProviderException(msg)

logging.info(f"minimum expected providers present in {db_uuid!r}")

def validate_namespaces(self, db_uuid: str) -> None:
db_info = self.get_db_info(db_uuid)
expected = expected_namespaces(db_info.schema_version)
Expand Down
2 changes: 2 additions & 0 deletions manager/tests/cli/.grype-db-manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ distribution:
download-url-prefix: http://localhost:4566/testbucket

validate:
expected-providers:
- oracle
listing:
image: "docker.io/oraclelinux:6@sha256:a06327c0f1d18d753f2a60bb17864c84a850bb6dcbcf5946dd1a8123f6e75495"
minimum-packages: 10 # 14 as of 2023-08-14, leaving some room for possible out-of-band changes to the data
Expand Down
2 changes: 2 additions & 0 deletions manager/tests/cli/.grype-db-v6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exp:
dbv6: true
2 changes: 1 addition & 1 deletion manager/tests/cli/.grype-db.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ provider:
configs:
# let's use a limited set of providers that we can show in isolation the setup is generally working. We don't
# need all providers / an entire database to test the workflow.
- name: oracle
- name: amazon
kind: vunnel

vunnel:
Expand Down
4 changes: 2 additions & 2 deletions manager/tests/cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ RESET := $(shell tput -T linux sgr0)
test: virtual-env-check ## Run CLI tests
pytest . -vv -o log_cli=true

.PHONY: vunnel-data
vunnel-data: cli-test-data/vunnel/oracle
.PHONY: vunnel-oracle-data
vunnel-oracle-data: cli-test-data/vunnel/oracle

cli-test-data/vunnel/oracle: ## Prepare oracle data for CLI tests
mkdir -p cli-test-data/vunnel
Expand Down
6 changes: 3 additions & 3 deletions manager/tests/cli/test_legacy_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_workflow_1(cli_env, command, logger):

logger.step("setup: clear previous data")
command.run("make clean-manager", env=cli_env)
command.run("make vunnel-data", env=cli_env)
command.run("make vunnel-oracle-data", env=cli_env)

logger.step("case 1: create the DB")
stdout, _ = command.run("grype-db-manager -v db build -s 5", env=cli_env)
Expand All @@ -34,7 +34,7 @@ def test_workflow_2(cli_env, command, logger):

logger.step("setup: create the DB")
command.run("make clean-manager", env=cli_env)
command.run("make vunnel-data", env=cli_env)
command.run("make vunnel-oracle-data", env=cli_env)

# create the database
stdout, _ = command.run("grype-db-manager -v db build -s 5", env=cli_env)
Expand Down Expand Up @@ -175,7 +175,7 @@ def test_workflow_4(cli_env, command, logger, tmp_path, grype):

logger.step("setup: clean manager and prepare data")
command.run("make clean-manager", env=cli_env)
command.run("make vunnel-data", env=cli_env)
command.run("make vunnel-oracle-data", env=cli_env)
command.run("make install-oracle-labels", env=cli_env)

logger.step("setup: start mock S3 and upload initial data")
Expand Down
115 changes: 57 additions & 58 deletions manager/tests/cli/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_workflow_1(cli_env, command, logger, tmp_path, grype):

logger.step("setup: clear previous data")
command.run("make clean-manager", env=cli_env)
command.run("make vunnel-data", env=cli_env)
command.run("make vunnel-oracle-data", env=cli_env)

logger.step("setup: start mock S3")
with command.pushd("s3-mock", logger):
Expand Down Expand Up @@ -70,60 +70,59 @@ def test_workflow_1(cli_env, command, logger, tmp_path, grype):
command.run("docker compose down -t 1 -v", env=cli_env)


# TODO: introduce this when there is v6 matching logic implemented
# @pytest.mark.usefixtures("cli_env")
# def test_workflow_2(cli_env, command, logger):
# """
# workflow 2: validate DB
# This test creates a database from raw vunnel data and performs validations via the quality gate.
# """
#
# logger.step("setup: create the DB")
# command.run("make clean-manager", env=cli_env)
# command.run("make vunnel-data", env=cli_env)
#
# # create the database
# stdout, _ = command.run("grype-db-manager -v db build -s 6", env=cli_env)
# assert stdout.strip(), "Expected non-empty output"
# db_id = stdout.splitlines()[-1] # Get the last line as the DB ID
#
# ### case 1: fail DB validation (too many unknowns) ###
# logger.step("case 1: fail DB validation (too many unknowns)")
# command.run("make clean-yardstick-labels", env=cli_env)
#
# # workaround for Go 1.23+ parent directory module lookup
# cli_env["GOWORK"] = "off"
#
# stdout, _ = command.run(
# f"grype-db-manager db validate {db_id} -vvv --skip-namespace-check --recapture",
# env=cli_env,
# expect_fail=True,
# )
# assert "current indeterminate matches % is greater than 10%" in stdout
#
# ### case 2: fail DB validation (missing namespaces) ###
# logger.step("case 2: fail DB validation (missing namespaces)")
# command.run("make clean-yardstick-labels", env=cli_env)
#
# logger.info("installing labels")
# command.run("make install-oracle-labels", env=cli_env)
#
# _, stderr = command.run(
# f"grype-db-manager db validate {db_id} -vvv",
# env=cli_env,
# expect_fail=True,
# )
# assert "missing namespaces in DB" in stderr
#
# ### case 3: pass DB validation ###
# logger.step("case 3: pass DB validation")
# command.run("make clean-yardstick-labels", env=cli_env)
#
# logger.info("installing labels")
# command.run("make install-oracle-labels", env=cli_env)
#
# stdout, _ = command.run(
# f"grype-db-manager db validate {db_id} -vvv --skip-namespace-check",
# env=cli_env,
# )
# assert "Quality gate passed!" in stdout
@pytest.mark.usefixtures("cli_env")
def test_workflow_2(cli_env, command, logger):
"""
workflow 2: validate DB
This test creates a database from raw vunnel data and performs validations via the quality gate.
"""

logger.step("setup: create the DB")
command.run("make clean-manager", env=cli_env)
command.run("make vunnel-oracle-data", env=cli_env)

# create the database
stdout, _ = command.run("grype-db-manager -v db build -s 6", env=cli_env)
assert stdout.strip(), "Expected non-empty output"
db_id = stdout.splitlines()[-1] # Get the last line as the DB ID

### case 1: fail DB validation (too many unknowns) ###
logger.step("case 1: fail DB validation (too many unknowns)")
command.run("make clean-yardstick-labels", env=cli_env)

# workaround for Go 1.23+ parent directory module lookup
cli_env["GOWORK"] = "off"

stdout, _ = command.run(
f"grype-db-manager db validate {db_id} -vvv --recapture",
env=cli_env,
expect_fail=True,
)
assert "current indeterminate matches % is greater than 10%" in stdout

### case 2: fail DB validation (missing providers) ###
logger.step("case 2: fail DB validation (missing providers)")
command.run("make clean-yardstick-labels", env=cli_env)

logger.info("installing labels")
command.run("make install-oracle-labels", env=cli_env)

_, stderr = command.run(
f"grype-db-manager db validate {db_id} -vvv",
env=cli_env,
expect_fail=True,
)
assert "missing providers in DB" in stderr

### case 3: pass DB validation ###
logger.step("case 3: pass DB validation")
command.run("make clean-yardstick-labels", env=cli_env)

logger.info("installing labels")
command.run("make install-oracle-labels", env=cli_env)

stdout, _ = command.run(
f"grype-db-manager db validate {db_id} -vvv",
env=cli_env,
)
assert "Quality gate passed!" in stdout
Loading

0 comments on commit 0d4a2fd

Please sign in to comment.