From 46ed2fdc8b62c97577e81cecfbbc2a761dabcddf Mon Sep 17 00:00:00 2001 From: Jakub Frejlach Date: Tue, 3 Oct 2023 15:47:43 +0200 Subject: [PATCH 1/8] Introduce example Griffon plugin --- griffon/commands/plugins/README.md | 9 +++++++++ griffon/commands/plugins/example.py | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 griffon/commands/plugins/example.py diff --git a/griffon/commands/plugins/README.md b/griffon/commands/plugins/README.md index a1f5fea..904d606 100644 --- a/griffon/commands/plugins/README.md +++ b/griffon/commands/plugins/README.md @@ -1,3 +1,12 @@ # Custom plugins Plugins are picked up dynamically. + +Each plugin is represented by a single Python file (`.py` extension) which contains +function `plugins` decorated with `click.group` which is a Griffon plugin bare minimum. + +Each plugin needs to comply to the following bare minimum: +* single file with `.py` extension +* file contains function called `plugins` decorated with suitable `click` decorator (`click.group`, `click.command`, etc.) + +See [example plugin](example.py) for the reference. diff --git a/griffon/commands/plugins/example.py b/griffon/commands/plugins/example.py new file mode 100644 index 0000000..82bbe43 --- /dev/null +++ b/griffon/commands/plugins/example.py @@ -0,0 +1,26 @@ +""" +minimal example of the Griffon plugin +""" + +import click + + +@click.group(help="Example plugin") +@click.pass_context +def plugins(ctx): + """Example plugin""" + pass + + +@plugins.command() +def test(): + """Test command""" + click.echo("Command 'test' of the 'example' plugin executed") + + +@plugins.command() +@click.option("-m", "--message", "message", help="Message to print out.") +def test_with_option(message): + """Test with option command""" + click.echo("Command 'test_with_option' of the 'example' plugin executed") + click.echo(message) From a5e8f7f8d506c92d8da60a6e92e770a60c0245ad Mon Sep 17 00:00:00 2001 From: Jakub Frejlach Date: Mon, 13 Nov 2023 16:03:41 +0100 Subject: [PATCH 2/8] Rename OSIDB/Corgi API env vars to SERVER env vars This caused a lot of confusion whether to assign OSIDB/Corgi API URL or more generally server URL. Since Griffon only needs general url name of these variables were changed --- Containerfile | 8 ++-- Containerfile-dev | 8 ++-- Makefile | 4 +- docs/user_guide.md | 8 ++-- griffon/__init__.py | 43 ++++++++++++++----- griffon/autocomplete/__init__.py | 16 +++---- griffon/commands/docs.py | 6 +-- .../entities/community_component_registry.py | 26 +++++------ griffon/commands/entities/corgi.py | 30 ++++++------- .../entities/osidb/affects/__init__.py | 6 +-- .../commands/entities/osidb/affects/cvss.py | 4 +- .../commands/entities/osidb/flaws/__init__.py | 4 +- .../entities/osidb/flaws/acknowledgments.py | 4 +- .../commands/entities/osidb/flaws/comments.py | 4 +- griffon/commands/entities/osidb/flaws/cvss.py | 4 +- .../entities/osidb/flaws/package_versions.py | 4 +- .../entities/osidb/flaws/references.py | 4 +- griffon/commands/entities/osidb/manage.py | 4 +- griffon/commands/entities/osidb/trackers.py | 4 +- griffon/services/core_queries.py | 32 +++++++------- griffon/services/core_reports.py | 10 ++--- tox.ini | 4 +- 22 files changed, 129 insertions(+), 108 deletions(-) diff --git a/Containerfile b/Containerfile index 3de8baf..0a0a308 100644 --- a/Containerfile +++ b/Containerfile @@ -9,15 +9,15 @@ LABEL maintainer="Red Hat Product Security Dev - Red Hat, Inc." \ ARG PIP_INDEX_URL ARG ROOT_CA_URL ARG REQUESTS_CA_BUNDLE -ARG CORGI_API_URL -ARG OSIDB_API_URL +ARG CORGI_SERVER_URL +ARG OSIDB_SERVER_URL ENV PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=off \ PIP_INDEX_URL="${PIP_INDEX_URL}" \ REQUESTS_CA_BUNDLE="${REQUESTS_CA_BUNDLE}" \ ROOT_CA_URL="${ROOT_CA_URL}" \ - CORGI_API_URL="${CORGI_API_URL}" \ - OSIDB_API_URL="${OSIDB_API_URL}" + CORGI_SERVER_URL="${CORGI_SERVER_URL}" \ + OSIDB_SERVER_URL="${OSIDB_SERVER_URL}" RUN cd /etc/pki/ca-trust/source/anchors/ && \ # The '| true' skips this step if the ROOT_CA_URL is unset or fails in another way diff --git a/Containerfile-dev b/Containerfile-dev index e14ede3..6da6bad 100644 --- a/Containerfile-dev +++ b/Containerfile-dev @@ -9,15 +9,15 @@ LABEL maintainer="Red Hat Product Security Dev - Red Hat, Inc." \ ARG PIP_INDEX_URL ARG ROOT_CA_URL ARG REQUESTS_CA_BUNDLE -ARG CORGI_API_URL -ARG OSIDB_API_URL +ARG CORGI_SERVER_URL +ARG OSIDB_SERVER_URL ENV PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=off \ PIP_INDEX_URL="${PIP_INDEX_URL}" \ REQUESTS_CA_BUNDLE="${REQUESTS_CA_BUNDLE}" \ ROOT_CA_URL="${ROOT_CA_URL}" \ - CORGI_API_URL="${CORGI_API_URL}" \ - OSIDB_API_URL="${OSIDB_API_URL}" + CORGI_SERVER_URL="${CORGI_SERVER_URL}" \ + OSIDB_SERVER_URL="${OSIDB_SERVER_URL}" RUN cd /etc/pki/ca-trust/source/anchors/ && \ # The '| true' skips this step if the ROOT_CA_URL is unset or fails in another way diff --git a/Makefile b/Makefile index c67cb60..f436b12 100644 --- a/Makefile +++ b/Makefile @@ -13,8 +13,8 @@ openssl=`which openssl` # container targets ############################################################################ build-container: Containerfile - $(podman) build --build-arg CORGI_API_URL="${CORGI_API_URL}" \ - --build-arg OSIDB_API_URL="${OSIDB_API_URL}" \ + $(podman) build --build-arg CORGI_SERVER_URL="${CORGI_SERVER_URL}" \ + --build-arg OSIDB_SERVER_URL="${OSIDB_SERVER_URL}" \ --build-arg REQUESTS_CA_BUNDLE="${REQUESTS_CA_BUNDLE}" \ --build-arg PIP_INDEX_URL="${PIP_INDEX_URL}" \ --build-arg ROOT_CA_URL="${ROOT_CA_URL}" \ diff --git a/docs/user_guide.md b/docs/user_guide.md index b3779e8..96c9f55 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -61,8 +61,8 @@ export REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt Set service urls. ```commandline -export OSIDB_API_URL="https://" -export CORGI_API_URL="https://" +export OSIDB_SERVER_URL="https://" +export CORGI_SERVER_URL="https://" ``` and the following is set to enable searching community components: @@ -113,8 +113,8 @@ The container is unsupported. First set some env vars ```commandline -export CORGI_API_URL= -export OSIDB_API_URL= +export CORGI_SERVER_URL= +export OSIDB_SERVER_URL= export REQUESTS_CA_BUNDLE= export PIP_INDEX_URL= export ROOT_CA_URL= diff --git a/griffon/__init__.py b/griffon/__init__.py index 0c8ecc3..c42d362 100644 --- a/griffon/__init__.py +++ b/griffon/__init__.py @@ -14,19 +14,40 @@ from pkg_resources import resource_filename # type: ignore from rich.logging import RichHandler +from griffon.helpers import Color, Style from griffon.output import console __version__ = "0.3.8" -if "CORGI_API_URL" not in os.environ: - print("Must set CORGI_API_URL environment variable.") - exit(1) -CORGI_API_URL = os.environ["CORGI_API_URL"] +# TODO: Deprecate CORGI_API_URL completely in the next version or two +if "CORGI_API_URL" in os.environ: + print( + ( + f"{Style.BOLD}{Color.YELLOW}WARNING: CORGI_API_URL will be deprecated " + "in the next version of Griffon in favour of CORGI_SERVER_URL, please " + f"switch to the new environment variable.{Style.RESET}" + ) + ) -if "OSIDB_API_URL" not in os.environ: - print("Must set OSIDB_API_URL environment variable.") +if "CORGI_SERVER_URL" not in os.environ and "CORGI_API_URL" not in os.environ: + print("Must set CORGI_SERVER_URL environment variable.") exit(1) -OSIDB_API_URL = os.environ["OSIDB_API_URL"] +CORGI_SERVER_URL = os.getenv("CORGI_SERVER_URL", os.getenv("CORGI_API_URL")) + +# TODO: Deprecate CORGI_API_URL completely in the next version or two +if "OSIDB_API_URL" in os.environ: + print( + ( + f"{Style.BOLD}{Color.YELLOW}WARNING: OSIDB_API_URL will be deprecated " + "in the next version of Griffon in favour of OSIDB_SERVER_URL, please " + f"switch to the new environment variable.{Style.RESET}" + ) + ) +if "OSIDB_SERVER_URL" not in os.environ and "OSIDB_API_URL" not in os.environ: + print("Must set OSIDB_SERVER_URL environment variable.") + exit(1) +OSIDB_SERVER_URL = os.getenv("OSIDB_SERVER_URL", os.getenv("OSIDB_API_URL")) + OSIDB_USERNAME = os.getenv("OSIDB_USERNAME", "") OSIDB_PASSWORD = os.getenv("OSIDB_PASSWORD", "") OSIDB_AUTH_METHOD = os.getenv("OSIDB_AUTH_METHOD", "kerberos") @@ -99,10 +120,10 @@ def create_session(): """init corgi session""" try: return component_registry_bindings.new_session( - component_registry_server_uri=CORGI_API_URL, + component_registry_server_uri=CORGI_SERVER_URL, ) except: # noqa - console.log(f"{CORGI_API_URL} is not accessible.") + console.log(f"{CORGI_SERVER_URL} is not accessible.") exit(1) @staticmethod @@ -166,9 +187,9 @@ def create_session(): if OSIDB_AUTH_METHOD == "credentials": credentials["username"] = OSIDB_USERNAME credentials["password"] = OSIDB_PASSWORD - return osidb_bindings.new_session(osidb_server_uri=OSIDB_API_URL, **credentials) + return osidb_bindings.new_session(osidb_server_uri=OSIDB_SERVER_URL, **credentials) except: # noqa - console.log(f"{OSIDB_API_URL} is not accessible (or krb ticket has expired).") + console.log(f"{OSIDB_SERVER_URL} is not accessible (or krb ticket has expired).") exit(1) @staticmethod diff --git a/griffon/autocomplete/__init__.py b/griffon/autocomplete/__init__.py index 91b10a1..08a67bb 100644 --- a/griffon/autocomplete/__init__.py +++ b/griffon/autocomplete/__init__.py @@ -2,7 +2,7 @@ import requests -from griffon import CORGI_API_URL, OSIDB_API_URL +from griffon import CORGI_SERVER_URL, OSIDB_SERVER_URL logger = logging.getLogger("griffon") @@ -10,7 +10,7 @@ def get_product_version_ofuris(ctx, param, incomplete): payload = {"limit": 100, "include_fields": "ofuri", "re_ofuri": incomplete} response = requests.get( - f"{CORGI_API_URL}/api/v1/product_versions", + f"{CORGI_SERVER_URL}/api/v1/product_versions", params=payload, headers={"Accept-Encoding": "gzip;q=1.0, identity; q=0.5, *;q=0"}, ) @@ -21,7 +21,7 @@ def get_product_version_ofuris(ctx, param, incomplete): def get_product_version_names(ctx, param, incomplete): payload = {"limit": 100, "include_fields": "name", "re_name": incomplete} response = requests.get( - f"{CORGI_API_URL}/api/v1/product_versions", + f"{CORGI_SERVER_URL}/api/v1/product_versions", params=payload, headers={"Accept-Encoding": "gzip;q=1.0, identity; q=0.5, *;q=0"}, ) @@ -32,7 +32,7 @@ def get_product_version_names(ctx, param, incomplete): def get_product_stream_ofuris(ctx, param, incomplete): payload = {"limit": 100, "include_fields": "ofuri", "re_ofuri": incomplete} response = requests.get( - f"{CORGI_API_URL}/api/v1/product_streams", + f"{CORGI_SERVER_URL}/api/v1/product_streams", params=payload, headers={"Accept-Encoding": "gzip;q=1.0, identity; q=0.5, *;q=0"}, ) @@ -43,7 +43,7 @@ def get_product_stream_ofuris(ctx, param, incomplete): def get_product_stream_names(ctx, param, incomplete): payload = {"limit": 100, "include_fields": "name", "re_name": incomplete} response = requests.get( - f"{CORGI_API_URL}/api/v1/product_streams", + f"{CORGI_SERVER_URL}/api/v1/product_streams", params=payload, headers={"Accept-Encoding": "gzip;q=1.0, identity; q=0.5, *;q=0"}, ) @@ -54,7 +54,7 @@ def get_product_stream_names(ctx, param, incomplete): def get_component_names(ctx, param, incomplete): payload = {"limit": 100, "include_fields": "name", "re_name": incomplete} response = requests.get( - f"{CORGI_API_URL}/api/v1/components", + f"{CORGI_SERVER_URL}/api/v1/components", params=payload, headers={"Accept-Encoding": "gzip;q=1.0, identity; q=0.5, *;q=0"}, ) @@ -65,7 +65,7 @@ def get_component_names(ctx, param, incomplete): def get_component_purls(ctx, param, incomplete): payload = {"limit": 100, "include_fields": "purl", "re_purl": incomplete} response = requests.get( - f"{CORGI_API_URL}/api/v1/components", + f"{CORGI_SERVER_URL}/api/v1/components", params=payload, headers={"Accept-Encoding": "gzip;q=1.0, identity; q=0.5, *;q=0"}, ) @@ -76,6 +76,6 @@ def get_component_purls(ctx, param, incomplete): def get_cve_ids(ctx, param, incomplete): """TODO - the following is not ideal for autocomplete lookup - need to investigate""" response = requests.get( - f"{OSIDB_API_URL}/osidb/api/v1/flaws?limit=10&re_cve_id={incomplete}&include_fields=cve_id" # noqa + f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws?limit=10&re_cve_id={incomplete}&include_fields=cve_id" # noqa ) return [k["cve_id"] for k in response.json()["results"] if k["cve_id"].startswith(incomplete)] diff --git a/griffon/commands/docs.py b/griffon/commands/docs.py index 5374b53..d51da89 100644 --- a/griffon/commands/docs.py +++ b/griffon/commands/docs.py @@ -4,7 +4,7 @@ import click -from griffon import CORGI_API_URL, OSIDB_API_URL +from griffon import CORGI_SERVER_URL, OSIDB_SERVER_URL @click.group(name="docs", help="Links to useful docs.") @@ -36,7 +36,7 @@ def griffon_tutorial(): @service_grp.command() def osidb(): - click.launch(OSIDB_API_URL) + click.launch(OSIDB_SERVER_URL) @service_grp.command() @@ -56,7 +56,7 @@ def osidb_bindings(): @service_grp.command() def corgi(): - click.launch(CORGI_API_URL) + click.launch(CORGI_SERVER_URL) @service_grp.command() diff --git a/griffon/commands/entities/community_component_registry.py b/griffon/commands/entities/community_component_registry.py index 653b4a7..9a4ac9f 100644 --- a/griffon/commands/entities/community_component_registry.py +++ b/griffon/commands/entities/community_component_registry.py @@ -34,7 +34,7 @@ from griffon import ( COMMUNITY_COMPONENTS_API_URL, - CORGI_API_URL, + CORGI_SERVER_URL, CommunityComponentService, progress_bar, ) @@ -203,7 +203,7 @@ def get_component_summary(ctx, component_name, strict_name_search, **params): for ps in component.product_streams: product_streams.append(ps["name"]) data = { - "link": f"{CORGI_API_URL}/api/v1/components?name={component_name}", + "link": f"{CORGI_SERVER_URL}/api/v1/components?name={component_name}", "type": component_type, "name": component_name, "tags": sorted(list(set(tags))), @@ -330,7 +330,7 @@ def get_component_manifest(ctx, component_uuid, purl, spdx_json_format): # PRODUCT STREAM -@commmunity_components_grp.group(help=f"{CORGI_API_URL}/api/v1/product_streams") +@commmunity_components_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/product_streams") @click.pass_context def product_streams(ctx): pass @@ -481,7 +481,7 @@ def get_product_stream_manifest(ctx, product_stream_name, ofuri, spdx_json_forma # BUILDS -@commmunity_components_grp.group(help=f"{CORGI_API_URL}/api/v1/builds") +@commmunity_components_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/builds") @click.pass_context def builds(ctx): pass @@ -543,7 +543,7 @@ def get_software_build(ctx, software_build_name, **params): # Products -@commmunity_components_grp.group(help=f"{CORGI_API_URL}/api/v1/products") +@commmunity_components_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/products") @click.pass_context def products(ctx): pass @@ -603,7 +603,7 @@ def get_product(ctx, product_name, ofuri, **params): # PRODUCT VERSION -@commmunity_components_grp.group(help=f"{CORGI_API_URL}/api/v1/product-versions") +@commmunity_components_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/product-versions") @click.pass_context def product_versions(ctx): pass @@ -668,7 +668,7 @@ def get_product_version(ctx, product_version_name, ofuri, **params): # PRODUCT VARIANT -@commmunity_components_grp.group(help=f"{CORGI_API_URL}/api/v1/product-variants") +@commmunity_components_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/product-variants") @click.pass_context def product_variants(ctx): pass @@ -733,7 +733,7 @@ def get_product_variant(ctx, product_variant_name, ofuri, **params): # CHANNEL -@commmunity_components_grp.group(help=f"{CORGI_API_URL}/api/v1/channels") +@commmunity_components_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/channels") @click.pass_context def channels(ctx): pass @@ -815,20 +815,20 @@ def corgi_health(ctx): session = CommunityComponentService.create_session() status = session.status()["status"] if status == "ok": - console.log(f"{CORGI_API_URL} is operational") + console.log(f"{CORGI_SERVER_URL} is operational") else: - console.log(f"{CORGI_API_URL} is NOT operational") + console.log(f"{CORGI_SERVER_URL} is NOT operational") exit(1) except: # noqa - console.log(f"{CORGI_API_URL} is NOT operational") + console.log(f"{CORGI_SERVER_URL} is NOT operational") raise click.ClickException("Component registry health check failed.") @manage_grp.command(name="data") def corgi_data(): - click.launch(f"{CORGI_API_URL}/data") + click.launch(f"{CORGI_SERVER_URL}/data") @manage_grp.command(name="api_doc") def corgi_api_docs(): - click.launch(f"{CORGI_API_URL}/api/v1/schema/docs") + click.launch(f"{CORGI_SERVER_URL}/api/v1/schema/docs") diff --git a/griffon/commands/entities/corgi.py b/griffon/commands/entities/corgi.py index 2f164df..4b23730 100644 --- a/griffon/commands/entities/corgi.py +++ b/griffon/commands/entities/corgi.py @@ -32,7 +32,7 @@ SoftwareBuild, ) -from griffon import CORGI_API_URL, CorgiService, progress_bar +from griffon import CORGI_SERVER_URL, CorgiService, progress_bar from griffon.autocomplete import ( get_component_purls, get_product_stream_names, @@ -59,7 +59,7 @@ def corgi_grp(ctx): # COMPONENTS -@corgi_grp.group(help=f"{CORGI_API_URL}/api/v1/components") +@corgi_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/components") @click.pass_context def components(ctx): """Corgi Components.""" @@ -198,7 +198,7 @@ def get_component_summary(ctx, component_name, strict_name_search, **params): for ps in component.product_streams: product_streams.append(ps["name"]) data = { - "link": f"{CORGI_API_URL}/api/v1/components?name={component_name}", + "link": f"{CORGI_SERVER_URL}/api/v1/components?name={component_name}", "type": component_type, "name": component_name, "tags": sorted(list(set(tags))), @@ -352,14 +352,14 @@ def get_component_tree(ctx, component_uuid, nvr, purl, show_purl): component_uuid = c.results[0].uuid c = session.components.retrieve(component_uuid, include_fields="uuid,nvr,arch") if c.arch == "src" or c.arch == "noarch": - data = requests.get(f"{CORGI_API_URL}/api/v1/components/{component_uuid}/taxonomy") + data = requests.get(f"{CORGI_SERVER_URL}/api/v1/components/{component_uuid}/taxonomy") return cprint(data.json(), ctx=ctx) else: logger.info(f"{c.nvr},{c.arch} not a root component.") # PRODUCT STREAM -@corgi_grp.group(help=f"{CORGI_API_URL}/api/v1/product_streams") +@corgi_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/product_streams") @click.pass_context def product_streams(ctx): pass @@ -512,7 +512,7 @@ def get_product_stream_manifest(ctx, product_stream_name, ofuri, spdx_json_forma # BUILDS -@corgi_grp.group(help=f"{CORGI_API_URL}/api/v1/builds") +@corgi_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/builds") @click.pass_context def builds(ctx): pass @@ -572,7 +572,7 @@ def get_software_build(ctx, software_build_name, **params): # Products -@corgi_grp.group(help=f"{CORGI_API_URL}/api/v1/products") +@corgi_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/products") @click.pass_context def products(ctx): pass @@ -633,7 +633,7 @@ def get_product(ctx, product_name, ofuri, **params): # PRODUCT VERSION -@corgi_grp.group(help=f"{CORGI_API_URL}/api/v1/product-versions") +@corgi_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/product-versions") @click.pass_context def product_versions(ctx): pass @@ -695,7 +695,7 @@ def get_product_version(ctx, product_version_name, ofuri, **params): # PRODUCT VARIANT -@corgi_grp.group(help=f"{CORGI_API_URL}/api/v1/product-variants") +@corgi_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/product-variants") @click.pass_context def product_variants(ctx): pass @@ -757,7 +757,7 @@ def get_product_variant(ctx, product_variant_name, ofuri, **params): # CHANNEL -@corgi_grp.group(help=f"{CORGI_API_URL}/api/v1/channels") +@corgi_grp.group(help=f"{CORGI_SERVER_URL}/api/v1/channels") @click.pass_context def channels(ctx): pass @@ -841,20 +841,20 @@ def corgi_health(ctx): session = CorgiService.create_session() status = session.status()["status"] if status == "ok": - console.log(f"{CORGI_API_URL} is operational") + console.log(f"{CORGI_SERVER_URL} is operational") else: - console.log(f"{CORGI_API_URL} is NOT operational") + console.log(f"{CORGI_SERVER_URL} is NOT operational") exit(1) except: # noqa - console.log(f"{CORGI_API_URL} is NOT operational") + console.log(f"{CORGI_SERVER_URL} is NOT operational") raise click.ClickException("Component registry health check failed.") @manage_grp.command(name="data") def corgi_data(): - click.launch(f"{CORGI_API_URL}/data") + click.launch(f"{CORGI_SERVER_URL}/data") @manage_grp.command(name="api_doc") def corgi_api_docs(): - click.launch(f"{CORGI_API_URL}/api/v1/schema/docs") + click.launch(f"{CORGI_SERVER_URL}/api/v1/schema/docs") diff --git a/griffon/commands/entities/osidb/affects/__init__.py b/griffon/commands/entities/osidb/affects/__init__.py index 1793c33..5c33d8a 100644 --- a/griffon/commands/entities/osidb/affects/__init__.py +++ b/griffon/commands/entities/osidb/affects/__init__.py @@ -15,7 +15,7 @@ from osidb_bindings.bindings.python_client.models import Affect from requests import HTTPError -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( abort_if_false, filter_request_fields, @@ -29,7 +29,7 @@ logger = logging.getLogger("griffon") -@click.group(help=f"{OSIDB_API_URL}/osidb/api/v1/affects") +@click.group(help=f"{OSIDB_SERVER_URL}/osidb/api/v1/affects") @click.pass_context def affects(ctx): """OSIDB Affects.""" @@ -75,7 +75,7 @@ def list_affects(ctx, **params): def get_affect(ctx, affect_uuid, **params): """ For parameter reference see: - /osidb/api/v1/schema/swagger-ui - /osidb/api/v1/affects/{uuid} + /osidb/api/v1/schema/swagger-ui - /osidb/api/v1/affects/{uuid} """ params = multivalue_params_to_csv(params) diff --git a/griffon/commands/entities/osidb/affects/cvss.py b/griffon/commands/entities/osidb/affects/cvss.py index ded17cf..ab71b19 100644 --- a/griffon/commands/entities/osidb/affects/cvss.py +++ b/griffon/commands/entities/osidb/affects/cvss.py @@ -15,7 +15,7 @@ from osidb_bindings.bindings.python_client.models import AffectCVSS from requests import HTTPError -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( abort_if_false, filter_request_fields, @@ -29,7 +29,7 @@ logger = logging.getLogger("griffon") -@click.group(help=f"{OSIDB_API_URL}/osidb/api/v1/affects//cvss_scores", name="cvss") +@click.group(help=f"{OSIDB_SERVER_URL}/osidb/api/v1/affects//cvss_scores", name="cvss") @click.pass_context def affect_cvss(ctx): """OSIDB Affect CVSS.""" diff --git a/griffon/commands/entities/osidb/flaws/__init__.py b/griffon/commands/entities/osidb/flaws/__init__.py index fc87da4..fd4ffab 100644 --- a/griffon/commands/entities/osidb/flaws/__init__.py +++ b/griffon/commands/entities/osidb/flaws/__init__.py @@ -15,7 +15,7 @@ from osidb_bindings.bindings.python_client.models import Flaw from requests import HTTPError -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( filter_request_fields, get_editor, @@ -34,7 +34,7 @@ logger = logging.getLogger("griffon") -@click.group(help=f"{OSIDB_API_URL}/osidb/api/v1/flaws") +@click.group(help=f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws") @click.pass_context def flaws(ctx): """OSIDB Flaws.""" diff --git a/griffon/commands/entities/osidb/flaws/acknowledgments.py b/griffon/commands/entities/osidb/flaws/acknowledgments.py index 9192d20..48db29e 100644 --- a/griffon/commands/entities/osidb/flaws/acknowledgments.py +++ b/griffon/commands/entities/osidb/flaws/acknowledgments.py @@ -15,7 +15,7 @@ from osidb_bindings.bindings.python_client.models import FlawAcknowledgment from requests import HTTPError -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( abort_if_false, filter_request_fields, @@ -32,7 +32,7 @@ @click.group( - help=f"{OSIDB_API_URL}/osidb/api/v1/flaws//acknowledgments", name="acknowledgments" + help=f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws//acknowledgments", name="acknowledgments" ) @click.pass_context def flaw_acknowledgments(ctx): diff --git a/griffon/commands/entities/osidb/flaws/comments.py b/griffon/commands/entities/osidb/flaws/comments.py index 3e3bcec..613ddd9 100644 --- a/griffon/commands/entities/osidb/flaws/comments.py +++ b/griffon/commands/entities/osidb/flaws/comments.py @@ -14,7 +14,7 @@ from osidb_bindings.bindings.python_client.models import FlawComment from requests import HTTPError -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( filter_request_fields, get_editor, @@ -27,7 +27,7 @@ logger = logging.getLogger("griffon") -@click.group(help=f"{OSIDB_API_URL}/osidb/api/v1/flaws//comments", name="comments") +@click.group(help=f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws//comments", name="comments") @click.pass_context def flaw_comments(ctx): """OSIDB Flaw Comments.""" diff --git a/griffon/commands/entities/osidb/flaws/cvss.py b/griffon/commands/entities/osidb/flaws/cvss.py index 1bf5c5f..a9f8cd5 100644 --- a/griffon/commands/entities/osidb/flaws/cvss.py +++ b/griffon/commands/entities/osidb/flaws/cvss.py @@ -15,7 +15,7 @@ from osidb_bindings.bindings.python_client.models import FlawCVSS from requests import HTTPError -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( abort_if_false, filter_request_fields, @@ -29,7 +29,7 @@ logger = logging.getLogger("griffon") -@click.group(help=f"{OSIDB_API_URL}/osidb/api/v1/flaws//cvss_scores", name="cvss") +@click.group(help=f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws//cvss_scores", name="cvss") @click.pass_context def flaw_cvss(ctx): """OSIDB Flaw CVSS.""" diff --git a/griffon/commands/entities/osidb/flaws/package_versions.py b/griffon/commands/entities/osidb/flaws/package_versions.py index e6b4754..26ee455 100644 --- a/griffon/commands/entities/osidb/flaws/package_versions.py +++ b/griffon/commands/entities/osidb/flaws/package_versions.py @@ -15,7 +15,7 @@ from osidb_bindings.bindings.python_client.models import FlawPackageVersion from requests import HTTPError -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( abort_if_false, filter_request_fields, @@ -30,7 +30,7 @@ @click.group( - help=f"{OSIDB_API_URL}/osidb/api/v1/flaws//package_versions", name="package_versions" + help=f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws//package_versions", name="package_versions" ) @click.pass_context def flaw_package_versions(ctx): diff --git a/griffon/commands/entities/osidb/flaws/references.py b/griffon/commands/entities/osidb/flaws/references.py index 86766a2..6aad49a 100644 --- a/griffon/commands/entities/osidb/flaws/references.py +++ b/griffon/commands/entities/osidb/flaws/references.py @@ -15,7 +15,7 @@ from osidb_bindings.bindings.python_client.models import FlawReference from requests import HTTPError -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( abort_if_false, filter_request_fields, @@ -29,7 +29,7 @@ logger = logging.getLogger("griffon") -@click.group(help=f"{OSIDB_API_URL}/osidb/api/v1/flaws//references", name="references") +@click.group(help=f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws//references", name="references") @click.pass_context def flaw_references(ctx): """OSIDB Flaw References.""" diff --git a/griffon/commands/entities/osidb/manage.py b/griffon/commands/entities/osidb/manage.py index 2ebbe38..1139ed0 100644 --- a/griffon/commands/entities/osidb/manage.py +++ b/griffon/commands/entities/osidb/manage.py @@ -6,7 +6,7 @@ import click -from griffon import OSIDB_API_URL, OSIDBService +from griffon import OSIDB_SERVER_URL, OSIDBService from griffon.output import cprint logger = logging.getLogger("griffon") @@ -31,4 +31,4 @@ def osidb_status(ctx): @manage_grp.command(name="api_doc") def osidb_api_docs(): - click.launch(f"{OSIDB_API_URL}/osidb/api/v1/schema/swagger-ui/") + click.launch(f"{OSIDB_SERVER_URL}/osidb/api/v1/schema/swagger-ui/") diff --git a/griffon/commands/entities/osidb/trackers.py b/griffon/commands/entities/osidb/trackers.py index d3ec4fe..a5f2e5c 100644 --- a/griffon/commands/entities/osidb/trackers.py +++ b/griffon/commands/entities/osidb/trackers.py @@ -11,7 +11,7 @@ ) from osidb_bindings.bindings.python_client.models import Tracker -from griffon import OSIDB_API_URL, OSIDBService, progress_bar +from griffon import OSIDB_SERVER_URL, OSIDBService, progress_bar from griffon.commands.entities.helpers import ( multivalue_params_to_csv, query_params_options, @@ -21,7 +21,7 @@ logger = logging.getLogger("griffon") -@click.group(help=f"{OSIDB_API_URL}/osidb/api/v1/trackers") +@click.group(help=f"{OSIDB_SERVER_URL}/osidb/api/v1/trackers") @click.pass_context def trackers(ctx): """OSIDB Trackers.""" diff --git a/griffon/services/core_queries.py b/griffon/services/core_queries.py index c1b1646..9a56ab6 100644 --- a/griffon/services/core_queries.py +++ b/griffon/services/core_queries.py @@ -13,8 +13,8 @@ from griffon import ( COMMUNITY_COMPONENTS_API_URL, - CORGI_API_URL, - OSIDB_API_URL, + CORGI_SERVER_URL, + OSIDB_SERVER_URL, CommunityComponentService, CorgiService, OSIDBService, @@ -69,8 +69,8 @@ def execute(self, status=None) -> List[Dict[str, Any]]: "brew_tags": [brew_tag for brew_tag in ps.brew_tags.to_dict().keys()], # "build_count": ps.build_count, "manifest_link": ps.manifest, - "latest_components_link": f"{CORGI_API_URL}/api/v1/components?ofuri={ps.ofuri}&view=summary", # noqa - "all_components_link": f"{CORGI_API_URL}/api/v1/components?product_streams={ps.ofuri}&include_fields=link,name,purl", # noqa + "latest_components_link": f"{CORGI_SERVER_URL}/api/v1/components?ofuri={ps.ofuri}&view=summary", # noqa + "all_components_link": f"{CORGI_SERVER_URL}/api/v1/components?product_streams={ps.ofuri}&include_fields=link,name,purl", # noqa } results.append(result) else: @@ -83,8 +83,8 @@ def execute(self, status=None) -> List[Dict[str, Any]]: "product_version": product_streams["product_versions"][0]["name"], "brew_tags": "", "manifest_link": product_streams["manifest"], - "latest_components_link": f"{CORGI_API_URL}/api/v1/components?ofuri={product_streams['ofuri']}&view=summary", # noqa - "all_components_link": f"{CORGI_API_URL}/api/v1/components?product_streams={product_streams['ofuri']}&include_fields=link,name,purl", # noqa + "latest_components_link": f"{CORGI_SERVER_URL}/api/v1/components?ofuri={product_streams['ofuri']}&view=summary", # noqa + "all_components_link": f"{CORGI_SERVER_URL}/api/v1/components?product_streams={product_streams['ofuri']}&include_fields=link,name,purl", # noqa } results.append(result) else: @@ -144,7 +144,7 @@ def execute(self, status=None) -> dict: for pv in c["product_versions"]: product_versions.add(ps["name"]) return { - "link": f"{OSIDB_API_URL}/osidb/api/v1/flaws/{flaw.cve_id}", + "link": f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws/{flaw.cve_id}", "cve_id": flaw.cve_id, "title": flaw.title, "description": flaw.description, @@ -873,7 +873,7 @@ def execute(self, status=None) -> dict: results.append(c.to_dict()) return { - "link": f"{OSIDB_API_URL}/osidb/api/v1/flaws/{flaw.cve_id}", + "link": f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws/{flaw.cve_id}", "cve_id": flaw.cve_id, "title": flaw.title, "description": flaw.description, @@ -948,9 +948,9 @@ def execute(self, status=None) -> List[Dict[str, Any]]: continue affects.append( { - "link_affect": f"{OSIDB_API_URL}/osidb/api/v1/affects/{affect.uuid}", # noqa - "link_cve": f"{OSIDB_API_URL}/osidb/api/v1/flaws/{flaw.cve_id}", # noqa - "link_component": f"{CORGI_API_URL}/api/v1/components?name={affect.ps_component}&latest_components_by_streams=True", # noqa + "link_affect": f"{OSIDB_SERVER_URL}/osidb/api/v1/affects/{affect.uuid}", # noqa + "link_cve": f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws/{flaw.cve_id}", # noqa + "link_component": f"{CORGI_SERVER_URL}/api/v1/components?name={affect.ps_component}&latest_components_by_streams=True", # noqa "link_community_component": f"{COMMUNITY_COMPONENTS_API_URL}/api/v1/components?name={affect.ps_component}&latest_components_by_streams=True", # noqa "flaw_cve_id": flaw.cve_id, "title": flaw.title, @@ -964,7 +964,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) components.append( { - "link": f"{CORGI_API_URL}/api/v1/components?name={self.component_name}", + "link": f"{CORGI_SERVER_URL}/api/v1/components?name={self.component_name}", "name": self.component_name, "affects": affects, } @@ -1037,9 +1037,9 @@ def execute(self, status=None) -> List[Dict[str, Any]]: continue affects.append( { - "link_affect": f"{OSIDB_API_URL}/osidb/api/v1/affects/{affect.uuid}", # noqa - "link_cve": f"{OSIDB_API_URL}/osidb/api/v1/flaws/{flaw.cve_id}", # noqa - "link_component": f"{CORGI_API_URL}/api/v1/components?name={affect.ps_component}&latest_components_by_streams=True", # noqa + "link_affect": f"{OSIDB_SERVER_URL}/osidb/api/v1/affects/{affect.uuid}", # noqa + "link_cve": f"{OSIDB_SERVER_URL}/osidb/api/v1/flaws/{flaw.cve_id}", # noqa + "link_component": f"{CORGI_SERVER_URL}/api/v1/components?name={affect.ps_component}&latest_components_by_streams=True", # noqa "link_community_component": f"{COMMUNITY_COMPONENTS_API_URL}/api/v1/components?name={affect.ps_component}&latest_components_by_streams=True", # noqa "flaw_cve_id": flaw.cve_id, "title": flaw.title, @@ -1054,7 +1054,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) components.append( { - "link": f"{CORGI_API_URL}/api/v1/product_versions?name={self.product_version_name}", # noqa + "link": f"{CORGI_SERVER_URL}/api/v1/product_versions?name={self.product_version_name}", # noqa "name": self.product_version_name, "affects": affects, } diff --git a/griffon/services/core_reports.py b/griffon/services/core_reports.py index 1bd87df..af4556b 100644 --- a/griffon/services/core_reports.py +++ b/griffon/services/core_reports.py @@ -7,7 +7,7 @@ import requests -from griffon import OSIDB_API_URL, CorgiService, OSIDBService +from griffon import OSIDB_SERVER_URL, CorgiService, OSIDBService logger = logging.getLogger("griffon") @@ -48,7 +48,7 @@ def generate(self) -> dict: # TODO osidb_session.affects does not support include_fields, do it manually for now affect_critical_res = requests.get( - f"{OSIDB_API_URL}/osidb/api/v1/affects?affectedness=AFFECTED&impact=CRITICAL&include_fields=ps_component,ps_module,impact&limit=10000" # noqa + f"{OSIDB_SERVER_URL}/osidb/api/v1/affects?affectedness=AFFECTED&impact=CRITICAL&include_fields=ps_component,ps_module,impact&limit=10000" # noqa ) affects_critical = affect_critical_res.json() critical_components = {} @@ -68,7 +68,7 @@ def generate(self) -> dict: top_critical_product = max(critical_products, key=critical_products.get) # type: ignore affect_important_res = requests.get( - f"{OSIDB_API_URL}/osidb/api/v1/affects?affectedness=AFFECTED&impact=IMPORTANT&include_fields=ps_component,ps_module,impact&limit=10000" # noqa + f"{OSIDB_SERVER_URL}/osidb/api/v1/affects?affectedness=AFFECTED&impact=IMPORTANT&include_fields=ps_component,ps_module,impact&limit=10000" # noqa ) affects_important = affect_important_res.json() important_components = {} @@ -87,7 +87,7 @@ def generate(self) -> dict: top_important_product = max(important_products, key=important_products.get) # type: ignore # noqa affect_moderate_res = requests.get( - f"{OSIDB_API_URL}/osidb/api/v1/affects?affectedness=AFFECTED&impact=MODERATE&include_fields=ps_component,ps_module,impact&limit=10000" # noqa + f"{OSIDB_SERVER_URL}/osidb/api/v1/affects?affectedness=AFFECTED&impact=MODERATE&include_fields=ps_component,ps_module,impact&limit=10000" # noqa ) affects_moderate = affect_moderate_res.json() moderate_components = {} @@ -106,7 +106,7 @@ def generate(self) -> dict: top_moderate_product = max(moderate_products, key=moderate_products.get) # type: ignore affect_low_res = requests.get( - f"{OSIDB_API_URL}/osidb/api/v1/affects?affectedness=AFFECTED&impact=LOW&include_fields=ps_component,ps_module,impact&limit=10000" # noqa + f"{OSIDB_SERVER_URL}/osidb/api/v1/affects?affectedness=AFFECTED&impact=LOW&include_fields=ps_component,ps_module,impact&limit=10000" # noqa ) affects_low = affect_low_res.json() low_components = {} diff --git a/tox.ini b/tox.ini index f55e749..662ad58 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,8 @@ skipsdist = false [testenv] basepython = python3.9 passenv = - CORGI_API_URL - OSIDB_API_URL + CORGI_SERVER_URL + OSIDB_SERVER_URL REQUESTS_CA_BUNDLE [testenv:griffon] From 9b304e798cc74866a7b858c90fc42525bf8d94dd Mon Sep 17 00:00:00 2001 From: Jakub Frejlach Date: Mon, 13 Nov 2023 16:34:40 +0100 Subject: [PATCH 3/8] Implement Color/Style helper This helper is supposed to format text color/style in cases where rich cannot be used (exceptions for example) --- griffon/commands/queries.py | 21 +++++++++++------ griffon/helpers.py | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/griffon/commands/queries.py b/griffon/commands/queries.py index 78f2710..9197b08 100644 --- a/griffon/commands/queries.py +++ b/griffon/commands/queries.py @@ -40,6 +40,7 @@ generate_entity_report, generate_license_report, ) +from griffon.helpers import Style from griffon.output import ( console, cprint, @@ -188,12 +189,12 @@ def retrieve_component_summary(ctx, component_name, strict_name_search): # @click.option( # "--cve-id", # "cve_id", -# help="Attach affects to this (\033[1mCVE-ID\033[0m).", +# help=f"Attach affects to this {Style.BOLD}CVE-ID{Style.RESET}.", # ) @click.option( "--sfm2-flaw-id", "sfm2_flaw_id", - help="Attach affects to this (\033[1msfm2 flaw id\033[0m).", + help=f"Attach affects to this {Style.BOLD}sfm2 flaw id{Style.RESET}.", ) @click.option( "--flaw-mode", @@ -207,28 +208,34 @@ def retrieve_component_summary(ctx, component_name, strict_name_search): "search_latest", is_flag=True, default=False, - help="Search root Components (\033[1menabled by default\033[0m).", + help=f"Search root Components {Style.BOLD}(enabled by default){Style.RESET}.", ) @click.option( "--search-provides", "search_provides", is_flag=True, default=False, - help="Search root Components by provides children(\033[1menabled by default\033[0m).", + help=( + f"Search root Components by provides children " + f"{Style.BOLD}(enabled by default){Style.RESET}." + ), ) @click.option( "--search-upstreams", "search_upstreams", is_flag=True, default=False, - help="Search root Components by upstreams children (\033[1menabled by default\033[0m).", + help=( + f"Search root Components by upstreams children " + f"{Style.BOLD}(enabled by default){Style.RESET}." + ), ) @click.option( "--search-related-url", "search_related_url", is_flag=True, default=False, - help="Search related url (\033[1menabled by default\033[0m).", + help=f"Search related url {Style.BOLD}(enabled by default){Style.RESET}.", ) @click.option( "--filter-rh-naming", @@ -357,7 +364,7 @@ def get_product_contain_component( if not purl and not component_name: click.echo(ctx.get_help()) click.echo("") - click.echo("\033[1mMust supply Component name or --purl.\033[0m") + click.echo(f"{Style.BOLD}Must supply Component name or --purl.{Style.RESET}") exit(0) if ( diff --git a/griffon/helpers.py b/griffon/helpers.py index f1822b6..c006976 100644 --- a/griffon/helpers.py +++ b/griffon/helpers.py @@ -2,6 +2,7 @@ Helpers for direct usage or debbuging """ import json +from enum import Enum from typing import Callable, Optional, Type, Union from component_registry_bindings.bindings.python_client.types import ( @@ -61,3 +62,47 @@ def debug_data_load( else: data = json_data return data + + +class Color(Enum): + """ + Helper enum for text color formatting for anything which + cannot be rendered using rich + """ + + BLACK = "\033[30m" + RED = "\033[31m" + GREEN = "\033[32m" + YELLOW = "\033[33m" + BLUE = "\033[34m" + MAGENTA = "\033[35m" + CYAN = "\033[36m" + WHITE = "\033[37m" + GREY = "\033[90m" + BRIGHT_RED = "\033[91m" + BRIGHT_GREEN = "\033[92m" + BRIGHT_YELLOW = "\033[93m" + BRIGHT_BLUE = "\033[94m" + BRIGHT_MAGENTA = "\033[95m" + BRIGHT_CYAN = "\033[96m" + BRIGHT_WHITE = "\033[97m" + RESET = "\033[0m" # Reset to default color and style + + def __str__(self): + return str(self.value) + + +class Style(Enum): + """ + Helper enum for text style formatting for anything which + cannot be rendered using rich + """ + + BOLD = "\033[1m" + ITALIC = "\033[3m" + UNDERLINE = "\033[4m" + STRIKE = "\033[9m" + RESET = "\033[0m" # Reset to default style + + def __str__(self): + return str(self.value) From 6e0a9f13005f1e2ccaea85ac32007e017b510505 Mon Sep 17 00:00:00 2001 From: Jakub Frejlach Date: Thu, 16 Nov 2023 15:21:22 +0100 Subject: [PATCH 4/8] Wrap ClickException with generic GriffonException --- .../entities/community_component_registry.py | 3 ++- griffon/commands/entities/corgi.py | 3 ++- .../entities/osidb/affects/__init__.py | 13 ++++----- .../commands/entities/osidb/affects/cvss.py | 13 ++++----- .../commands/entities/osidb/flaws/__init__.py | 11 ++++---- .../entities/osidb/flaws/acknowledgments.py | 13 ++++----- .../commands/entities/osidb/flaws/comments.py | 5 ++-- griffon/commands/entities/osidb/flaws/cvss.py | 13 ++++----- .../entities/osidb/flaws/package_versions.py | 13 ++++----- .../entities/osidb/flaws/references.py | 13 ++++----- griffon/exceptions.py | 27 ++++++++++++++++--- 11 files changed, 78 insertions(+), 49 deletions(-) diff --git a/griffon/commands/entities/community_component_registry.py b/griffon/commands/entities/community_component_registry.py index 9a4ac9f..ae6da1a 100644 --- a/griffon/commands/entities/community_component_registry.py +++ b/griffon/commands/entities/community_component_registry.py @@ -48,6 +48,7 @@ multivalue_params_to_csv, query_params_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -821,7 +822,7 @@ def corgi_health(ctx): exit(1) except: # noqa console.log(f"{CORGI_SERVER_URL} is NOT operational") - raise click.ClickException("Component registry health check failed.") + raise GriffonException("Component registry health check failed.") @manage_grp.command(name="data") diff --git a/griffon/commands/entities/corgi.py b/griffon/commands/entities/corgi.py index 4b23730..b49e3f6 100644 --- a/griffon/commands/entities/corgi.py +++ b/griffon/commands/entities/corgi.py @@ -43,6 +43,7 @@ multivalue_params_to_csv, query_params_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -847,7 +848,7 @@ def corgi_health(ctx): exit(1) except: # noqa console.log(f"{CORGI_SERVER_URL} is NOT operational") - raise click.ClickException("Component registry health check failed.") + raise GriffonException("Component registry health check failed.") @manage_grp.command(name="data") diff --git a/griffon/commands/entities/osidb/affects/__init__.py b/griffon/commands/entities/osidb/affects/__init__.py index 5c33d8a..5e124df 100644 --- a/griffon/commands/entities/osidb/affects/__init__.py +++ b/griffon/commands/entities/osidb/affects/__init__.py @@ -24,6 +24,7 @@ query_params_options, request_body_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -91,7 +92,7 @@ def get_affect(ctx, affect_uuid, **params): def update_affect(ctx, affect_uuid, **params): request_body_type = getattr(osidb_api_v1_affects_update, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Affect update. " "Is correct version of osidb-bindings installed?" ) @@ -106,7 +107,7 @@ def update_affect(ctx, affect_uuid, **params): except Exception as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to fetch Affect with ID '{affect_uuid}'. " "Affect either does not exist or you have insufficient permissions. " "Consider running griffon with -v option for verbose error log." @@ -126,7 +127,7 @@ def update_affect(ctx, affect_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to update Affect with ID '{affect_uuid}'. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -143,7 +144,7 @@ def update_affect(ctx, affect_uuid, **params): def create_affect(ctx, **params): request_body_type = getattr(osidb_api_v1_affects_create, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Affect create. " "Is correct version of osidb-bindings installed?" ) @@ -170,7 +171,7 @@ def create_affect(ctx, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( "Failed to create Affect. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -195,7 +196,7 @@ def delete_affect(ctx, affect_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to delete {affect_uuid}. " "It either does not exist or you have insufficient permissions." ) diff --git a/griffon/commands/entities/osidb/affects/cvss.py b/griffon/commands/entities/osidb/affects/cvss.py index ab71b19..bcfcd04 100644 --- a/griffon/commands/entities/osidb/affects/cvss.py +++ b/griffon/commands/entities/osidb/affects/cvss.py @@ -24,6 +24,7 @@ query_params_options, request_body_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -106,7 +107,7 @@ def list_affect_cvss(ctx, affect_id, **params): def create_affect_cvss(ctx, affect_id, **params): request_body_type = getattr(osidb_api_v1_affects_cvss_scores_create, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for affect CVSS create. " "Is correct version of osidb-bindings installed?" ) @@ -133,7 +134,7 @@ def create_affect_cvss(ctx, affect_id, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( "Failed to create affect CVSS. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -154,7 +155,7 @@ def create_affect_cvss(ctx, affect_id, **params): def update_affect_cvss(ctx, affect_id, cvss_uuid, **params): request_body_type = getattr(osidb_api_v1_affects_cvss_scores_update, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for affect CVSS update. " "Is correct version of osidb-bindings installed?" ) @@ -171,7 +172,7 @@ def update_affect_cvss(ctx, affect_id, cvss_uuid, **params): except Exception as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to fetch affect CVSS with ID '{cvss_uuid}'. " "affect or affect CVSS either does not exist or you have insufficient permissions. " "Consider running griffon with -v option for verbose error log." @@ -191,7 +192,7 @@ def update_affect_cvss(ctx, affect_id, cvss_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to update affect CVSS with ID '{cvss_uuid}'. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -222,7 +223,7 @@ def delete_affect_cvss(ctx, affect_id, cvss_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to delete affect CVSS {cvss_uuid}. " "It either does not exist or you have insufficient permissions." ) diff --git a/griffon/commands/entities/osidb/flaws/__init__.py b/griffon/commands/entities/osidb/flaws/__init__.py index fd4ffab..9ff5033 100644 --- a/griffon/commands/entities/osidb/flaws/__init__.py +++ b/griffon/commands/entities/osidb/flaws/__init__.py @@ -23,6 +23,7 @@ query_params_options, request_body_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint from .acknowledgments import flaw_acknowledgments @@ -102,7 +103,7 @@ def get_flaw(ctx, flaw_id, **params): def update_flaw(ctx, flaw_id, **params): request_body_type = getattr(osidb_api_v1_flaws_update, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw update. " "Is correct version of osidb-bindings installed?" ) @@ -119,7 +120,7 @@ def update_flaw(ctx, flaw_id, **params): except Exception as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to fetch Flaw with ID '{flaw_id}'. " "Flaw either does not exist or you have insufficient permissions. " "Consider running griffon with -v option for verbose error log." @@ -139,7 +140,7 @@ def update_flaw(ctx, flaw_id, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to update Flaw with ID '{flaw_id}'. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -156,7 +157,7 @@ def update_flaw(ctx, flaw_id, **params): def create_flaw(ctx, **params): request_body_type = getattr(osidb_api_v1_flaws_create, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw create. " "Is correct version of osidb-bindings installed?" ) @@ -183,7 +184,7 @@ def create_flaw(ctx, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( "Failed to create Flaw. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." diff --git a/griffon/commands/entities/osidb/flaws/acknowledgments.py b/griffon/commands/entities/osidb/flaws/acknowledgments.py index 48db29e..624c407 100644 --- a/griffon/commands/entities/osidb/flaws/acknowledgments.py +++ b/griffon/commands/entities/osidb/flaws/acknowledgments.py @@ -24,6 +24,7 @@ query_params_options, request_body_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -116,7 +117,7 @@ def create_flaw_acknowledgment(ctx, flaw_id, **params): osidb_api_v1_flaws_acknowledgments_create, "REQUEST_BODY_TYPE", None ) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw Acknowledgment create. " "Is correct version of osidb-bindings installed?" ) @@ -143,7 +144,7 @@ def create_flaw_acknowledgment(ctx, flaw_id, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( "Failed to create Flaw Acknowledgment. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -166,7 +167,7 @@ def update_flaw_acknowledgment(ctx, flaw_id, acknowledgment_uuid, **params): osidb_api_v1_flaws_acknowledgments_update, "REQUEST_BODY_TYPE", None ) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw Acknowledgment update. " "Is correct version of osidb-bindings installed?" ) @@ -183,7 +184,7 @@ def update_flaw_acknowledgment(ctx, flaw_id, acknowledgment_uuid, **params): except Exception as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to fetch Flaw Acknowledgment with ID '{acknowledgment_uuid}'. " "Flaw or Flaw Acknowledgment either does not exist or you have " "insufficient permissions. Consider running griffon with " @@ -204,7 +205,7 @@ def update_flaw_acknowledgment(ctx, flaw_id, acknowledgment_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to update Flaw Acknowledgment with ID '{acknowledgment_uuid}'. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -235,7 +236,7 @@ def delete_flaw_acknowledgments(ctx, flaw_id, acknowledgment_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to delete Flaw Acknowledgment {acknowledgment_uuid}. " "It either does not exist or you have insufficient permissions." ) diff --git a/griffon/commands/entities/osidb/flaws/comments.py b/griffon/commands/entities/osidb/flaws/comments.py index 613ddd9..71a56ca 100644 --- a/griffon/commands/entities/osidb/flaws/comments.py +++ b/griffon/commands/entities/osidb/flaws/comments.py @@ -22,6 +22,7 @@ query_params_options, request_body_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -104,7 +105,7 @@ def list_flaw_comments(ctx, flaw_id, **params): def create_flaw_comment(ctx, flaw_id, **params): request_body_type = getattr(osidb_api_v1_flaws_comments_create, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw Comment create. " "Is correct version of osidb-bindings installed?" ) @@ -131,7 +132,7 @@ def create_flaw_comment(ctx, flaw_id, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( "Failed to create Flaw Comment. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." diff --git a/griffon/commands/entities/osidb/flaws/cvss.py b/griffon/commands/entities/osidb/flaws/cvss.py index a9f8cd5..543f5e0 100644 --- a/griffon/commands/entities/osidb/flaws/cvss.py +++ b/griffon/commands/entities/osidb/flaws/cvss.py @@ -24,6 +24,7 @@ query_params_options, request_body_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -106,7 +107,7 @@ def list_flaw_cvss(ctx, flaw_id, **params): def create_flaw_cvss(ctx, flaw_id, **params): request_body_type = getattr(osidb_api_v1_flaws_cvss_scores_create, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw CVSS create. " "Is correct version of osidb-bindings installed?" ) @@ -133,7 +134,7 @@ def create_flaw_cvss(ctx, flaw_id, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( "Failed to create Flaw CVSS. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -154,7 +155,7 @@ def create_flaw_cvss(ctx, flaw_id, **params): def update_flaw_cvss(ctx, flaw_id, cvss_uuid, **params): request_body_type = getattr(osidb_api_v1_flaws_cvss_scores_update, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw CVSS update. " "Is correct version of osidb-bindings installed?" ) @@ -171,7 +172,7 @@ def update_flaw_cvss(ctx, flaw_id, cvss_uuid, **params): except Exception as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to fetch Flaw CVSS with ID '{cvss_uuid}'. " "Flaw or Flaw CVSS either does not exist or you have insufficient permissions. " "Consider running griffon with -v option for verbose error log." @@ -191,7 +192,7 @@ def update_flaw_cvss(ctx, flaw_id, cvss_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to update Flaw CVSS with ID '{cvss_uuid}'. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -222,7 +223,7 @@ def delete_flaw_cvss(ctx, flaw_id, cvss_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to delete Flaw CVSS {cvss_uuid}. " "It either does not exist or you have insufficient permissions." ) diff --git a/griffon/commands/entities/osidb/flaws/package_versions.py b/griffon/commands/entities/osidb/flaws/package_versions.py index 26ee455..22ee0ef 100644 --- a/griffon/commands/entities/osidb/flaws/package_versions.py +++ b/griffon/commands/entities/osidb/flaws/package_versions.py @@ -24,6 +24,7 @@ query_params_options, request_body_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -114,7 +115,7 @@ def create_flaw_package_version(ctx, flaw_id, **params): osidb_api_v1_flaws_package_versions_create, "REQUEST_BODY_TYPE", None ) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw Package Version create. " "Is correct version of osidb-bindings installed?" ) @@ -141,7 +142,7 @@ def create_flaw_package_version(ctx, flaw_id, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( "Failed to create Flaw Package Version. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -164,7 +165,7 @@ def update_flaw_package_version(ctx, flaw_id, package_version_uuid, **params): osidb_api_v1_flaws_package_versions_update, "REQUEST_BODY_TYPE", None ) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw Package Version update. " "Is correct version of osidb-bindings installed?" ) @@ -181,7 +182,7 @@ def update_flaw_package_version(ctx, flaw_id, package_version_uuid, **params): except Exception as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to fetch Flaw Package Version with ID '{package_version_uuid}'. " "Flaw or Flaw Package Version either does not exist or you have " "insufficient permissions. " @@ -202,7 +203,7 @@ def update_flaw_package_version(ctx, flaw_id, package_version_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to update Flaw Package Version with ID '{package_version_uuid}'. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -233,7 +234,7 @@ def delete_flaw_package_version(ctx, flaw_id, package_versions_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to delete Flaw Package Version {package_versions_uuid}. " "It either does not exist or you have insufficient permissions." ) diff --git a/griffon/commands/entities/osidb/flaws/references.py b/griffon/commands/entities/osidb/flaws/references.py index 6aad49a..a33d412 100644 --- a/griffon/commands/entities/osidb/flaws/references.py +++ b/griffon/commands/entities/osidb/flaws/references.py @@ -24,6 +24,7 @@ query_params_options, request_body_options, ) +from griffon.exceptions import GriffonException from griffon.output import console, cprint logger = logging.getLogger("griffon") @@ -110,7 +111,7 @@ def list_flaw_references(ctx, flaw_id, **params): def create_flaw_reference(ctx, flaw_id, **params): request_body_type = getattr(osidb_api_v1_flaws_references_create, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw Reference create. " "Is correct version of osidb-bindings installed?" ) @@ -137,7 +138,7 @@ def create_flaw_reference(ctx, flaw_id, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( "Failed to create Flaw Reference. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -158,7 +159,7 @@ def create_flaw_reference(ctx, flaw_id, **params): def update_flaw_reference(ctx, flaw_id, reference_uuid, **params): request_body_type = getattr(osidb_api_v1_flaws_references_update, "REQUEST_BODY_TYPE", None) if request_body_type is None: - raise click.ClickException( + raise GriffonException( "No request body template for Flaw Reference update. " "Is correct version of osidb-bindings installed?" ) @@ -175,7 +176,7 @@ def update_flaw_reference(ctx, flaw_id, reference_uuid, **params): except Exception as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to fetch Flaw Reference with ID '{reference_uuid}'. " "Flaw or Flaw Reference either does not exist or you have insufficient permissions. " "Consider running griffon with -v option for verbose error log." @@ -195,7 +196,7 @@ def update_flaw_reference(ctx, flaw_id, reference_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to update Flaw Reference with ID '{reference_uuid}'. " "You might have insufficient permission or you've supplied malformed data. " "Consider running griffon with -v option for verbose error log." @@ -226,7 +227,7 @@ def delete_flaw_reference(ctx, flaw_id, reference_uuid, **params): except HTTPError as e: if ctx.obj["VERBOSE"]: console.log(e, e.response.json()) - raise click.ClickException( + raise GriffonException( f"Failed to delete Flaw Reference {reference_uuid}. " "It either does not exist or you have insufficient permissions." ) diff --git a/griffon/exceptions.py b/griffon/exceptions.py index 0e0bb44..f9216cd 100644 --- a/griffon/exceptions.py +++ b/griffon/exceptions.py @@ -1,14 +1,33 @@ """ - Trap exceptions generically +Griffon exceptions and exception handling - TODO: eventually we will want to gracefully handle all errors - though useful during development to 'see' everything. +TODO: eventually we will want to gracefully handle all errors +though useful during development to 'see' everything. """ from functools import partial, wraps import click +from griffon.helpers import Color, Style + + +class GriffonException(click.ClickException): + """Base Griffon exception""" + + def __init__(self, message, *args, **kwargs) -> None: + super().__init__(f"{Style.BOLD}{Color.RED}{message}{Color.RESET}", *args, **kwargs) + + def show(self, *args, **kwargs): + click.echo((f"{Style.BOLD}{Color.RED}{' Griffon Error ':*^55}{Style.RESET}")) + super().show(*args, **kwargs) + + +class GriffonUsageError(GriffonException, click.UsageError): + """Griffon exception that signals a usage error""" + + pass + def catch_exception(func=None, *, handle): """catch exception decorator""" @@ -20,6 +39,6 @@ def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except handle as e: - raise click.ClickException(e) + raise click.GriffonException(e) return wrapper From 09fdf0b75011135b02be2a2a40a7b2f3dcf989a7 Mon Sep 17 00:00:00 2001 From: Jakub Frejlach Date: Thu, 16 Nov 2023 17:23:40 +0100 Subject: [PATCH 5/8] Implement click custom options/arguments/commands --- griffon/commands/custom_commands.py | 89 ++++++++++++++++++ griffon/commands/queries.py | 139 +++++++++++++++++++--------- 2 files changed, 183 insertions(+), 45 deletions(-) create mode 100644 griffon/commands/custom_commands.py diff --git a/griffon/commands/custom_commands.py b/griffon/commands/custom_commands.py new file mode 100644 index 0000000..d21b8bc --- /dev/null +++ b/griffon/commands/custom_commands.py @@ -0,0 +1,89 @@ +""" +Custom defined Click commands/options/etc. +""" + +import click + +from griffon.exceptions import GriffonUsageError +from griffon.helpers import Style + + +class ListParamType(click.ParamType): + """Custom comma-separated list type""" + + name = "list" + + def convert(self, value, param, ctx): + if value is None: + return [] + return value.split(",") + + +class BaseGroupParameter: + """ + Custom base parameter which handles: + * mutually exclusive options + * required group options (one of the group is required) + """ + + def handle_parse_result(self, ctx, opts, args): + if self.mutually_exclusive_group: + if opts.get(self.name) is None: + pass # skip check for not supplied click.Arguments + elif self.name in opts and any( + opt in opts for opt in self.mutually_exclusive_group if opts.get(opt) is not None + ): + raise GriffonUsageError( + ( + f"{Style.BOLD}{self.name} cannot be used with " + f"{', '.join(self.mutually_exclusive_group)}.{Style.RESET}" + ), + ctx=ctx, + ) + + if self.required_group: + group_set = set( + opt for opt in opts if opt in self.required_group and opts.get(opt) is not None + ) + if not any(group_set): + raise GriffonUsageError( + f"{Style.BOLD}At least one of {', '.join(self.required_group)} " + f"is required.{Style.RESET}", + ctx=ctx, + ) + + return super().handle_parse_result(ctx, opts, args) + + +class GroupOption(BaseGroupParameter, click.Option): + """Custom Option with BaseGroupParameter functionality""" + + def __init__(self, *args, **kwargs): + self.mutually_exclusive_group = set(kwargs.pop("mutually_exclusive_group", [])) + self.required_group = set(kwargs.pop("required_group", [])) + + if self.mutually_exclusive_group: + mutually_exclusive_str = ", ".join(self.mutually_exclusive_group) + kwargs["help"] = kwargs.get("help", "") + ( + f", this argument is mutually exclusive " + f"with arguments: {Style.BOLD}[{mutually_exclusive_str}]{Style.RESET}" + ) + + if self.required_group: + required_str = ", ".join(self.required_group) + kwargs["help"] = kwargs.get("help", "") + ( + f", at least one of these arguments: " + f"{Style.BOLD}[{required_str}]{Style.RESET} is required." + ) + + super().__init__(*args, **kwargs) + + +class GroupArgument(BaseGroupParameter, click.Argument): + """Custom Argument with BaseGroupParameter functionality""" + + def __init__(self, *args, **kwargs): + self.mutually_exclusive_group = set(kwargs.pop("mutually_exclusive_group", [])) + self.required_group = set(kwargs.pop("required_group", [])) + + super().__init__(*args, **kwargs) diff --git a/griffon/commands/queries.py b/griffon/commands/queries.py index 9197b08..a880f5d 100644 --- a/griffon/commands/queries.py +++ b/griffon/commands/queries.py @@ -28,6 +28,7 @@ get_product_stream_ofuris, get_product_version_names, ) +from griffon.commands.custom_commands import GroupArgument, GroupOption from griffon.commands.entities.corgi import ( get_component_manifest, get_component_summary, @@ -157,10 +158,17 @@ def retrieve_component_summary(ctx, component_name, strict_name_search): ) @click.argument( "component_name", + cls=GroupArgument, required=False, + required_group=["component_name", "purl"], + mutually_exclusive_group=["purl"], ) @click.option( - "--purl", help="Component purl, needs to be in quotes (ex. 'pkg:rpm/python-pyjwt@1.7.1')" + "--purl", + cls=GroupOption, + help="Component purl, needs to be in quotes (ex. 'pkg:rpm/python-pyjwt@1.7.1')", + required_group=["component_name", "purl"], + mutually_exclusive_group=["component_name"], ) @click.option( "--arch", @@ -361,11 +369,6 @@ def get_product_contain_component( """List products of a latest component.""" if verbose: ctx.obj["VERBOSE"] = verbose - if not purl and not component_name: - click.echo(ctx.get_help()) - click.echo("") - click.echo(f"{Style.BOLD}Must supply Component name or --purl.{Style.RESET}") - exit(0) if ( not search_latest @@ -592,8 +595,19 @@ def get_product_contain_component( name="components-contain-component", help="List Components containing Component.", ) -@click.argument("component_name", required=False) -@click.option("--purl") +@click.argument( + "component_name", + cls=GroupArgument, + required=False, + required_group=["component_name", "purl"], + mutually_exclusive_group=["purl"], +) +@click.option( + "--purl", + cls=GroupOption, + required_group=["component_name", "purl"], + mutually_exclusive_group=["component_name"], +) @click.option("--type", "component_type", type=click.Choice(CorgiService.get_component_types())) @click.option("--version", "component_version") @click.option( @@ -633,9 +647,6 @@ def get_component_contain_component( """List components that contain component.""" if verbose: ctx.obj["VERBOSE"] = verbose - if not component_name and not purl: - click.echo(ctx.get_help()) - exit(0) if component_name: q = query_service.invoke(core_queries.components_containing_component_query, ctx.params) cprint(q, ctx=ctx) @@ -650,8 +661,23 @@ def get_component_contain_component( name="product-manifest", help="Get Product manifest (includes Root Components and all dependencies).", ) -@click.argument("product_stream_name", required=False, shell_complete=get_product_stream_names) -@click.option("--ofuri", "ofuri", type=click.STRING, shell_complete=get_product_stream_ofuris) +@click.argument( + "product_stream_name", + cls=GroupArgument, + required=False, + shell_complete=get_product_stream_names, + required_group=["ofuri", "product_stream_name"], + mutually_exclusive_group=["ofuri"], +) +@click.option( + "--ofuri", + "ofuri", + cls=GroupOption, + type=click.STRING, + shell_complete=get_product_stream_ofuris, + required_group=["ofuri", "product_stream_name"], + mutually_exclusive_group=["product_stream_name"], +) @click.option( "--spdx-json", "spdx_json_format", @@ -662,10 +688,6 @@ def get_component_contain_component( @click.pass_context def get_product_manifest_query(ctx, product_stream_name, ofuri, spdx_json_format): """List components of a specific product version.""" - if not ofuri and not product_stream_name: - click.echo(ctx.get_help()) - exit(0) - if spdx_json_format: ctx.ensure_object(dict) ctx.obj["FORMAT"] = "json" # TODO - investigate if we need yaml format. @@ -682,8 +704,23 @@ def get_product_manifest_query(ctx, product_stream_name, ofuri, spdx_json_format name="product-components", help="List LATEST Root Components of Product.", ) -@click.argument("product_stream_name", required=False, shell_complete=get_product_stream_names) -@click.option("--ofuri", "ofuri", type=click.STRING, shell_complete=get_product_stream_ofuris) +@click.argument( + "product_stream_name", + cls=GroupArgument, + required=False, + shell_complete=get_product_stream_names, + required_group=["ofuri", "product_stream_name"], + mutually_exclusive_group=["ofuri"], +) +@click.option( + "--ofuri", + "ofuri", + cls=GroupOption, + type=click.STRING, + shell_complete=get_product_stream_ofuris, + required_group=["ofuri", "product_stream_name"], + mutually_exclusive_group=["product_stream_name"], +) @query_params_options( entity="Component", endpoint_module=v1_components_list, @@ -704,9 +741,6 @@ def get_product_latest_components_query(ctx, product_stream_name, ofuri, verbose if verbose: ctx.obj["VERBOSE"] = verbose ctx.params.pop("verbose") - if not ofuri and not product_stream_name: - click.echo(ctx.get_help()) - exit(0) if ofuri: params["ofuri"] = ofuri if product_stream_name: @@ -724,8 +758,20 @@ def get_product_latest_components_query(ctx, product_stream_name, ofuri, verbose name="component-manifest", help="Get Component manifest.", ) -@click.option("--uuid", "component_uuid") -@click.option("--purl", help="Component Purl (must be quoted).") +@click.option( + "--uuid", + "component_uuid", + cls=GroupOption, + required_group=["component_uuid", "purl"], + mutually_exclusive_group=["purl"], +) +@click.option( + "--purl", + cls=GroupOption, + required_group=["component_uuid", "purl"], + mutually_exclusive_group=["component_uuid"], + help="Component Purl (must be quoted).", +) @click.option( "--spdx-json", "spdx_json_format", @@ -736,9 +782,6 @@ def get_product_latest_components_query(ctx, product_stream_name, ofuri, verbose @click.pass_context def retrieve_component_manifest(ctx, component_uuid, purl, spdx_json_format): """Retrieve Component manifest.""" - if not component_uuid and not purl: - click.echo(ctx.get_help()) - exit(0) if spdx_json_format: ctx.ensure_object(dict) ctx.obj["FORMAT"] = "json" @@ -754,7 +797,7 @@ def retrieve_component_manifest(ctx, component_uuid, purl, spdx_json_format): name="components-affected-by-flaw", help="List Components affected by Flaw.", ) -@click.argument("cve_id", required=False, type=click.STRING, shell_complete=get_cve_ids) +@click.argument("cve_id", required=True, type=click.STRING, shell_complete=get_cve_ids) @click.option( "--affectedness", help="Filter by Affect affectedness.", @@ -789,9 +832,6 @@ def components_affected_by_specific_cve_query( ctx, cve_id, affectedness, affect_resolution, affect_impact, component_type, namespace ): """List components affected by specific CVE.""" - if not cve_id: - click.echo(ctx.get_help()) - exit(0) q = query_service.invoke(core_queries.components_affected_by_specific_cve_query, ctx.params) cprint(q, ctx=ctx) @@ -800,14 +840,11 @@ def components_affected_by_specific_cve_query( name="products-affected-by-flaw", help="List Products affected by Flaw.", ) -@click.argument("cve_id", required=False, type=click.STRING, shell_complete=get_cve_ids) +@click.argument("cve_id", required=True, type=click.STRING, shell_complete=get_cve_ids) @click.pass_context @progress_bar def product_versions_affected_by_cve_query(ctx, cve_id): """List Products affected by a CVE.""" - if not cve_id: - click.echo(ctx.get_help()) - exit(0) q = query_service.invoke( core_queries.products_versions_affected_by_specific_cve_query, ctx.params ) @@ -815,8 +852,19 @@ def product_versions_affected_by_cve_query(ctx, cve_id): @queries_grp.command(name="component-flaws", help="List Flaws affecting a Component.") -@click.argument("component_name", required=False) -@click.option("--purl") +@click.argument( + "component_name", + cls=GroupArgument, + required=False, + required_group=["component_name", "purl"], + mutually_exclusive_group=["purl"], +) +@click.option( + "--purl", + cls=GroupOption, + required_group=["component_name", "purl"], + mutually_exclusive_group=["component_name"], +) @click.option( "--flaw-impact", "flaw_impact", @@ -867,10 +915,6 @@ def cves_for_specific_component_query( strict_name_search, ): """List flaws of a specific component.""" - if not purl and not component_name: - click.echo(ctx.get_help()) - exit(0) - q = query_service.invoke(core_queries.cves_for_specific_component_query, ctx.params) cprint(q, ctx=ctx) @@ -883,9 +927,17 @@ def cves_for_specific_component_query( "product_version_name", required=False, type=click.STRING, + cls=GroupArgument, shell_complete=get_product_version_names, + required_group=["ofuri", "product_version_name"], + mutually_exclusive_group=["ofuri"], +) +@click.option( + "--ofuri", + cls=GroupOption, + required_group=["ofuri", "product_version_name"], + mutually_exclusive_group=["product_version_name"], ) -@click.option("--ofuri") @click.option( "--flaw-impact", "flaw_impact", @@ -936,8 +988,5 @@ def cves_for_specific_product_query( strict_name_search, ): """List flaws of a specific product.""" - if not product_version_name and not ofuri: - click.echo(ctx.get_help()) - exit(0) q = query_service.invoke(core_queries.cves_for_specific_product_query, ctx.params) cprint(q, ctx=ctx) From 09b6dee7b7935235d3a8dd37f1fd2accaec33bf6 Mon Sep 17 00:00:00 2001 From: Jakub Frejlach Date: Tue, 21 Nov 2023 16:51:27 +0100 Subject: [PATCH 6/8] Remove python2 http support --- griffon/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/griffon/__init__.py b/griffon/__init__.py index c42d362..9b08bfe 100644 --- a/griffon/__init__.py +++ b/griffon/__init__.py @@ -72,11 +72,8 @@ def config_logging(level="INFO"): # if set to 'DEBUG' then we want all the http conversation if level == "DEBUG": - try: - import http.client as http_client - except ImportError: - # Python 2 - import httplib as http_client + import http.client as http_client + http_client.HTTPConnection.debuglevel = 1 message_format = "%(asctime)s %(name)s %(levelname)s %(message)s" From 6a7ae84ebc6e8ebbae407192dd4bbef60a9cb8f5 Mon Sep 17 00:00:00 2001 From: Jakub Frejlach Date: Wed, 22 Nov 2023 11:44:48 +0100 Subject: [PATCH 7/8] Standardize progress bar accross griffon this also fixes non working no progress bar option --- griffon/__init__.py | 48 +++- griffon/commands/process.py | 2 +- griffon/commands/queries.py | 453 ++++++++++++++++--------------- griffon/commands/reports.py | 15 +- griffon/services/core_queries.py | 76 +++--- 5 files changed, 326 insertions(+), 268 deletions(-) diff --git a/griffon/__init__.py b/griffon/__init__.py index 9b08bfe..043fb8b 100644 --- a/griffon/__init__.py +++ b/griffon/__init__.py @@ -6,12 +6,13 @@ import logging import os from configparser import ConfigParser +from contextlib import contextmanager from functools import partial, wraps import component_registry_bindings import osidb_bindings from osidb_bindings.bindings.python_client.models import Affect, Flaw, Tracker -from pkg_resources import resource_filename # type: ignore +from pkg_resources import resource_filename from rich.logging import RichHandler from griffon.helpers import Color, Style @@ -340,6 +341,44 @@ def get_fields(model, prefix=""): return fields +@contextmanager +def console_status(no_progress_bar): + """updatable console status progress bar""" + + class DisabledStatusObject: + """ + Dummy disabled status object for graceful handle of + no progress bar option + """ + + def __getattr__(self, attr): + def dummy_method(*args, **kwargs): + pass # Do nothing when any method is called + + return dummy_method + + class StatusObject: + """ + Status object for default Griffon status handling + """ + + def __init__(self, status): + self.status = status + + def update(self, status, *args, **kwargs): + self.status.update( + status=f"[magenta b]griffoning:[/magenta b] [bold]{status}[/bold]", *args, **kwargs + ) + + if no_progress_bar: + yield DisabledStatusObject() + else: + with console.status( + "[magenta b]griffoning[/magenta b]", spinner="line" + ) as operation_status: + yield StatusObject(operation_status) + + def progress_bar( func=None, ): @@ -350,11 +389,8 @@ def progress_bar( @wraps(func) def wrapper(*args, **kwargs): obj: dict = args[0].obj - if obj.get("NO_PROGRESS_BAR"): - func(*args, **kwargs) - else: - with console.status("griffoning", spinner="line"): - func(*args, **kwargs) + with console_status(obj.get("NO_PROGRESS_BAR")) as operation_status: + func(*args, operation_status=operation_status, **kwargs) return wrapper diff --git a/griffon/commands/process.py b/griffon/commands/process.py index 9c2c03f..95e707e 100644 --- a/griffon/commands/process.py +++ b/griffon/commands/process.py @@ -32,7 +32,7 @@ @catch_exception(handle=(HTTPError)) @click.pass_context @progress_bar -def generate_affects_for_component_process(ctx, purl, cve_id): +def generate_affects_for_component_process(ctx, purl, cve_id, operation_status): """Generate affects for specific component.""" if not purl and not cve_id: click.echo(ctx.get_help()) diff --git a/griffon/commands/queries.py b/griffon/commands/queries.py index a880f5d..9f71aab 100644 --- a/griffon/commands/queries.py +++ b/griffon/commands/queries.py @@ -336,6 +336,7 @@ def retrieve_component_summary(ctx, component_name, strict_name_search): help="Verbose output, more detailed search results, can be used multiple times (e.g. -vvv).", ) # noqa @click.pass_context +@progress_bar def get_product_contain_component( ctx, component_name, @@ -364,231 +365,232 @@ def get_product_contain_component( include_product_stream_excluded_components, output_type_filter, verbose, + operation_status, ): - with console.status("griffoning", spinner="line") as operation_status: - """List products of a latest component.""" - if verbose: - ctx.obj["VERBOSE"] = verbose - - if ( - not search_latest - and not search_all - and not search_all_roots - and not search_related_url - and not search_community - and not search_all_upstreams - and not search_redhat - and not search_provides - and not search_upstreams - ): - ctx.params["search_latest"] = True - ctx.params["search_provides"] = True - - params = copy.deepcopy(ctx.params) - params.pop("verbose") - params.pop("sfm2_flaw_id") - params.pop("flaw_mode") - params.pop("affect_mode") - if component_name: - q = query_service.invoke( - core_queries.products_containing_component_query, params, status=operation_status - ) - if purl: - q = query_service.invoke( - core_queries.products_containing_specific_component_query, - params, - status=operation_status, - ) + # with console_status(ctx) as operation_status: + """List products of a latest component.""" + if verbose: + ctx.obj["VERBOSE"] = verbose - # TODO: interim hack for middleware - if component_name and MIDDLEWARE_CLI and not no_middleware: - operation_status.update("griffoning: searching deptopia middleware.", spinner="line") - mw_command = [MIDDLEWARE_CLI, component_name, "-e", "maven", "--json"] - if strict_name_search: - mw_command.append("-s") - proc = subprocess.run( - mw_command, - capture_output=True, - text=True, - ) - try: - mw_json = loads(proc.stdout) - mw_components = mw_json["deps"] - # TODO: need to determine if we use "build" or "deps" - # if search_all: - # mw_components.extend(mw_json["deps"]) - for build in mw_components: - if build["build_type"] == "maven": - component = { - "product_versions": [{"name": build["ps_module"]}], - "product_streams": [ - { - "name": build["ps_update_stream"], - "product_versions": [{"name": build["ps_module"]}], - } - ], - "product_active": True, - "type": build["build_type"], - "name": build["build_name"], - "nvr": build["build_nvr"], - "upstreams": [], - "sources": [], - "software_build": { - "build_id": build["build_id"], - "source": build["build_repo"], - }, - } - if "sources" in build: - for deps in build["sources"]: - for dep in deps["dependencies"]: - components = [] - components.append( - { - "name": dep.get("name"), - "nvr": dep.get("nvr"), - "type": dep.get("type"), - } - ) - component["sources"] = components - q.append(component) - except Exception: - logger.warning("problem accessing deptopia.") - - # TODO: in the short term affect handling will be mediated via sfm2 here in the operation itself # noqa - if ctx.params["sfm2_flaw_id"]: - operation_status.update("griffoning: invoking sfm2.", spinner="line") - - console.no_color = True - console.highlighter = None - operation_status.stop() - - # generate affects - output = raw_json_transform(q, True) - - exclude_products = [] - if get_config_option(ctx.obj["PROFILE"], "exclude"): - exclude_products = get_config_option(ctx.obj["PROFILE"], "exclude").split("\n") - exclude_components = [] - if get_config_option(ctx.obj["PROFILE"], "exclude_components"): - exclude_components = get_config_option( - ctx.obj["PROFILE"], "exclude_components" - ).split("\n") - normalised_results = generate_normalised_results( - output, - exclude_products, - exclude_components, - output_type_filter, - include_inactive_product_streams, - include_product_stream_excluded_components, + if ( + not search_latest + and not search_all + and not search_all_roots + and not search_related_url + and not search_community + and not search_all_upstreams + and not search_redhat + and not search_provides + and not search_upstreams + ): + ctx.params["search_latest"] = True + ctx.params["search_provides"] = True + + params = copy.deepcopy(ctx.params) + params.pop("verbose") + params.pop("sfm2_flaw_id") + params.pop("flaw_mode") + params.pop("affect_mode") + if component_name: + q = query_service.invoke( + core_queries.products_containing_component_query, params, status=operation_status + ) + if purl: + q = query_service.invoke( + core_queries.products_containing_specific_component_query, + params, + status=operation_status, + ) + + # TODO: interim hack for middleware + if component_name and MIDDLEWARE_CLI and not no_middleware: + operation_status.update("searching deptopia middleware.") + mw_command = [MIDDLEWARE_CLI, component_name, "-e", "maven", "--json"] + if strict_name_search: + mw_command.append("-s") + proc = subprocess.run( + mw_command, + capture_output=True, + text=True, + ) + try: + mw_json = loads(proc.stdout) + mw_components = mw_json["deps"] + # TODO: need to determine if we use "build" or "deps" + # if search_all: + # mw_components.extend(mw_json["deps"]) + for build in mw_components: + if build["build_type"] == "maven": + component = { + "product_versions": [{"name": build["ps_module"]}], + "product_streams": [ + { + "name": build["ps_update_stream"], + "product_versions": [{"name": build["ps_module"]}], + } + ], + "product_active": True, + "type": build["build_type"], + "name": build["build_name"], + "nvr": build["build_nvr"], + "upstreams": [], + "sources": [], + "software_build": { + "build_id": build["build_id"], + "source": build["build_repo"], + }, + } + if "sources" in build: + for deps in build["sources"]: + for dep in deps["dependencies"]: + components = [] + components.append( + { + "name": dep.get("name"), + "nvr": dep.get("nvr"), + "type": dep.get("type"), + } + ) + component["sources"] = components + q.append(component) + except Exception: + logger.warning("problem accessing deptopia.") + + # TODO: in the short term affect handling will be mediated via sfm2 here in the operation itself # noqa + if ctx.params["sfm2_flaw_id"]: + operation_status.update("invoking sfm2.") + + console.no_color = True + console.highlighter = None + operation_status.stop() + + # generate affects + output = raw_json_transform(q, True) + + exclude_products = [] + if get_config_option(ctx.obj["PROFILE"], "exclude"): + exclude_products = get_config_option(ctx.obj["PROFILE"], "exclude").split("\n") + exclude_components = [] + if get_config_option(ctx.obj["PROFILE"], "exclude_components"): + exclude_components = get_config_option(ctx.obj["PROFILE"], "exclude_components").split( + "\n" ) - result_tree = generate_result_tree(normalised_results) - affects = generate_affects(ctx, result_tree, exclude_components, "add", format="json") - - # attempt to import sfm2client module - try: - import sfm2client - except ImportError: - logger.warning("sfm2client library not found, cannot compare with flaw affects") - ctx.exit() - - # TODO: paramaterise into dotfile/env var - sfm2 = sfm2client.api.core.SFMApi({"url": "http://localhost:5600"}) - try: - flaw = sfm2.flaw.get(sfm2_flaw_id) - except Exception as e: - logger.warning(f"Could not retrieve flaw {sfm2_flaw_id}: {e}") - return - - if ctx.params["flaw_mode"] == "replace": - if affects: + normalised_results = generate_normalised_results( + output, + exclude_products, + exclude_components, + output_type_filter, + include_inactive_product_streams, + include_product_stream_excluded_components, + ) + result_tree = generate_result_tree(normalised_results) + affects = generate_affects(ctx, result_tree, exclude_components, "add", format="json") + + # attempt to import sfm2client module + try: + import sfm2client + except ImportError: + logger.warning("sfm2client library not found, cannot compare with flaw affects") + ctx.exit() + + # TODO: paramaterise into dotfile/env var + sfm2 = sfm2client.api.core.SFMApi({"url": "http://localhost:5600"}) + try: + flaw = sfm2.flaw.get(sfm2_flaw_id) + except Exception as e: + logger.warning(f"Could not retrieve flaw {sfm2_flaw_id}: {e}") + return + + if ctx.params["flaw_mode"] == "replace": + if affects: + console.print( + f"The following affects will REPLACE all flaw {flaw['id']}'s existing affects in \"new\" state:\n" # noqa + ) + for m in affects: console.print( - f"The following affects will REPLACE all flaw {flaw['id']}'s existing affects in \"new\" state:\n" # noqa + f"{m['product_version']}\t{m['component_name']}", + no_wrap=False, ) + + if click.confirm( + f"\nREPLACE flaw {flaw['id']}'s existing affects in \"new\" state with the above? THIS CANNOT BE UNDONE: ", # noqa + default=True, + ): + click.echo("Updating ...") + # only discard affects in 'new' state, we should preserve all others so not to throw work away # noqa + new_affects = [a for a in flaw["affects"] if a["affected"] != "new"] + # get map of existing affects first, so that we don't try to add duplicates + existing = set((a["ps_module"], a["ps_component"]) for a in new_affects) for m in affects: - console.print( - f"{m['product_version']}\t{m['component_name']}", - no_wrap=False, + if (m["product_version"], m["component_name"]) in existing: + continue + new_affects.append( + { + "affected": "new", + "ps_component": m["component_name"], + "ps_module": m["product_version"], + } ) - - if click.confirm( - f"\nREPLACE flaw {flaw['id']}'s existing affects in \"new\" state with the above? THIS CANNOT BE UNDONE: ", # noqa - default=True, - ): - click.echo("Updating ...") - # only discard affects in 'new' state, we should preserve all others so not to throw work away # noqa - new_affects = [a for a in flaw["affects"] if a["affected"] != "new"] - # get map of existing affects first, so that we don't try to add duplicates - existing = set((a["ps_module"], a["ps_component"]) for a in new_affects) - for m in affects: - if (m["product_version"], m["component_name"]) in existing: - continue - new_affects.append( - { - "affected": "new", - "ps_component": m["component_name"], - "ps_module": m["product_version"], - } - ) - try: - sfm2.flaw.update(flaw["id"], data={"affects": new_affects}) - except Exception as e: - msg = e.response.json() - logger.warning(f"Failed to update flaw: {e}: {msg}") - console.print("Operation done.") - ctx.exit() - - click.echo("No affects were added to flaw.") - else: - console.print("No affects to add to flaw.") + try: + sfm2.flaw.update(flaw["id"], data={"affects": new_affects}) + except Exception as e: + msg = e.response.json() + logger.warning(f"Failed to update flaw: {e}: {msg}") + console.print("Operation done.") + ctx.exit() + + click.echo("No affects were added to flaw.") else: - missing = [] - for affect in affects: - flaw_has_affect = False - for a in flaw.get("affects"): - if a.get("ps_module") == affect.get("product_version") and a.get( - "ps_component" - ) == affect.get("component_name"): - flaw_has_affect = True - if not flaw_has_affect: - missing.append(affect) - - if missing: - console.log("Flaw is missing the following affect entries:\n") + console.print("No affects to add to flaw.") + else: + missing = [] + for affect in affects: + flaw_has_affect = False + for a in flaw.get("affects"): + if a.get("ps_module") == affect.get("product_version") and a.get( + "ps_component" + ) == affect.get("component_name"): + flaw_has_affect = True + if not flaw_has_affect: + missing.append(affect) + + if missing: + console.log("Flaw is missing the following affect entries:\n") + for m in missing: + console.print( + f"{m['product_version']}\t{m['component_name']}", + no_wrap=False, + ) + if click.confirm( + "Would you like to add them? ", + default=True, + ): + click.echo("Updating ...") + + updated_affects = flaw.get("affects")[:] for m in missing: - console.print( - f"{m['product_version']}\t{m['component_name']}", - no_wrap=False, + updated_affects.append( + { + "affected": "new", + "ps_component": m["component_name"], + "ps_module": m["product_version"], + } ) - if click.confirm( - "Would you like to add them? ", - default=True, - ): - click.echo("Updating ...") - - updated_affects = flaw.get("affects")[:] - for m in missing: - updated_affects.append( - { - "affected": "new", - "ps_component": m["component_name"], - "ps_module": m["product_version"], - } - ) - try: - sfm2.flaw.update(flaw["id"], data={"affects": updated_affects}) - except Exception as e: - msg = e.response.json() - logger.warning(f"Failed to update flaw: {e}: {msg}") - console.print("Operation done.") - ctx.exit() - click.echo("No affects were added to flaw.") - - else: - console.print("Flaw is not missing any affect entries") + try: + sfm2.flaw.update(flaw["id"], data={"affects": updated_affects}) + except Exception as e: + msg = e.response.json() + logger.warning(f"Failed to update flaw: {e}: {msg}") + console.print("Operation done.") + ctx.exit() + click.echo("No affects were added to flaw.") - ctx.exit() + else: + console.print("Flaw is not missing any affect entries") - cprint(q, ctx=ctx) + ctx.exit() + + cprint(q, ctx=ctx) @queries_grp.command( @@ -643,6 +645,7 @@ def get_component_contain_component( namespace, strict_name_search, verbose, + operation_status, ): """List components that contain component.""" if verbose: @@ -686,7 +689,8 @@ def get_component_contain_component( help="Generate spdx manifest (json).", ) @click.pass_context -def get_product_manifest_query(ctx, product_stream_name, ofuri, spdx_json_format): +@progress_bar +def get_product_manifest_query(ctx, product_stream_name, ofuri, spdx_json_format, operation_status): """List components of a specific product version.""" if spdx_json_format: ctx.ensure_object(dict) @@ -736,7 +740,10 @@ def get_product_manifest_query(ctx, product_stream_name, ofuri, spdx_json_format help="Verbose output, more detailed search results, can be used multiple times (e.g. -vvv).", ) # noqa @click.pass_context -def get_product_latest_components_query(ctx, product_stream_name, ofuri, verbose, **params): +@progress_bar +def get_product_latest_components_query( + ctx, product_stream_name, ofuri, verbose, operation_status, **params +): """List components of a specific product version.""" if verbose: ctx.obj["VERBOSE"] = verbose @@ -780,7 +787,8 @@ def get_product_latest_components_query(ctx, product_stream_name, ofuri, verbose help="Generate spdx manifest (json).", ) @click.pass_context -def retrieve_component_manifest(ctx, component_uuid, purl, spdx_json_format): +@progress_bar +def retrieve_component_manifest(ctx, component_uuid, purl, spdx_json_format, operation_status): """Retrieve Component manifest.""" if spdx_json_format: ctx.ensure_object(dict) @@ -829,7 +837,14 @@ def retrieve_component_manifest(ctx, component_uuid, purl, spdx_json_format): @click.pass_context @progress_bar def components_affected_by_specific_cve_query( - ctx, cve_id, affectedness, affect_resolution, affect_impact, component_type, namespace + ctx, + cve_id, + affectedness, + affect_resolution, + affect_impact, + component_type, + namespace, + operation_status, ): """List components affected by specific CVE.""" q = query_service.invoke(core_queries.components_affected_by_specific_cve_query, ctx.params) @@ -843,7 +858,7 @@ def components_affected_by_specific_cve_query( @click.argument("cve_id", required=True, type=click.STRING, shell_complete=get_cve_ids) @click.pass_context @progress_bar -def product_versions_affected_by_cve_query(ctx, cve_id): +def product_versions_affected_by_cve_query(ctx, cve_id, operation_status): """List Products affected by a CVE.""" q = query_service.invoke( core_queries.products_versions_affected_by_specific_cve_query, ctx.params @@ -913,6 +928,7 @@ def cves_for_specific_component_query( affect_resolution, affect_impact, strict_name_search, + operation_status, ): """List flaws of a specific component.""" q = query_service.invoke(core_queries.cves_for_specific_component_query, ctx.params) @@ -986,6 +1002,7 @@ def cves_for_specific_product_query( affect_impact, affect_resolution, strict_name_search, + operation_status, ): """List flaws of a specific product.""" q = query_service.invoke(core_queries.cves_for_specific_product_query, ctx.params) diff --git a/griffon/commands/reports.py b/griffon/commands/reports.py index 58884ba..4832f8a 100644 --- a/griffon/commands/reports.py +++ b/griffon/commands/reports.py @@ -42,7 +42,16 @@ def reports_grp(ctx): @click.pass_context @progress_bar def generate_affects_report( - ctx, product_version_name, all, show_components, show_products, purl, name, product_name, ofuri + ctx, + product_version_name, + all, + show_components, + show_products, + purl, + name, + product_name, + ofuri, + operation_status, ): """A report operation""" if not all and not product_version_name: @@ -55,7 +64,7 @@ def generate_affects_report( @click.option("--all", is_flag=True, default=True, help="Show summary report on all entities.") @click.pass_context @progress_bar -def generate_entity_report(ctx, all): +def generate_entity_report(ctx, all, operation_status): """A report operation""" if not all: click.echo(ctx.get_help()) @@ -76,7 +85,7 @@ def generate_entity_report(ctx, all): ) @click.pass_context @progress_bar -def generate_license_report(ctx, product_stream_name, purl, exclude_children): +def generate_license_report(ctx, product_stream_name, purl, exclude_children, operation_status): """A report operation""" if not product_stream_name and not purl: click.echo(ctx.get_help()) diff --git a/griffon/services/core_queries.py b/griffon/services/core_queries.py index 9a56ab6..4b9e27a 100644 --- a/griffon/services/core_queries.py +++ b/griffon/services/core_queries.py @@ -303,7 +303,7 @@ def __init__(self, params: dict) -> None: self.include_inactive_product_streams = self.params.get("include_inactive_product_streams") def execute(self, status=None) -> List[Dict[str, Any]]: - status.update("griffoning: searching component-registry.") + status.update("searching component-registry.") results = [] params = { "limit": 50, @@ -321,27 +321,27 @@ def execute(self, status=None) -> List[Dict[str, Any]]: search_latest_params["active_streams"] = "True" search_latest_params["root_components"] = "True" search_latest_params["latest_components_by_streams"] = "True" - status.update("griffoning: searching latest root component(s).") + status.update("searching latest root component(s).") latest_components_cnt = self.corgi_session.components.count(**search_latest_params) - status.update(f"griffoning: found {latest_components_cnt} latest component(s).") + status.update(f"found {latest_components_cnt} latest component(s).") latest_components = self.corgi_session.components.retrieve_list_iterator_async( **search_latest_params ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {latest_components_cnt} latest root component(s), retrieving sources & upstreams." # noqa + f"found {latest_components_cnt} latest root component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.corgi_session), latest_components ): results.append(processed_component) if not self.no_community: - status.update("griffoning: searching latest community root component(s).") + status.update("searching latest community root component(s).") community_component_cnt = self.community_session.components.count( **search_latest_params ) status.update( - f"griffoning: found {community_component_cnt} latest community root component(s)." # noqa + f"found {community_component_cnt} latest community root component(s)." # noqa ) latest_community_components = ( self.community_session.components.retrieve_list_iterator_async( @@ -350,7 +350,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {community_component_cnt} latest community root component(s), retrieving sources & upstreams." # noqa + f"found {community_component_cnt} latest community root component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.community_session), @@ -370,27 +370,27 @@ def execute(self, status=None) -> List[Dict[str, Any]]: search_provides_params["active_streams"] = "True" search_provides_params["root_components"] = "True" search_provides_params["latest_components_by_streams"] = "True" - status.update("griffoning: searching latest provided child component(s).") + status.update("searching latest provided child component(s).") latest_components_cnt = self.corgi_session.components.count(**search_provides_params) - status.update(f"griffoning: found {latest_components_cnt} latest component(s).") + status.update(f"found {latest_components_cnt} latest component(s).") latest_components = self.corgi_session.components.retrieve_list_iterator_async( **search_provides_params ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {latest_components_cnt} latest provides child component(s), retrieving sources & upstreams." # noqa + f"found {latest_components_cnt} latest provides child component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.corgi_session), latest_components ): results.append(processed_component) if not self.no_community: - status.update("griffoning: searching latest community provided child component(s).") + status.update("searching latest community provided child component(s).") community_component_cnt = self.community_session.components.count( **search_provides_params ) status.update( - f"griffoning: found {community_component_cnt} latest community provided child component(s)." # noqa + f"found {community_component_cnt} latest community provided child component(s)." # noqa ) latest_community_components = ( self.community_session.components.retrieve_list_iterator_async( @@ -399,7 +399,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {community_component_cnt} latest community provided child component(s), retrieving sources & upstreams." # noqa + f"found {community_component_cnt} latest community provided child component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.community_session), @@ -420,29 +420,27 @@ def execute(self, status=None) -> List[Dict[str, Any]]: search_upstreams_params["active_streams"] = "True" search_upstreams_params["released_components"] = "True" search_upstreams_params["latest_components_by_streams"] = "True" - status.update("griffoning: searching latest upstreams child component(s).") + status.update("searching latest upstreams child component(s).") latest_components_cnt = self.corgi_session.components.count(**search_upstreams_params) - status.update(f"griffoning: found {latest_components_cnt} latest component(s).") + status.update(f"found {latest_components_cnt} latest component(s).") latest_components = self.corgi_session.components.retrieve_list_iterator_async( **search_upstreams_params ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {latest_components_cnt} latest upstreams child component(s), retrieving sources & upstreams." # noqa + f"found {latest_components_cnt} latest upstreams child component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.corgi_session), latest_components ): results.append(processed_component) if not self.no_community: - status.update( - "griffoning: searching latest community upstreams child component(s)." - ) + status.update("searching latest community upstreams child component(s).") community_component_cnt = self.community_session.components.count( **search_upstreams_params ) status.update( - f"griffoning: found {community_component_cnt} latest community upstreams child component(s)." # noqa + f"found {community_component_cnt} latest community upstreams child component(s)." # noqa ) latest_community_components = ( self.community_session.components.retrieve_list_iterator_async( @@ -451,7 +449,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {community_component_cnt} latest community provided child component(s), retrieving sources & upstreams." # noqa + f"found {community_component_cnt} latest community provided child component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.community_session), @@ -473,15 +471,13 @@ def execute(self, status=None) -> List[Dict[str, Any]]: related_url_components_cnt = self.corgi_session.components.count( **search_related_url_params ) - status.update( - f"griffoning: found {related_url_components_cnt} related url component(s)." - ) + status.update(f"found {related_url_components_cnt} related url component(s).") related_url_components = self.corgi_session.components.retrieve_list_iterator_async( **search_related_url_params ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {related_url_components_cnt} related url component(s), retrieving sources & upstreams." # noqa + f"found {related_url_components_cnt} related url component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.corgi_session), related_url_components @@ -492,7 +488,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: **search_related_url_params ) status.update( - f"griffoning: found {latest_community_url_components_cnt} related url community component(s)." # noqa + f"found {latest_community_url_components_cnt} related url community component(s)." # noqa ) latest_community_url_components = ( self.community_session.components.retrieve_list_iterator_async( @@ -501,7 +497,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {latest_community_url_components_cnt} related url community component(s), retrieving sources & upstreams." # noqa + f"found {latest_community_url_components_cnt} related url community component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.community_session), @@ -523,15 +519,15 @@ def execute(self, status=None) -> List[Dict[str, Any]]: search_all_params["active_streams"] = "True" search_all_params["released_components"] = "True" all_components_cnt = self.corgi_session.components.count(**search_all_params) - status.update(f"griffoning: found {all_components_cnt} all component(s).") + status.update(f"found {all_components_cnt} all component(s).") # TODO: remove max_results all_components = self.corgi_session.components.retrieve_list_iterator_async( **search_all_params, max_results=10000 ) - status.update(f"griffoning: found {all_components_cnt} all component(s).") + status.update(f"found {all_components_cnt} all component(s).") with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {all_components_cnt} all component(s), retrieving sources & upstreams." # noqa + f"found {all_components_cnt} all component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.corgi_session), all_components @@ -543,7 +539,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: **search_all_params ) status.update( - f"griffoning: found {all_community_components_cnt} community all component(s)." # noqa + f"found {all_community_components_cnt} community all component(s)." # noqa ) # TODO: remove max_results all_community_components = ( @@ -553,7 +549,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {all_community_components_cnt} community all component(s), retrieving sources & upstreams." # noqa + f"found {all_community_components_cnt} community all component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.community_session), @@ -574,7 +570,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: search_all_roots_params["active_streams"] = "True" search_all_roots_params["released_components"] = "True" all_src_components_cnt = self.corgi_session.components.count(**search_all_roots_params) - status.update(f"griffoning: found {all_src_components_cnt} all root component(s).") + status.update(f"found {all_src_components_cnt} all root component(s).") all_src_components = self.corgi_session.components.retrieve_list_iterator_async( **search_all_roots_params ) @@ -590,7 +586,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) ) status.update( - f"griffoning: found {all_src_community_components_cnt} community all root component(s)." # noqa + f"found {all_src_community_components_cnt} community all root component(s)." # noqa ) for c in all_src_community_components: results.append(c) @@ -610,12 +606,12 @@ def execute(self, status=None) -> List[Dict[str, Any]]: upstream_components_cnt = self.corgi_session.components.count( **search_all_upstreams_params ) - status.update(f"griffoning: found {upstream_components_cnt} upstream component(s).") + status.update(f"found {upstream_components_cnt} upstream component(s).") upstream_components = self.corgi_session.components.retrieve_list_iterator_async( **search_all_upstreams_params ) with multiprocessing.Pool() as pool: - status.update(f"griffoning: found {upstream_components_cnt} upstream component(s).") + status.update(f"found {upstream_components_cnt} upstream component(s).") for processed_component in pool.map( partial(process_component, self.corgi_session), upstream_components ): @@ -625,7 +621,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: **search_all_upstreams_params ) status.update( - f"griffoning: found {commmunity_upstream_components_cnt} community upstream component(s)." # noqa + f"found {commmunity_upstream_components_cnt} community upstream component(s)." # noqa ) commmunity_upstream_components = ( self.community_session.components.retrieve_list_iterator_async( @@ -634,7 +630,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {commmunity_upstream_components_cnt} community upstream component(s), retrieving sources & upstreams." # noqa + f"found {commmunity_upstream_components_cnt} community upstream component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.community_session), @@ -695,7 +691,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: **search_community_params ) status.update( - f"griffoning: found {all_community_components_cnt} community all component(s)." # noqa + f"found {all_community_components_cnt} community all component(s)." # noqa ) all_community_components = ( self.community_session.components.retrieve_list_iterator_async( @@ -704,7 +700,7 @@ def execute(self, status=None) -> List[Dict[str, Any]]: ) with multiprocessing.Pool() as pool: status.update( - f"griffoning: found {all_community_components_cnt} community all component(s), retrieving sources & upstreams." # noqa + f"found {all_community_components_cnt} community all component(s), retrieving sources & upstreams." # noqa ) for processed_component in pool.map( partial(process_component, self.community_session), all_community_components From 85dae729758e4b97ac3abced0eb5ca9f60debe7c Mon Sep 17 00:00:00 2001 From: Jakub Frejlach Date: Wed, 22 Nov 2023 12:15:21 +0100 Subject: [PATCH 8/8] Update changelog --- CHANGELOG.md | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fcd6f..38cbb3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased -### changed -* existing --search-upstreams changed to --search-all-upstreams in +### Changed +* existing --search-upstreams changed to --search-all-upstreams in service products-contain-component +* OSIDB_API_URL environment variable changed to OSIDB_SERVER_URL +* CORGI_API_URL environment variable changed to CORGI_SERVER_URL + ### Added * --search-provides in service products-contain-component which will search name of components that are provide dependencies * --search-upstreams in service products-contain-component which will search name of components that are upstream dependencies +* example plugin introduced + +### Fixed +* standardized progress bar accross whole Griffon which fixes + no_progress_bar functionality ## [0.3.8] - 2023-10-18 ### Added @@ -76,6 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.20] - 2023-08-11 ### Changed * make 'new' default for -a (when generating affects) + ### Added * --no-upstream-affects which will exclude upstream components when generating affects @@ -121,19 +130,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.10] - 2023-06-30 ### Changed * return components in active product streams for products-contains-component operation + ### Added * --include-inactive-product-streams option for products-contains-component operation -* + ## [0.2.9] - 2023-06-29 ### Changed * pin osidb-bindings 3.3.0 * update crypto python module -* fix generation of affects +* fix generation of affects ## [0.2.8] - 2023-06-28 ### Added - CRUD operations for OSIDB entities Flaw Comment (create, retreive, list), Flaw Reference (create, retrieve, list, update, delete) + ## [0.2.7] - 2023-06-14 ### Changed - ensure we choose latest version of component using products-contains-component @@ -142,13 +153,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.6] - 2023-05-29 ### Added - add terminal --width flag + ### Changed - bumped python requests module in tests, dev and lint requirements to address vuln ## [0.2.5] - 2023-05-26 ### Added - minimal middleware support to products-contains-component, requires Requires GRIFFON_MIDDLEWARE_CLI to be set. - + ### Changed - bumped python requests module to address vuln - fixed products-contains-component when used with --purl @@ -156,9 +168,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.4] - 2023-05-25 ### Added - add --no-wrap flag + ### Changed - enhanced error handling when retrieving product streams and manifests -- fixed regex display +- fixed regex display ## [0.2.3] - 2023-05-24 ### Changed @@ -166,8 +179,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.2.2] - 2023-05-24 ### Changed -- fix --search-upstreams +- fix --search-upstreams - add summary count of sources + ### Added - bugzilla bzowner plugin @@ -192,6 +206,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - allow -v at the end of CLI invoke - prefer nvr for component name in text output + ### Added - added entities component-registry components tree which displays dependency tree @@ -208,6 +223,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - deactivate progress bar when performing entity CRUD - enabled community searching when using --search-all + ### Added - added --search-redhat @@ -258,6 +274,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - simple griffon plugins semgrep - products, product-versions, product-variants, channels to corgi entities + ### Changed - minor plugin enhancements - minor docs updates @@ -272,6 +289,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - enable OSIDB local development instances to be used with Griffon - added editor option to .griffonrc + ### Added - CRUD operations for OSIDB entitites. Flaws (create, update, get, list), Affects (create, update, delete, get, list), Trackers (get, list) @@ -292,6 +310,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.1.8] - 2023-03-31 ### Changed - shortern sha256 versions in service product-components + ### Added - added type,latest and download_url to service component-summary operation