From 2ee98fea6453003b4581dbd02127009ae5b91a82 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 11 Feb 2022 10:17:37 +0000 Subject: [PATCH 1/6] chore: bumped dependencies (#107) Signed-off-by: Paul Horton --- jake/command/oss.py | 48 +++++++++++------------ jake/command/sbom.py | 8 ++-- poetry.lock | 91 +++++++++++++++++++++++++++++--------------- pyproject.toml | 4 +- 4 files changed, 90 insertions(+), 61 deletions(-) diff --git a/jake/command/oss.py b/jake/command/oss.py index 030be68..c2ab0d7 100644 --- a/jake/command/oss.py +++ b/jake/command/oss.py @@ -102,53 +102,53 @@ def handle_args(self) -> int: components: List[Component] = [] for component in parser.get_components(): oss_index_component: OssIndexComponent = list(filter( - lambda oic: oic.get_package_url().to_string() == component.purl.to_string(), oss_index_results + lambda oic_: oic_.get_package_url().to_string() == component.purl.to_string(), oss_index_results )).pop() - if oss_index_component.has_known_vulnerabilities(): - for oic_vulnerability in oss_index_component.get_vulnerabilities(): + if oss_index_component.vulnerabilities: + for oic_vulnerability in oss_index_component.vulnerabilities: ratings: List[VulnerabilityRating] = [] - if oic_vulnerability.get_cvss_score(): + if oic_vulnerability.cvss_score: ratings.append( VulnerabilityRating( source=VulnerabilitySource( - name='OSS Index', url=XsUri(oic_vulnerability.get_oss_index_reference_url()) + name='OSS Index', url=XsUri(oic_vulnerability.reference) ), score=Decimal( - oic_vulnerability.get_cvss_score() - ) if oic_vulnerability.get_cvss_score() else None, + oic_vulnerability.cvss_score + ) if oic_vulnerability.cvss_score else None, severity=VulnerabilitySeverity.get_from_cvss_scores( - (oic_vulnerability.get_cvss_score(),) - ) if oic_vulnerability.get_cvss_score() else None, + (oic_vulnerability.cvss_score,) + ) if oic_vulnerability.cvss_score else None, method=VulnerabilityScoreSource.get_from_vector( - vector=oic_vulnerability.get_cvss_vector() - ) if oic_vulnerability.get_cvss_vector() else None, - vector=oic_vulnerability.get_cvss_vector() + vector=oic_vulnerability.cvss_vector + ) if oic_vulnerability.cvss_vector else None, + vector=oic_vulnerability.cvss_vector ) ) vulnerability: Vulnerability = Vulnerability( - bom_ref=str(oic_vulnerability.get_id()) if oic_vulnerability.get_id() else None, - id=str(oic_vulnerability.get_id()), + bom_ref=oic_vulnerability.id, + id=oic_vulnerability.id, source=VulnerabilitySource( - name='OSS Index', url=XsUri(oic_vulnerability.get_oss_index_reference_url()) + name='OSS Index', url=XsUri(oic_vulnerability.reference) ), - cwes=[int(oic_vulnerability.get_cwe()[4:])] if oic_vulnerability.get_cwe() else None, - description=oic_vulnerability.get_title(), - detail=oic_vulnerability.get_description(), + cwes=[int(oic_vulnerability.cwe[4:])] if oic_vulnerability.cwe else None, + description=oic_vulnerability.title, + detail=oic_vulnerability.description, ratings=ratings, references=[ VulnerabilityReference( - id=str(oic_vulnerability.get_cve()), source=VulnerabilitySource( - name='OSS Index', url=XsUri(oic_vulnerability.get_oss_index_reference_url()) + id=oic_vulnerability.display_name, source=VulnerabilitySource( + name='OSS Index', url=XsUri(oic_vulnerability.reference) ) ) ] ) - if oic_vulnerability.get_external_reference_urls(): + if oic_vulnerability.external_references: advisories: List[VulnerabilityAdvisory] = [] - for ext_ref_url in oic_vulnerability.get_external_reference_urls(): + for ext_ref_url in oic_vulnerability.external_references: advisories.append( VulnerabilityAdvisory(url=XsUri(uri=ext_ref_url)) ) @@ -189,13 +189,13 @@ def handle_args(self) -> int: # Update exit_code if warn only is not enabled and issues have been detected if not self._arguments.warn_only: for oic in oss_index_results: - if oic.has_known_vulnerabilities(): + if oic.vulnerabilities: exit_code = 1 break return exit_code - def setup_argument_parser(self, subparsers: argparse._SubParsersAction): + def setup_argument_parser(self, subparsers: argparse._SubParsersAction) -> None: parser = subparsers.add_parser('ddt', help='perform a scan backed by OSS Index') parser.add_argument('--clear-cache', help='Clears any local cached OSS Index data prior to execution', diff --git a/jake/command/sbom.py b/jake/command/sbom.py index ea33b7a..658889e 100644 --- a/jake/command/sbom.py +++ b/jake/command/sbom.py @@ -33,7 +33,7 @@ ThisTool = Tool(vendor='Sonatype Nexus Community', name='jake', version=_jake_version or 'UNKNOWN') -ThisTool.add_external_references(references=[ +ThisTool.external_references.update([ ExternalReference( reference_type=ExternalReferenceType.BUILD_SYSTEM, url=XsUri('https://app.circleci.com/pipelines/github/sonatype-nexus-community/jake') @@ -68,9 +68,9 @@ class SbomCommand(BaseCommand): def handle_args(self) -> int: - self._arguments.sbom_input_type - bom = Bom.from_parser(self._get_parser()) - bom.metadata.add_tool(ThisTool) + # self._arguments.sbom_input_type + bom = Bom.from_parser(parser=self._get_parser()) + bom.metadata.tools.add(ThisTool) output_format = OutputFormat.XML if self._arguments.sbom_output_format == 'json': diff --git a/poetry.lock b/poetry.lock index 5e69e42..9460b9e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -30,7 +30,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.10" +version = "2.0.11" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -71,18 +71,18 @@ toml = ["tomli"] [[package]] name = "cyclonedx-bom" -version = "2.0.1" +version = "3.0.0rc0" description = "CycloneDX Software Bill of Materials (SBOM) generation utility" category = "main" optional = false python-versions = ">=3.6,<4.0" [package.dependencies] -cyclonedx-python-lib = ">=1.3.0,<2.0.0" +cyclonedx-python-lib = ">=2.0.0rc1,<3.0.0" [[package]] name = "cyclonedx-python-lib" -version = "1.3.0" +version = "2.0.0rc1" description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files." category = "main" optional = false @@ -195,7 +195,7 @@ python-versions = "*" [[package]] name = "ossindex-lib" -version = "0.2.1" +version = "1.0.0rc1" description = "A library for querying the OSS Index free catalogue of open source components to help developers identify vulnerabilities, understand risk, and keep their software safe." category = "main" optional = false @@ -203,19 +203,21 @@ python-versions = ">=3.6,<4.0" [package.dependencies] packageurl-python = ">=0.9.4,<0.10.0" -requests = ">=2.26.0,<3.0.0" +requests = ">=2.27.1,<3.0.0" tinydb = ">=4.5.1,<5.0.0" +types-requests = ">=2.27.8,<3.0.0" +types-setuptools = ">=57.0.0" [[package]] name = "packageurl-python" -version = "0.9.6" +version = "0.9.7" description = "A purl aka. Package URL parser and builder" category = "main" optional = false python-versions = ">=3.6" [package.extras] -test = ["pytest", "isort"] +test = ["isort", "pytest"] [[package]] name = "packaging" @@ -356,7 +358,7 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rich" -version = "11.0.0" +version = "11.2.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "main" optional = false @@ -422,9 +424,20 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] +[[package]] +name = "types-requests" +version = "2.27.9" +description = "Typing stubs for requests" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +types-urllib3 = "<1.27" + [[package]] name = "types-setuptools" -version = "57.4.7" +version = "57.4.9" description = "Typing stubs for setuptools" category = "main" optional = false @@ -432,12 +445,20 @@ python-versions = "*" [[package]] name = "types-toml" -version = "0.10.3" +version = "0.10.4" description = "Typing stubs for toml" category = "main" optional = false python-versions = "*" +[[package]] +name = "types-urllib3" +version = "1.26.9" +description = "Typing stubs for urllib3" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "typing-extensions" version = "3.10.0.2" @@ -461,7 +482,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.13.0" +version = "20.13.1" description = "Virtual Python Environment builder" category = "dev" optional = false @@ -494,7 +515,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "5a105db8059665d0b73d021f518e096931e4daf525c733d55b9b047ba495804b" +content-hash = "3953052192b6b6ceead8f852e0570dc8770c2189adf32f225c07477e9e708599" [metadata.files] atomicwrites = [ @@ -510,8 +531,8 @@ certifi = [ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, - {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, + {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, + {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -571,12 +592,12 @@ coverage = [ {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] cyclonedx-bom = [ - {file = "cyclonedx-bom-2.0.1.tar.gz", hash = "sha256:83ae46cd76b0a7137bb786c6696c2f2c55abe10872a815381a2e3c064b2a5d9e"}, - {file = "cyclonedx_bom-2.0.1-py3-none-any.whl", hash = "sha256:57a932053118804f30d11b634b2205652d6100d671a62873ddea0a658435d47e"}, + {file = "cyclonedx-bom-3.0.0rc0.tar.gz", hash = "sha256:642096a6fb46618fa6a71f593963852293702eee6a0e523e704c7b7ce5eca441"}, + {file = "cyclonedx_bom-3.0.0rc0-py3-none-any.whl", hash = "sha256:e1267d8bf380ad70913dfb40295d0c2fb00d59e711419d8353634cfe4221e5f8"}, ] cyclonedx-python-lib = [ - {file = "cyclonedx-python-lib-1.3.0.tar.gz", hash = "sha256:8793fcf6a4735835bda33cda461a9a60c63faf0a8f9c58fc1fc4312e8f307164"}, - {file = "cyclonedx_python_lib-1.3.0-py3-none-any.whl", hash = "sha256:2b8b1250f6b22b4836bfb86b87381a228377e4d329d6d6d7488a2cdfbc073049"}, + {file = "cyclonedx-python-lib-2.0.0rc1.tar.gz", hash = "sha256:5f2badb8f73e88607f9c79fcf2bbb04e69a9dbab610236efbc8114651224e5f8"}, + {file = "cyclonedx_python_lib-2.0.0rc1-py3-none-any.whl", hash = "sha256:c32c9fab754161e19c599a11d23e396f64fa3f7a9541482197c735b9c69056e0"}, ] dataclasses = [ {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, @@ -615,12 +636,12 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] ossindex-lib = [ - {file = "ossindex-lib-0.2.1.tar.gz", hash = "sha256:cce56009d83904df3e94bcbf701dea5977df1dad26c3ab2aa91ca50ffc100013"}, - {file = "ossindex_lib-0.2.1-py3-none-any.whl", hash = "sha256:72a888d82272bab3eac050f2953fc1207133bb30c64b0a9b393a2f334d19d12a"}, + {file = "ossindex-lib-1.0.0rc1.tar.gz", hash = "sha256:5d18bf333d964f6809b4777d6be4ec980f761b4657601900fc7432432cacb333"}, + {file = "ossindex_lib-1.0.0rc1-py3-none-any.whl", hash = "sha256:da47c955b2ab302d8de68f75df33a193cb6dbb8577d9ee2f99bf7e36b8acd722"}, ] packageurl-python = [ - {file = "packageurl-python-0.9.6.tar.gz", hash = "sha256:c01fbaf62ad2eb791e97158d1f30349e830bee2dd3e9503a87f6c3ffae8d1cf0"}, - {file = "packageurl_python-0.9.6-py3-none-any.whl", hash = "sha256:676dcb8278721df952e2444bfcd8d7bf3518894498050f0c6a5faddbe0860cd0"}, + {file = "packageurl-python-0.9.7.tar.gz", hash = "sha256:d43db6af6a350099e32e9e05e737013d0409287b761fa59c58a03e55dbc3648a"}, + {file = "packageurl_python-0.9.7-py3-none-any.whl", hash = "sha256:ba971c2f3c5c976574803467e97522ae5f542ade3dfec763192a52abb2d7ea8f"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, @@ -671,8 +692,8 @@ requests = [ {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rich = [ - {file = "rich-11.0.0-py3-none-any.whl", hash = "sha256:d7a8086aa1fa7e817e3bba544eee4fd82047ef59036313147759c11475f0dafd"}, - {file = "rich-11.0.0.tar.gz", hash = "sha256:c32a8340b21c75931f157466fefe81ae10b92c36a5ea34524dff3767238774a4"}, + {file = "rich-11.2.0-py3-none-any.whl", hash = "sha256:d5f49ad91fb343efcae45a2b2df04a9755e863e50413623ab8c9e74f05aee52b"}, + {file = "rich-11.2.0.tar.gz", hash = "sha256:1a6266a5738115017bb64a66c59c717e7aa047b3ae49a011ede4abdeffc6536e"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -690,13 +711,21 @@ tox = [ {file = "tox-3.24.5-py2.py3-none-any.whl", hash = "sha256:be3362472a33094bce26727f5f771ca0facf6dafa217f65875314e9a6600c95c"}, {file = "tox-3.24.5.tar.gz", hash = "sha256:67e0e32c90e278251fea45b696d0fef3879089ccbe979b0c556d35d5a70e2993"}, ] +types-requests = [ + {file = "types-requests-2.27.9.tar.gz", hash = "sha256:7368974534d297939492efdfdab232930440b11e2203f6df1f0c40e3242a87ea"}, + {file = "types_requests-2.27.9-py3-none-any.whl", hash = "sha256:74070045418faf710f3154403d6a16c9e67db50e5119906ca6955f1658d20f7b"}, +] types-setuptools = [ - {file = "types-setuptools-57.4.7.tar.gz", hash = "sha256:9677d969b00ec1c14552f5be2b2b47a6fbea4d0ed4de0fdcee18abdaa0cc9267"}, - {file = "types_setuptools-57.4.7-py3-none-any.whl", hash = "sha256:ffda504687ea02d4b7751c0d1df517fbbcdc276836d90849e4f1a5f1ccd79f01"}, + {file = "types-setuptools-57.4.9.tar.gz", hash = "sha256:536ef74744f8e1e4be4fc719887f886e74e4cf3c792b4a06984320be4df450b5"}, + {file = "types_setuptools-57.4.9-py3-none-any.whl", hash = "sha256:948dc6863373750e2cd0b223a84f1fb608414cde5e55cf38ea657b93aeb411d2"}, ] types-toml = [ - {file = "types-toml-0.10.3.tar.gz", hash = "sha256:215a7a79198651ec5bdfd66193c1e71eb681a42f3ef7226c9af3123ced62564a"}, - {file = "types_toml-0.10.3-py3-none-any.whl", hash = "sha256:988457744d9774d194e3539388772e3a685d8057b7c4a89407afeb0a6cbd1b14"}, + {file = "types-toml-0.10.4.tar.gz", hash = "sha256:9340e7c1587715581bb13905b3af30b79fe68afaccfca377665d5e63b694129a"}, + {file = "types_toml-0.10.4-py3-none-any.whl", hash = "sha256:4a9ffd47bbcec49c6fde6351a889b2c1bd3c0ef309fa0eed60dc28e58c8b9ea6"}, +] +types-urllib3 = [ + {file = "types-urllib3-1.26.9.tar.gz", hash = "sha256:abd2d4857837482b1834b4817f0587678dcc531dbc9abe4cde4da28cef3f522c"}, + {file = "types_urllib3-1.26.9-py3-none-any.whl", hash = "sha256:4a54f6274ab1c80968115634a55fb9341a699492b95e32104a7c513db9fe02e9"}, ] typing-extensions = [ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, @@ -708,8 +737,8 @@ urllib3 = [ {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] virtualenv = [ - {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, - {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, + {file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"}, + {file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"}, ] zipp = [ {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, diff --git a/pyproject.toml b/pyproject.toml index e44a1ae..bc684e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,8 +41,8 @@ jake = 'jake.app:main' [tool.poetry.dependencies] python = "^3.6.2" -cyclonedx-bom = "^2.0.1" -ossindex-lib = ">= 0.2.1" +cyclonedx-bom = "^3.0.0rc0" +ossindex-lib = "^1.0.0rc1" polling2 = ">= 0.5.0" pyfiglet = ">= 0.8.post1" requests = "^>= 2.25.1" From 671c8c4bb682b4b944568198ecf381818d49f3ac Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Fri, 11 Feb 2022 11:44:43 +0000 Subject: [PATCH 2/6] feat: support for Python 3.10 (#110) Signed-off-by: Paul Horton --- .circleci/config.yml | 21 ++++++++++++--------- jake/__init__.py | 15 +++++++++++++++ poetry.lock | 6 +++--- pyproject.toml | 3 ++- tests/__init__.py | 15 +++++++++++++++ 5 files changed, 47 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 194a53a..e388f14 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,5 @@ -# Copyright 2019-present Sonatype Inc. +# +# Copyright 2019-Present Sonatype Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,19 +12,22 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +# version: 2.1 commands: ensure_poetry_installed: - description: "Installs Poetry ready for use" + description: "Install Poetry" steps: - run: | python -m ensurepip --default-pip pip install --upgrade pip - pip install poetry + pip install poetry==1.1.11 executors: + python310: + docker: + - image: cimg/python:3.10 python39: docker: - image: cimg/python:3.9 @@ -74,7 +78,7 @@ jobs: poetry run jake ddt coding_standards: - executor: python39 + executor: python310 steps: - ensure_poetry_installed - checkout @@ -94,7 +98,7 @@ jobs: poetry run tox -eflake8 release_and_pypi_publish: - executor: python39 + executor: python310 steps: - add_ssh_keys: fingerprints: @@ -103,12 +107,11 @@ jobs: - run: name: "Install python-semantic-release" command: | - export pip install python-semantic-release - checkout - restore_cache: # ensure this step occurs *before* installing dependencies name: "Restore any valid cache" - key: dependencies-{{ .Branch }}-39-{{ checksum "poetry.lock" }} + key: dependencies-{{ .Branch }}-310-{{ checksum "poetry.lock" }} - run: name: "Build for release" command: | @@ -127,7 +130,7 @@ workflows: - build_and_test: matrix: parameters: - python_version: ["39", "38", "37", "36"] + python_version: ["310", "39", "38", "37", "36"] - coding_standards release: jobs: diff --git a/jake/__init__.py b/jake/__init__.py index e69de29..2d4347a 100644 --- a/jake/__init__.py +++ b/jake/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2019-Present Sonatype Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/poetry.lock b/poetry.lock index 9460b9e..5a3aa82 100644 --- a/poetry.lock +++ b/poetry.lock @@ -195,7 +195,7 @@ python-versions = "*" [[package]] name = "ossindex-lib" -version = "1.0.0rc1" +version = "1.0.0rc2" description = "A library for querying the OSS Index free catalogue of open source components to help developers identify vulnerabilities, understand risk, and keep their software safe." category = "main" optional = false @@ -636,8 +636,8 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] ossindex-lib = [ - {file = "ossindex-lib-1.0.0rc1.tar.gz", hash = "sha256:5d18bf333d964f6809b4777d6be4ec980f761b4657601900fc7432432cacb333"}, - {file = "ossindex_lib-1.0.0rc1-py3-none-any.whl", hash = "sha256:da47c955b2ab302d8de68f75df33a193cb6dbb8577d9ee2f99bf7e36b8acd722"}, + {file = "ossindex-lib-1.0.0rc2.tar.gz", hash = "sha256:cc4fa29ae9dd2fd393bcef9e4a54ca753bbf559f74d333e371186ab1225d0e4d"}, + {file = "ossindex_lib-1.0.0rc2-py3-none-any.whl", hash = "sha256:ace4b6e2e3ffc76f4e33d603fe7508867096f762bec879e27c41456b17e03bba"}, ] packageurl-python = [ {file = "packageurl-python-0.9.7.tar.gz", hash = "sha256:d43db6af6a350099e32e9e05e737013d0409287b761fa59c58a03e55dbc3648a"}, diff --git a/pyproject.toml b/pyproject.toml index bc684e7..e7235ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,8 @@ classifiers = [ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9' + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10' ] keywords = [ "BOM", "SBOM", "SCA", "OWASP" diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..2d4347a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright 2019-Present Sonatype Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# From 223978f94e43f69ee47a9db80e9542accc2646fe Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Mon, 14 Feb 2022 13:26:52 +0000 Subject: [PATCH 3/6] doc: first pass at docs for RTD (#111) Signed-off-by: Paul Horton --- .readthedocs.yaml | 47 +++++++ docs/Makefile | 20 +++ docs/changelog.rst | 17 +++ docs/conf.py | 66 +++++++++ docs/index.rst | 44 ++++++ docs/install.rst | 36 +++++ docs/make.bat | 35 +++++ docs/requirements.txt | 19 +++ docs/support.rst | 36 +++++ docs/usage.rst | 311 ++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 631 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 docs/Makefile create mode 100644 docs/changelog.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/install.rst create mode 100644 docs/make.bat create mode 100644 docs/requirements.txt create mode 100644 docs/support.rst create mode 100644 docs/usage.rst diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..7d47936 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,47 @@ +# encoding: utf-8 + +# +# Copyright 2019-Present Sonatype Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-20.04 + tools: + python: "3.9" + # You can also specify other tool versions: + # nodejs: "16" + # rust: "1.55" + # golang: "1.17" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Formats +formats: all + +# Optionally declare the Python requirements required to build your docs +python: + install: + - method: pip + path: . + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..9576d39 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,17 @@ +.. # + # Copyright 2019-Present Sonatype Inc. + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + # + +.. mdinclude:: ../CHANGELOG.md \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..0ec8a8e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,66 @@ +# encoding: utf-8 + +# +# Copyright 2019-Present Sonatype Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import pkg_resources + +# -- Project information ----------------------------------------------------- + +project = 'Jake' +copyright = '2019-Present Sonatype Inc.' +author = 'Paul Horton, Jeffry Hesse, Dan Rollo' + +# The full version, including alpha/beta/rc tags +release = pkg_resources.get_distribution("jake").version + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "autoapi.extension", + "sphinx_rtd_theme", + "m2r2" +] + +# Document Python Code +autoapi_type = 'python' +autoapi_dirs = ['../jake'] + +source_suffix = ['.rst', '.md'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..24a8c97 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,44 @@ +.. # + # Copyright 2019-Present Sonatype Inc. + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + # + +Jake's Documentation +==================================================== + +``jake`` is a tool to check for your Python environments and applications that can: + +1. produce CycloneDX software bill-of-materials +2. report on known vulnerabilities + +``jake`` is powered by `Sonatype OSS Index`_ and can also be used with `Sonatype's Nexus IQ Server`_. + +Internally, ``jake`` utilises libraries from `CycloneDX`_ when producing Software Bill of Materials. + +CycloneDX is a lightweight BOM specification that is easily created, human-readable, and simple to parse. + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + install + usage + support + changelog + + +.. _Sonatype OSS Index: https://ossindex.sonatype.org/ +.. _Sonatype's Nexus IQ Server: https://www.sonatype.com/products/open-source-security-dependency-management +.. _CycloneDX: https://cyclonedx.org/tool-center/ \ No newline at end of file diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..b91d030 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,36 @@ +.. # + # Copyright 2019-Present Sonatype Inc. + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + # + +Installation +==================================================== + +``jake`` can be installed from `PyPi`_ and also `Conda Forge`_. + +Example using ``pip``: + +.. code-block:: + + pip install jake + +Example using ``poetry``: + +.. code-block:: + + poetry add jake + + +.. _Conda Forge: https://anaconda.org/conda-forge/jake +.. _PyPi: https://pypi.org/project/jake \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..153be5e --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..e584322 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,19 @@ +# +# Copyright 2019-Present Sonatype Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +m2r2>=0.3.2 +Sphinx>=4.3.2 +sphinx-autoapi>=1.8.4 +sphinx-rtd-theme>=1.0.0 \ No newline at end of file diff --git a/docs/support.rst b/docs/support.rst new file mode 100644 index 0000000..04f314e --- /dev/null +++ b/docs/support.rst @@ -0,0 +1,36 @@ +.. # + # Copyright 2019-Present Sonatype Inc. + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + # + +Support +======= + +If you run into issues utilising this library, please raise a `GitHub Issue`_. When raising an issue please include as +much detail as possible including: + +* Version ``jake`` you have installed +* Input(s) +* Expected Output(s) +* Actual Output(s) + +Python Version Support +---------------------- + +We endeavour to support all functionality for all `current actively supported Python versions`_. +However, some features may not be possible/present in older Python versions due to their lack of support - which are +noted below. + +.. _GitHub Issue: https://github.com/sonatype-nexus-community/jake/issues +.. _current actively supported Python versions: https://www.python.org/downloads/ \ No newline at end of file diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..25bacc3 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,311 @@ +.. # + # Copyright 2019-Present Sonatype Inc. + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + # + +Usage +==================================================== + +Getting Started +--------------- + +`jake` can guide you... + +.. code-block:: + + > jake --help + usage: jake [-h] [-v] [-w] [-X] ... + + Put your Python dependencies in a chokehold + + optional arguments: + -h, --help show this help message and exit + -v, --version show which version of jake you are running + -w, --warn-only prevents exit with non-zero code when issues have been + detected + -X enable debug output + + Jake sub-commands: + + iq perform a scan backed by Nexus Lifecycle + ddt perform a scan backed by OSS Index + sbom generate a CycloneDX software-bill-of-materials (no + vulnerabilities) + + +``jake`` will exit with code ``0`` under normal operation and ``1`` if vulnerabilities are found (Oss Index) or Policy +Violations are detected (Nexus IQ), unless you pass the ``-w`` flag in which case ``jake`` will always exit with code +``0``. + +Generating an SBOM +------------------ + +``jake`` can take data from various inputs (or just look at your current Python environment) and produce a CycloneDX for +you. + +.. code-block:: + + > jake sbom --help + + usage: jake sbom [-h] [-i FILE_PATH] [-t TYPE] [-o PATH/TO/FILE] + [--output-format {json,xml}] + [--schema-version {1.0,1.1,1.2,1.3}] + + optional arguments: + -h, --help show this help message and exit + -i FILE_PATH, --input FILE_PATH + Where to get input data from. If a path to a file is + not specified directly here,then we will attempt to + read data from STDIN. If there is no data on STDIN, we + will then fall back to looking for standard files in + the current directory that relate to the type of input + indicated by the -t flag. + -t TYPE, --type TYPE, -it TYPE, --input-type TYPE + how jake should find the packages from which to + generate your SBOM.ENV = Read from the current Python + Environment; CONDA = Read output from `conda list + --explicit`; CONDA_JSON = Read output from `conda list + --json`; PIP = read from a requirements.txt; PIPENV = + read from Pipfile.lock; POETRY = read from a + poetry.lock. (Default = ENV) + -o PATH/TO/FILE, --output-file PATH/TO/FILE + Specify a file to output the SBOM to + --output-format {json,xml} + SBOM output format (default = xml) + --schema-version {1.0,1.1,1.2,1.3} + CycloneDX schema version to use (default = 1.3) + +Check out these examples using STDIN: + +.. code-block:: + + conda list --explicit --md5 | jake sbom -t CONDA + conda list --json | jake sbom -t CONDA_JSON + cat /path/to/Pipfile.lock | jake sbom -t PIPENV + + +Check out these examples specifying a manifest: + +.. code-block:: + + jake sbom -t PIP -i /path/to/requirements.txt + jake sbom -t PIPENV -i /path/to/Pipfile.lock + + +Check for vulnerabilities using OSS Index +----------------------------------------- + +``jake`` will look at the packaged installed in your current Python environment and check these against OSS Index for +you. + +.. code-block:: + + > jake ddt --help + + usage: jake ddt [-h] [--clear-cache] [-o PATH/TO/FILE] [--output-format {xml,json}] [--schema-version {1.2,1.1,1.0,1.3}] + + optional arguments: + -h, --help show this help message and exit + --clear-cache Clears any local cached OSS Index data prior to execution + -o PATH/TO/FILE, --output-file PATH/TO/FILE + Specify a file to output the SBOM to. If not specified the report will be output to the console. STDOUT is not supported. + --output-format {xml,json} + SBOM output format (default = xml) + --schema-version {1.2,1.1,1.0,1.3} + CycloneDX schema version to use (default = 1.3) + +So you can quickly get a report by running: + +.. code-block:: + + > jake ddt + + ___ ___ ___ + ___ / /\ / /\ / /\ + /__/\ / /::\ / /:/ / /::\ + \__\:\ / /:/\:\ / /:/ / /:/\:\ + ___ / /::\ / /::\ \:\ / /::\____ / /::\ \:\ + /__/\ /:/\/ /__/:/\:\_\:\ /__/:/\:::::\ /__/:/\:\ \:\ + \ \:\/:/~~ \__\/ \:\/:/ \__\/~|:|~~~~ \ \:\ \:\_\/ + \ \::/ \__\::/ | |:| \ \:\ \:\ + \__\/ / /:/ | |:| \ \:\_\/ + /__/:/ |__|:| \ \:\ + \__\/ \__\| \__\/ + + + /) /) + _/_(/ _ _ __ _ (/_ _ + o o (__/ )__(/_ /_)_/ (_(_(_/(___(/_ o o + + + + Jake Version: 1.1.0 + Put your Python dependencies in a chokehold. + + ๐Ÿ Collected 42 packages from your environment (0:00:00.10) + ๐Ÿ Successfully queried OSS Index for package and vulnerability info (0:00:00.59) + ๐Ÿ Sane number of results from OSS Index + + + โ•”Summaryโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•— + โ•‘ Audited Dependencies โ•‘ 42 โ•‘ + โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•ฃ + โ•‘ Vulnerablities Found โ•‘ 0 โ•‘ + โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ• + +This is what ``jake`` will output if any bad things are found: + +.. code-block:: + + ___ ___ ___ + ___ / /\ / /\ / /\ + /__/\ / /::\ / /:/ / /::\ + \__\:\ / /:/\:\ / /:/ / /:/\:\ + ___ / /::\ / /::\ \:\ / /::\____ / /::\ \:\ + /__/\ /:/\/ /__/:/\:\_\:\ /__/:/\:::::\ /__/:/\:\ \:\ + \ \:\/:/~~ \__\/ \:\/:/ \__\/~|:|~~~~ \ \:\ \:\_\/ + \ \::/ \__\::/ | |:| \ \:\ \:\ + \__\/ / /:/ | |:| \ \:\_\/ + /__/:/ |__|:| \ \:\ + \__\/ \__\| \__\/ + + + /) /) + _/_(/ _ _ __ _ (/_ _ + o o (__/ )__(/_ /_)_/ (_(_(_/(___(/_ o o + + + + Jake Version: 1.1.5 + Put your Python dependencies in a chokehold + + ๐Ÿ Collected 69 packages from your environment โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” 100% -:--:-- + ๐Ÿ Successfully queried OSS Index for package and vulnerability info โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” 100% -:--:-- + ๐Ÿ Sane number of results from OSS Index โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” 100% -:--:-- + + [59/69] - pkg:pypi/cryptography@2.2 [VULNERABLE] + Vulnerability Details for pkg:pypi/cryptography@2.2 + โ”œโ”€โ”€ โš  ID: 333aca51-7375-4a9d-be64-16d316ab9274 + โ”‚ โ””โ”€โ”€ โ•ญโ”€ CVE-2020-36242 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ + โ”‚ โ”‚ โ”‚ + โ”‚ โ”‚ In the cryptography package before 3.3.2 for Python, certain sequences of update calls to symmetrically encrypt multi-GB values could result in an integer overflow and buffer overflow, as demonstrated by the Fernet class. โ”‚ + โ”‚ โ”‚ โ”‚ + โ”‚ โ”‚ Details: โ”‚ + โ”‚ โ”‚ - CVSS Score: 9.1 - Critical โ”‚ + โ”‚ โ”‚ - CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H โ”‚ + โ”‚ โ”‚ - CWE: Unknown โ”‚ + โ”‚ โ”‚ โ”‚ + โ”‚ โ”‚ References: โ”‚ + โ”‚ โ”‚ - https://ossindex.sonatype.org/vulnerability/333aca51-7375-4a9d-be64-16d316ab9274?component-type=pypi&component-name=cryptography&utm_source=python-oss-index-lib%400.2.1&utm_medium=integration โ”‚ + โ”‚ โ”‚ - https://nvd.nist.gov/vuln/detail/CVE-2020-36242 โ”‚ + โ”‚ โ”‚ โ”‚ + โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + โ””โ”€โ”€ โš  ID: f19ff95c-cec5-4263-8d3b-e3e64698881e + โ””โ”€โ”€ โ•ญโ”€ CVE-2018-10903 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ + โ”‚ โ”‚ + โ”‚ A flaw was found in python-cryptography versions between >=1.9.0 and <2.3. The finalize_with_tag API did not enforce a minimum tag length. If a user did not validate the input length prior to passing it to finalize_with_tag an attacker could craft an invalid payload with a shortened tag (e.g. 1 โ”‚ + โ”‚ byte) such that they would have a 1 in 256 chance of passing the MAC check. GCM tag forgeries can cause key leakage. โ”‚ + โ”‚ โ”‚ + โ”‚ Details: โ”‚ + โ”‚ - CVSS Score: 7.5 - High โ”‚ + โ”‚ - CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N โ”‚ + โ”‚ - CWE: Unknown โ”‚ + โ”‚ โ”‚ + โ”‚ References: โ”‚ + โ”‚ - https://ossindex.sonatype.org/vulnerability/f19ff95c-cec5-4263-8d3b-e3e64698881e?component-type=pypi&component-name=cryptography&utm_source=python-oss-index-lib%400.2.1&utm_medium=integration โ”‚ + โ”‚ - https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2018-10903 โ”‚ + โ”‚ - https://github.com/pyca/cryptography/pull/4342/commits/688e0f673bfbf43fa898994326c6877f00ab19ef โ”‚ + โ”‚ - https://nvd.nist.gov/vuln/detail/CVE-2018-10903 โ”‚ + โ”‚ โ”‚ + โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + + Summary + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ + โ”ƒ Audited Dependencies โ”ƒ Vulnerabilities Found โ”ƒ + โ”กโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ + โ”‚ 69 โ”‚ 2 โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + +Pre-commit Hook +~~~~~~~~~~~~~~~ + +A pre-commit hook is also available for use: + +.. code-block:: yaml + + - repo: https://github.com/sonatype-nexus-community/jake + rev: "v1.3.0" + hooks: + - id: scan + +Check for vulnerabilities using Sonatype Nexus Lifecycle +-------------------------------------------------------- + +Access Sonatype's proprietary vulnerability data using ``jake``: + +.. code-block:: + + > jake iq --help + + usage: jake iq [-h] -s https://localhost:8070 -i APP_ID -u USER_ID -p PASSWORD [-t STAGE] + + optional arguments: + -h, --help show this help message and exit + -s https://localhost:8070, --server-url https://localhost:8070 + Full http(s):// URL to your Nexus Lifecycle server + -i APP_ID, --application-id APP_ID + Public Application ID in Nexus Lifecycle + -u USER_ID, --username USER_ID + Username for authentication to Nexus Lifecycle + -p PASSWORD, --password PASSWORD + Password for authentication to Nexus Lifecycle + -t STAGE, --stage STAGE + The stage for the report + +So passing parameters that suit your Nexus Lifecycle environment you can get a report: + +.. code-block:: + + > jake iq -s https://my-nexus-lifecyle -i APP_ID -u USERNAME -p PASSWORD + + ___ ___ ___ + ___ / /\ / /\ / /\ + /__/\ / /::\ / /:/ / /::\ + \__\:\ / /:/\:\ / /:/ / /:/\:\ + ___ / /::\ / /::\ \:\ / /::\____ / /::\ \:\ + /__/\ /:/\/ /__/:/\:\_\:\ /__/:/\:::::\ /__/:/\:\ \:\ + \ \:\/:/~~ \__\/ \:\/:/ \__\/~|:|~~~~ \ \:\ \:\_\/ + \ \::/ \__\::/ | |:| \ \:\ \:\ + \__\/ / /:/ | |:| \ \:\_\/ + /__/:/ |__|:| \ \:\ + \__\/ \__\| \__\/ + + + /) /) + _/_(/ _ _ __ _ (/_ _ + o o (__/ )__(/_ /_)_/ (_(_(_/(___(/_ o o + + + + Jake Version: 1.0.1 + Put your Python dependencies in a chokehold + + ๐Ÿ IQ Server at https://my-nexus-lifecyle is up and accessible (0:00:00.14) + ๐Ÿ Collected 42 packages from your environment (0:00:00.09) + ๐Ÿงจ Something slithers around your ankle! There are policy warnings from Sonatype Nexus IQ. (0:00:11.50) + + Your Sonatype Nexus IQ Lifecycle Report is available here: + HTML: https://my-nexus-lifecyle/ui/links/application/APP_ID/report/4831bcb7fbaa45c3a2481048e446b598 + PDF: https://my-nexus-lifecyle/ui/links/application/APP_ID/report/4831bcb7fbaa45c3a2481048e446b598/pdf From a0ab7eecac96ea9dd1324117651556493247eb98 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Mon, 21 Feb 2022 17:11:11 +0000 Subject: [PATCH 4/6] feat: typing as per PEP-561 and other refactors (#114) * feat: typing of `jake` WIP Signed-off-by: Paul Horton * port of fix for #112 Signed-off-by: Paul Horton * resolved a bunch of typing issues Signed-off-by: Paul Horton * ci: fixed parameter references Signed-off-by: Paul Horton * ci: fixed parameter references Signed-off-by: Paul Horton * ci: fixed mypy Signed-off-by: Paul Horton * fix: updated `ossindex-lib` to latest RC which now appears to properly resolve caching issues #100 Signed-off-by: Paul Horton * defined lowest dependencies and aligned Signed-off-by: Paul Horton * defined lowest dependencies and aligned Signed-off-by: Paul Horton * defined lowest dependencies and aligned Signed-off-by: Paul Horton * defined lowest dependencies and aligned Signed-off-by: Paul Horton * WIP: JSON output updated to use JSON serialisation - All tests passing locally Signed-off-by: Paul Horton --- .circleci/config.yml | 30 +++++- .mypy.ini | 51 +++++++++ jake/app.py | 110 +++++++++----------- jake/command/__init__.py | 37 +++++-- jake/command/config.py | 20 ++-- jake/command/iq.py | 102 +++++++++--------- jake/command/oss.py | 102 ++++++++++-------- jake/command/sbom.py | 120 +++++++++++----------- jake/py.typed | 2 + poetry.lock | 216 ++++++++++++++++++++++----------------- pyproject.toml | 18 ++-- requirements.lowest.txt | 23 +++++ tests/test_jake.py | 43 +++----- tox.ini | 47 +++++++-- 14 files changed, 537 insertions(+), 384 deletions(-) create mode 100644 .mypy.ini create mode 100644 jake/py.typed create mode 100644 requirements.lowest.txt diff --git a/.circleci/config.yml b/.circleci/config.yml index e388f14..302f988 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,13 +52,13 @@ jobs: - checkout - restore_cache: # ensure this step occurs *before* installing dependencies name: "Restore any valid cache" - key: dependencies-{{ .Branch }}-{{ parameters.python_version }}-{{ checksum "poetry.lock" }} + key: dependencies-{{ .Branch }}-<< parameters.python_version >>-{{ checksum "poetry.lock" }} - run: command: | poetry install - save_cache: name: "Cache dependencies" - key: dependencies-{{ .Branch }}-{{ parameters.python_version }}-{{ checksum "poetry.lock" }} + key: dependencies-{{ .Branch }}-<< parameters.python_version >>-{{ checksum "poetry.lock" }} paths: - /home/circleci/.cache/pypoetry/virtualenvs - run: @@ -84,13 +84,13 @@ jobs: - checkout - restore_cache: # ensure this step occurs *before* installing dependencies name: "Restore any valid cache" - key: dependencies-{{ .Branch }}-{{ parameters.python_version }}-{{ checksum "poetry.lock" }} + key: dependencies-{{ .Branch }}-310-{{ checksum "poetry.lock" }} - run: command: | poetry install - save_cache: name: "Cache dependencies" - key: dependencies-{{ .Branch }}-{{ parameters.python_version }}-{{ checksum "poetry.lock" }} + key: dependencies-{{ .Branch }}-310-{{ checksum "poetry.lock" }} paths: - /home/circleci/.cache/pypoetry/virtualenvs - run: @@ -124,6 +124,23 @@ jobs: git config user.email "$GITHUB_EMAIL" semantic-release publish + static_code_analysis: + parameters: + python_version: + type: string + toxenv_factor: + type: string + executor: python<< parameters.python_version >> + steps: + - checkout + - ensure_poetry_installed + - run: + command: | + poetry install --no-root + - run: + command: | + poetry run tox -e mypy-<< parameters.toxenv_factor >> + workflows: cicd: jobs: @@ -132,6 +149,11 @@ workflows: parameters: python_version: ["310", "39", "38", "37", "36"] - coding_standards + - static_code_analysis: + matrix: + parameters: + python_version: [ "310", "36" ] + toxenv_factor: [ "locked", "lowest" ] release: jobs: - manual_release: diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..e11b826 --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,51 @@ +# +# Copyright 2019-Present Sonatype Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +[mypy] + +files = jake/ + +show_error_codes = True +pretty = True + +warn_unreachable = True +allow_redefinition = False + +# ignore_missing_imports = False +# follow_imports = normal +# follow_imports_for_stubs = True + +### Strict mode ### +warn_unused_configs = True +disallow_subclassing_any = True +disallow_any_generics = True +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +no_implicit_optional = True +warn_redundant_casts = True +warn_return_any = True +no_implicit_reexport = True + +# needed to silence some py37|py38 differences +warn_unused_ignores = False + +[mypy-pytest.*] +ignore_missing_imports = True + +[mypy-tests.*] +disallow_untyped_decorators = False diff --git a/jake/app.py b/jake/app.py index a93eadf..5a084c3 100644 --- a/jake/app.py +++ b/jake/app.py @@ -16,112 +16,94 @@ # See the License for the specific language governing permissions and # limitations under the License. # - import argparse +from argparse import ArgumentParser from datetime import datetime from typing import Dict -from pyfiglet import figlet_format +from pyfiglet import figlet_format # type: ignore from rich.console import Console -from .command import BaseCommand, _jake_version +from .command import BaseCommand, jake_version from .command.iq import IqCommand from .command.oss import OssCommand from .command.sbom import SbomCommand +_SUB_COMMANDS: Dict[str, BaseCommand] = { + 'iq': IqCommand(), + 'ddt': OssCommand(), + 'sbom': SbomCommand() +} -class JakeCmd: - # Whether debug output is enabled - _DEBUG_ENABLED: bool = False - - # Argument Parser - _arg_parser: argparse.ArgumentParser - - # Parsed Arguments - _arguments: argparse.Namespace - # Sub Commands - _subcommands: Dict[str, BaseCommand] = [] - - # Rich Console - _console: Console +class JakeCmd: - def __init__(self): + def __init__(self, args: argparse.Namespace) -> None: + self._arguments = args self._console = Console() - # Build and parse command arguments - self._load_subcommands() - self._build_arg_parser() - self._parse_arguments() - if self._arguments.debug_enabled: self._DEBUG_ENABLED = True self._debug_message('!!! DEBUG MODE ENABLED !!!') self._debug_message('Parsed Arguments: {}'.format(self._arguments)) - def execute(self): + @staticmethod + def get_arg_parser() -> ArgumentParser: + arg_parser = ArgumentParser(description='Put your Python dependencies in a chokehold') + + # Add global options + arg_parser.add_argument('-v', '--version', help='show which version of jake you are running', + action='version', + version=f'jake {jake_version}') + arg_parser.add_argument('-w', '--warn-only', action='store_true', dest='warn_only', + help='prevents exit with non-zero code when issues have been detected') + arg_parser.add_argument('-X', action='store_true', help='enable debug output', dest='debug_enabled') + + subparsers = arg_parser.add_subparsers(title='Jake sub-commands', dest='cmd', metavar='') + for subcommand in _SUB_COMMANDS.keys(): + _SUB_COMMANDS[subcommand].setup_argument_parser( + arg_parser=subparsers.add_parser( + name=_SUB_COMMANDS[subcommand].get_argument_parser_name(), + help=_SUB_COMMANDS[subcommand].get_argument_parser_help() + ) + ) + + return arg_parser + + def execute(self) -> None: # Show the Jake header self._print_jake_header() # Determine primary command and then hand off to that Command handler - if self._arguments.cmd: - command = self._subcommands[self._arguments.cmd] + if self._arguments.cmd and self._arguments.cmd in _SUB_COMMANDS.keys(): + command = _SUB_COMMANDS[self._arguments.cmd] exit_code: int = command.execute(arguments=self._arguments) exit(exit_code) else: - self._arg_parser.print_help() - - def _load_subcommands(self): - self._subcommands = { - # 'config': ConfigCommand(), - 'iq': IqCommand(), - 'ddt': OssCommand(), - 'sbom': SbomCommand() - } - - def _build_arg_parser(self): - self._arg_parser = argparse.ArgumentParser(description='Put your Python dependencies in a chokehold') + JakeCmd.get_arg_parser().print_help() - # Add global options - self._arg_parser.add_argument('-v', '--version', help='show which version of jake you are running', - action='version', - version=f'jake {_jake_version}') - self._arg_parser.add_argument('-w', '--warn-only', action='store_true', dest='warn_only', - help='prevents exit with non-zero code when issues have been detected') - self._arg_parser.add_argument('-X', action='store_true', help='enable debug output', dest='debug_enabled') - - subparsers = self._arg_parser.add_subparsers(title='Jake sub-commands', dest='cmd', metavar='') - for subcommand in self._subcommands.keys(): - self._subcommands[subcommand].setup_argument_parser(subparsers=subparsers) - - def _debug_message(self, message: str): + def _debug_message(self, message: str) -> None: if self._DEBUG_ENABLED: print('[DEBUG] - {} - {}'.format(datetime.now(), message)) - def _print_jake_header(self): + def _print_jake_header(self) -> None: """ Prints the banner, most of the user facing commands start with this """ self._console.print(figlet_format('Jake', font='isometric4'), style='dark_green') self._console.print(figlet_format('..the snake..', font='invita'), style='dark_green') - print("Jake Version: {}".format(_jake_version)) + print("Jake Version: {}".format(jake_version)) print('Put your Python dependencies in a chokehold') print('') @staticmethod - def _error_and_exit(message: str, exit_code: int = 1): + def _error_and_exit(message: str, exit_code: int = 1) -> None: print('[ERROR] - {} - {}'.format(datetime.now(), message)) exit(exit_code) - def _parse_arguments(self): - self._arguments = self._arg_parser.parse_args() - - -# only for testing -def _create_cmd(): - return JakeCmd() - -def main(): - JakeCmd().execute() +def main() -> None: + parser = JakeCmd.get_arg_parser() + args = parser.parse_args() + JakeCmd(args).execute() if __name__ == "__main__": diff --git a/jake/command/__init__.py b/jake/command/__init__.py index 3b8e86e..6e5494c 100644 --- a/jake/command/__init__.py +++ b/jake/command/__init__.py @@ -15,10 +15,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -import argparse import sys from abc import ABC, abstractmethod +from argparse import ArgumentParser, Namespace from typing import Optional if sys.version_info >= (3, 8): @@ -26,24 +25,42 @@ else: from importlib_metadata import version as meta_version +jake_version: str = 'TBC' try: - _jake_version: Optional[str] = str(meta_version('jake')) # type: ignore[no-untyped-call] + jake_version = str(meta_version('jake')) # type: ignore[no-untyped-call] except Exception: - _jake_version = 'DEVELOPMENT' + jake_version = 'DEVELOPMENT' class BaseCommand(ABC): - # Parsed Arguments - _arguments: argparse.Namespace + + def __init__(self) -> None: + super().__init__() + self._arguments: Optional[Namespace] = None + + def execute(self, arguments: Namespace) -> int: + self._arguments = arguments + return self.handle_args() @abstractmethod def handle_args(self) -> int: pass - def execute(self, arguments: argparse.Namespace) -> int: - self._arguments = arguments - return self.handle_args() + @abstractmethod + def get_argument_parser_name(self) -> str: + pass @abstractmethod - def setup_argument_parser(self, subparsers: argparse._SubParsersAction): + def get_argument_parser_help(self) -> str: pass + + @abstractmethod + def setup_argument_parser(self, arg_parser: ArgumentParser) -> None: + pass + + @property + def arguments(self) -> Namespace: + if self._arguments: + return self._arguments + + raise ValueError('Arguments have not been set yet - execute() has to be called first') diff --git a/jake/command/config.py b/jake/command/config.py index 7e7c4ee..d65ec85 100644 --- a/jake/command/config.py +++ b/jake/command/config.py @@ -15,18 +15,22 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from argparse import ArgumentParser -import argparse from . import BaseCommand class ConfigCommand(BaseCommand): - def setup_argument_parser(self, subparsers: argparse._SubParsersAction): - parser_config: argparse.ArgumentParser = subparsers.add_parser( - 'config', - help='configure jake for OSS Index or Nexus Lifecycle access' - ) + def handle_args(self) -> int: + pass - parser_config.add_argument('oss', help='configure Nexus IQ Server or OSSIndex', nargs='?', - choices=('iq', 'oss')) + def get_argument_parser_name(self) -> str: + return 'config' + + def get_argument_parser_help(self) -> str: + return 'configure jake for OSS Index or Nexus Lifecycle access' + + def setup_argument_parser(self, arg_parser: ArgumentParser) -> None: + arg_parser.add_argument('oss', help='configure Nexus IQ Server or OSSIndex', nargs='?', + choices=('iq', 'oss')) diff --git a/jake/command/iq.py b/jake/command/iq.py index c1ecef4..8cc8742 100644 --- a/jake/command/iq.py +++ b/jake/command/iq.py @@ -15,21 +15,20 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -import argparse import logging -from typing import Union +from argparse import ArgumentParser +from typing import Any, Dict, Optional, Union from urllib.parse import urlparse import requests from cyclonedx.model.bom import Bom from cyclonedx.output import get_instance from cyclonedx_py.parser.environment import EnvironmentParser -from polling2 import poll_decorator +from polling2 import poll_decorator # type: ignore from requests.auth import HTTPBasicAuth from rich.progress import Progress -from . import BaseCommand, _jake_version +from . import BaseCommand, jake_version class IqCommand(BaseCommand): @@ -43,33 +42,28 @@ class IqServerApi: _logger: logging.Logger = logging.getLogger('jake.iq') - _server_url: str - _username: str - _password: str - _auth: HTTPBasicAuth - - _default_headers = { - 'User-Agent': 'jake/{}'.format(_jake_version) + _DEFAULT_HEADERS = { + 'User-Agent': 'jake/{}'.format(jake_version) } - _max_wait_in_seconds: int = 300 - _check_interval_seconds: int = 10 - - def __init__(self, server_url: str, username: str, password: str): - self._server_url = urlparse(server_url).geturl() - self._username = username - self._password = password + def __init__(self, server_url: str, username: str, password: str) -> None: + self._server_url: str = urlparse(server_url).geturl() + self._username: str = username + self._password: str = password if self._validate_server(): - self._auth = HTTPBasicAuth(username, password) + self._auth: Optional[HTTPBasicAuth] = HTTPBasicAuth(username, password) else: + self._auth = None self._logger.error( 'IQ server at {} does not appear accessible or in a ready-state to receive requests'.format( self._server_url ) ) - def scan_application_with_bom(self, bom: Bom, iq_public_application_id: str, iq_scan_stage: str): + def scan_application_with_bom(self, bom: Bom, iq_public_application_id: str, + iq_scan_stage: str) -> Any: + """ This method is intentionally blocking. @@ -105,7 +99,7 @@ def _get_internal_application_id_from_public_application_id(self, iq_public_appl raise ValueError('Response from IQ is missing the \'applications\' key. Cannot parse') if len(iq_response['applications']) == 1: - return iq_response['applications'][0]['id'] + return str(iq_response['applications'][0]['id']) else: message = 'There were {} matching Applications found in IQ for {}'.format( len(iq_response['applications']), iq_public_application_id @@ -113,8 +107,8 @@ def _get_internal_application_id_from_public_application_id(self, iq_public_appl self._logger.warning(message) raise ValueError(message) - @poll_decorator(step=10, timeout=300, log_error=logging.DEBUG) - def _get_scan_report_results(self, status_uri: str): + @poll_decorator(step=10, timeout=300, log_error=logging.DEBUG) # type: ignore + def _get_scan_report_results(self, status_uri: str) -> Union[Any, bool]: try: response = self.__make_request( uri='/{}'.format(status_uri) @@ -126,7 +120,7 @@ def _get_scan_report_results(self, status_uri: str): except ValueError: return False - def _submit_bom(self, bom: Bom, iq_internal_application_id: str, iq_scan_stage: str): + def _submit_bom(self, bom: Bom, iq_internal_application_id: str, iq_scan_stage: str) -> Any: self._logger.debug( 'Submitting BOM to IQ for Application {} at stage {}'.format(iq_internal_application_id, iq_scan_stage) ) @@ -149,9 +143,9 @@ def _validate_server(self) -> bool: )) return False - def __make_request(self, uri: str, body_data: object = None, additional_headers=None, - method: str = 'GET'): - if additional_headers is None: + def __make_request(self, uri: str, body_data: Optional[str] = None, + additional_headers: Optional[Dict[str, Any]] = None, method: str = 'GET') -> Any: + if not additional_headers: additional_headers = {} self._logger.debug('Beginning request to IQ {}'.format(uri)) response = requests.request( @@ -159,7 +153,7 @@ def __make_request(self, uri: str, body_data: object = None, additional_headers= url='{}{}'.format(self._server_url, uri), data=(body_data.encode('UTF-8') if body_data else None), auth=self._auth, - headers={**self._default_headers, **additional_headers} + headers={**self._DEFAULT_HEADERS, **additional_headers} ) if response.ok: @@ -169,11 +163,9 @@ def __make_request(self, uri: str, body_data: object = None, additional_headers= else: raise ValueError(response.text) - _iq_server: Union[IqServerApi, None] = None - - def __init__(self): + def __init__(self) -> None: super().__init__() - self._iq_server = None + self._iq_server: Union['IqCommand.IqServerApi', None] = None def handle_args(self) -> int: exit_code: int = 0 @@ -191,13 +183,13 @@ def handle_args(self) -> int: # task_validate_iq self._iq_server = self.IqServerApi( - server_url=self._arguments.iq_server_url, - username=self._arguments.iq_username, - password=self._arguments.iq_password + server_url=self.arguments.iq_server_url, + username=self.arguments.iq_username, + password=self.arguments.iq_password ) progress.update( task_validate_iq, completed=10, - description=f"๐Ÿ [green]IQ Server at {self._arguments.iq_server_url} is up and accessible" + description=f"๐Ÿ [green]IQ Server at {self.arguments.iq_server_url} is up and accessible" ) # task_parser @@ -212,8 +204,8 @@ def handle_args(self) -> int: progress.start_task(task_query_iq) iq_response = self._iq_server.scan_application_with_bom( bom=Bom.from_parser(parser=parser), - iq_public_application_id=self._arguments.iq_application_id, - iq_scan_stage=self._arguments.iq_scan_stage + iq_public_application_id=self.arguments.iq_application_id, + iq_scan_stage=self.arguments.iq_scan_stage ) if iq_response['policyAction'] == 'Failure': @@ -236,26 +228,30 @@ def handle_args(self) -> int: print('') print('Your Sonatype Nexus IQ Lifecycle Report is available here:') - print(' HTML: {}/{}'.format(self._arguments.iq_server_url, iq_response['reportHtmlUrl'])) - print(' PDF: {}/{}'.format(self._arguments.iq_server_url, iq_response['reportPdfUrl'])) + print(' HTML: {}/{}'.format(self.arguments.iq_server_url, iq_response['reportHtmlUrl'])) + print(' PDF: {}/{}'.format(self.arguments.iq_server_url, iq_response['reportPdfUrl'])) print('') return exit_code - def setup_argument_parser(self, subparsers: argparse._SubParsersAction): - parser: argparse.ArgumentParser = subparsers.add_parser('iq', help='perform a scan backed by Nexus Lifecycle') + def get_argument_parser_name(self) -> str: + return 'iq' + + def get_argument_parser_help(self) -> str: + return 'perform a scan backed by Sonatype Nexus Lifecycle' - parser.add_argument('-s', '--server-url', help='Full http(s):// URL to your Nexus Lifecycle server', - metavar='https://localhost:8070', required=True, dest='iq_server_url') + def setup_argument_parser(self, arg_parser: ArgumentParser) -> None: + arg_parser.add_argument('-s', '--server-url', help='Full http(s):// URL to your Nexus Lifecycle server', + metavar='https://localhost:8070', required=True, dest='iq_server_url') - parser.add_argument('-i', '--application-id', help='Public Application ID in Nexus Lifecycle', - metavar='APP_ID', required=True, dest='iq_application_id') + arg_parser.add_argument('-i', '--application-id', help='Public Application ID in Nexus Lifecycle', + metavar='APP_ID', required=True, dest='iq_application_id') - parser.add_argument('-u', '--username', help='Username for authentication to Nexus Lifecycle', - metavar='USER_ID', required=True, dest='iq_username') + arg_parser.add_argument('-u', '--username', help='Username for authentication to Nexus Lifecycle', + metavar='USER_ID', required=True, dest='iq_username') - parser.add_argument('-p', '--password', help='Password for authentication to Nexus Lifecycle', - metavar='PASSWORD', required=True, dest='iq_password') + arg_parser.add_argument('-p', '--password', help='Password for authentication to Nexus Lifecycle', + metavar='PASSWORD', required=True, dest='iq_password') - parser.add_argument('-t', '--stage', help='The stage for the report', - metavar='STAGE', required=False, dest='iq_scan_stage', default='source') + arg_parser.add_argument('-t', '--stage', help='The stage for the report', + metavar='STAGE', required=False, dest='iq_scan_stage', default='source') diff --git a/jake/command/oss.py b/jake/command/oss.py index c2ab0d7..a066506 100644 --- a/jake/command/oss.py +++ b/jake/command/oss.py @@ -15,11 +15,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -import argparse import os +from argparse import ArgumentParser from decimal import Decimal -from typing import List, Optional +from typing import cast, Iterable, List, Set from cyclonedx.model import XsUri from cyclonedx.model.bom import Bom @@ -27,10 +26,12 @@ from cyclonedx.model.impact_analysis import ImpactAnalysisAffectedStatus from cyclonedx.model.vulnerability import BomTarget, BomTargetVersionRange, Vulnerability, VulnerabilityAdvisory, \ VulnerabilityRating, VulnerabilityReference, VulnerabilityScoreSource, VulnerabilitySeverity, VulnerabilitySource -from cyclonedx.output import get_instance, OutputFormat, SchemaVersion +from cyclonedx.output import get_instance, OutputFormat, SchemaVersion, LATEST_SUPPORTED_SCHEMA_VERSION from cyclonedx_py.parser.environment import EnvironmentParser from ossindex.model import OssIndexComponent from ossindex.ossindex import OssIndex +# See https://github.com/package-url/packageurl-python/issues/65 +from packageurl import PackageURL # type: ignore from rich.console import Console from rich.panel import Panel from rich.progress import Progress @@ -68,7 +69,7 @@ def handle_args(self) -> int: oss_index_results: List[OssIndexComponent] oss = OssIndex() - if self._arguments.oss_clear_cache: + if self.arguments.oss_clear_cache: progress.update(task_query_ossi, completed=1, description='Clearing OSS Index local cache') oss.purge_local_cache() progress.update(task_query_ossi, completed=2, description='Cleared OSS Index local cache') @@ -76,7 +77,7 @@ def handle_args(self) -> int: progress.update(task_query_ossi, completed=3, description='Querying OSS Index for details on your packages') oss_index_results = oss.get_component_report( - packages=list(map(lambda c: c.purl if c.purl else None, parser.get_components())) + packages=list(map(lambda c: c.purl, filter(lambda c: c.purl, parser.get_components()))) ) progress.update( task_query_ossi, completed=10, @@ -101,9 +102,13 @@ def handle_args(self) -> int: components: List[Component] = [] for component in parser.get_components(): - oss_index_component: OssIndexComponent = list(filter( - lambda oic_: oic_.get_package_url().to_string() == component.purl.to_string(), oss_index_results - )).pop() + if component.purl: + oss_index_component: OssIndexComponent = list(filter( + lambda oic_: oic_.get_package_url().to_string() == cast(PackageURL, component.purl).to_string(), + oss_index_results + )).pop() + else: + continue if oss_index_component.vulnerabilities: for oic_vulnerability in oss_index_component.vulnerabilities: @@ -147,23 +152,21 @@ def handle_args(self) -> int: ] ) if oic_vulnerability.external_references: - advisories: List[VulnerabilityAdvisory] = [] + advisories: Set[VulnerabilityAdvisory] = set() for ext_ref_url in oic_vulnerability.external_references: - advisories.append( - VulnerabilityAdvisory(url=XsUri(uri=ext_ref_url)) - ) + advisories.add(VulnerabilityAdvisory(url=XsUri(uri=ext_ref_url))) vulnerability.advisories = advisories - vulnerability.affects = [ + vulnerability.affects.add( BomTarget( - ref=component.bom_ref, + ref=str(component.bom_ref), versions=[ BomTargetVersionRange( version=component.version, status=ImpactAnalysisAffectedStatus.AFFECTED ) ] ) - ] + ) component.add_vulnerability(vulnerability=vulnerability) @@ -173,21 +176,21 @@ def handle_args(self) -> int: print('') self._print_oss_index_report(components=components) - if self._arguments.oss_output_file: + if self.arguments.oss_output_file: cyclonedx_output = get_instance( bom=OssCommand._build_bom(components=components), - output_format=OutputFormat[str(self._arguments.oss_output_format).upper()], + output_format=OutputFormat[str(self.arguments.oss_output_format).upper()], schema_version=SchemaVersion['V{}'.format( - str(self._arguments.oss_schema_version).replace('.', '_') + str(self.arguments.oss_schema_version).replace('.', '_') )]) - output_filename = os.path.realpath(self._arguments.oss_output_file) + output_filename = os.path.realpath(self.arguments.oss_output_file) cyclonedx_output.output_to_file(filename=output_filename, allow_overwrite=True) print('') print('CycloneDX has been written to {}'.format(output_filename)) # Update exit_code if warn only is not enabled and issues have been detected - if not self._arguments.warn_only: + if not self.arguments.warn_only: for oic in oss_index_results: if oic.vulnerabilities: exit_code = 1 @@ -195,29 +198,37 @@ def handle_args(self) -> int: return exit_code - def setup_argument_parser(self, subparsers: argparse._SubParsersAction) -> None: - parser = subparsers.add_parser('ddt', help='perform a scan backed by OSS Index') - - parser.add_argument('--clear-cache', help='Clears any local cached OSS Index data prior to execution', - action='store_true', dest='oss_clear_cache', default=False) - - parser.add_argument('-o', '--output-file', help='Specify a file to output the SBOM to. If not specified the ' - 'report will be output to the console. ' - 'STDOUT is not supported.', - metavar='PATH/TO/FILE', dest='oss_output_file', default=None) - parser.add_argument('--output-format', help='SBOM output format (default = xml)', choices={'json', 'xml'}, - default='xml', dest='oss_output_format') - parser.add_argument('--schema-version', help='CycloneDX schema version to use (default = 1.3)', - choices={'1.4', '1.3', '1.2', '1.1', '1.0'}, default='1.3', - dest='oss_schema_version') + def get_argument_parser_name(self) -> str: + return 'ddt' + + def get_argument_parser_help(self) -> str: + return 'perform a scan backed by OSS Index' + + def setup_argument_parser(self, arg_parser: ArgumentParser) -> None: + arg_parser.add_argument('--clear-cache', help='Clears any local cached OSS Index data prior to execution', + action='store_true', dest='oss_clear_cache', default=False) + + arg_parser.add_argument('-o', '--output-file', + help='Specify a file to output the SBOM to. If not specified the ' + 'report will be output to the console. ' + 'STDOUT is not supported.', + metavar='PATH/TO/FILE', dest='oss_output_file', default=None) + arg_parser.add_argument('--output-format', help='SBOM output format (default = xml)', choices={'json', 'xml'}, + default='xml', dest='oss_output_format') + arg_parser.add_argument('--schema-version', + help=f'CycloneDX schema version to use (default = ' + f'{LATEST_SUPPORTED_SCHEMA_VERSION.to_version()})', + choices={'1.4', '1.3', '1.2', '1.1', '1.0'}, + default=f'{LATEST_SUPPORTED_SCHEMA_VERSION.to_version()})', + dest='oss_schema_version') @staticmethod - def _build_bom(components: List[Component]) -> Bom: + def _build_bom(components: Iterable[Component]) -> Bom: bom = Bom() - bom.components = components + bom.components = set(components) return bom - def _print_oss_index_report(self, components: List[Component]): + def _print_oss_index_report(self, components: List[Component]) -> None: total_vulnerabilities = 0 total_packages = len(components) @@ -261,12 +272,12 @@ def _print_oss_index_report(self, components: List[Component]): def _get_max_cvss_score_for_vulnerability(vulnerability: Vulnerability) -> float: max_score: float = 0.0 for rating in vulnerability.ratings: - if float(rating.score) > max_score: + if rating.score and float(rating.score) > max_score: max_score = float(rating.score) return max_score @staticmethod - def _get_max_cvss_score(component: Component) -> Optional[float]: + def _get_max_cvss_score(component: Component) -> float: max_cvss_score: float = 0.0 for v in component.get_vulnerabilities(): max_cvss_score = OssCommand._get_max_cvss_score_for_vulnerability(vulnerability=v) @@ -287,14 +298,15 @@ def _print_vulnerability(tree: Tree, v: Vulnerability) -> None: {v.detail} Ratings: -{os.linesep.join([f' - [{severity_color}]{rating.score:.1f} {rating.severity.name} - ' +{os.linesep.join([f' - [{severity_color}]{rating.score:.1f} {rating.severity.name if rating.severity else ""} - ' f'Vector: {rating.vector if rating.vector else "Unknown"}, ' f'CWEs: {",".join(list(map(lambda cwe: str(cwe), v.cwes))) if v.cwes else "None Recorded"}' f'[bright_white]' for rating in v.ratings])} References: -{os.linesep.join([f' - {reference.source.name if reference.source.name else ""} [Ref: {reference.id}]{os.linesep}' - f' URL: {reference.source.url if reference.source.url else "None"}' +{os.linesep.join([f' - {reference.source.name if reference.source and reference.source.name else ""} ' + f'[Ref: {reference.id}]{os.linesep}' + f' URL: {reference.source.url if reference.source and reference.source.url else "None"}' for reference in v.references])} """ @@ -314,7 +326,7 @@ def _get_color_for_cvss_score(cvss_score: float = 0.0) -> str: return 'bright_green' @staticmethod - def _get_severity_for_cvss_score(cvss_score: float = None) -> str: + def _get_severity_for_cvss_score(cvss_score: float) -> str: if cvss_score >= 9.0: return 'Critical' elif cvss_score >= 7.0: diff --git a/jake/command/sbom.py b/jake/command/sbom.py index 658889e..b941ec9 100644 --- a/jake/command/sbom.py +++ b/jake/command/sbom.py @@ -15,24 +15,22 @@ # See the License for the specific language governing permissions and # limitations under the License. # - -import argparse import sys +from argparse import ArgumentParser, FileType +import cyclonedx.parser from cyclonedx.model import ExternalReference, ExternalReferenceType, Tool, XsUri from cyclonedx.model.bom import Bom -from cyclonedx.output import BaseOutput, get_instance, OutputFormat, SchemaVersion, DEFAULT_SCHEMA_VERSION -from cyclonedx.parser import BaseParser +from cyclonedx.output import BaseOutput, get_instance, OutputFormat, SchemaVersion, LATEST_SUPPORTED_SCHEMA_VERSION from cyclonedx_py.parser.conda import CondaListJsonParser, CondaListExplicitParser from cyclonedx_py.parser.environment import EnvironmentParser from cyclonedx_py.parser.pipenv import PipEnvParser, PipEnvFileParser from cyclonedx_py.parser.poetry import PoetryParser, PoetryFileParser from cyclonedx_py.parser.requirements import RequirementsParser, RequirementsFileParser -from . import BaseCommand, _jake_version - +from . import BaseCommand, jake_version -ThisTool = Tool(vendor='Sonatype Nexus Community', name='jake', version=_jake_version or 'UNKNOWN') +ThisTool = Tool(vendor='Sonatype Nexus Community', name='jake', version=jake_version or 'UNKNOWN') ThisTool.external_references.update([ ExternalReference( reference_type=ExternalReferenceType.BUILD_SYSTEM, @@ -68,98 +66,102 @@ class SbomCommand(BaseCommand): def handle_args(self) -> int: - # self._arguments.sbom_input_type bom = Bom.from_parser(parser=self._get_parser()) bom.metadata.tools.add(ThisTool) output_format = OutputFormat.XML - if self._arguments.sbom_output_format == 'json': + if self.arguments.sbom_output_format == 'json': output_format = OutputFormat.JSON - schema_version = DEFAULT_SCHEMA_VERSION - if self._arguments.sbom_schema_version: - schema_version = SchemaVersion['V{}'.format(str(self._arguments.sbom_schema_version).replace('.', '_'))] + schema_version = LATEST_SUPPORTED_SCHEMA_VERSION + if self.arguments.sbom_schema_version: + schema_version = SchemaVersion['V{}'.format(str(self.arguments.sbom_schema_version).replace('.', '_'))] output: BaseOutput = get_instance(bom=bom, output_format=output_format, schema_version=schema_version) - if self._arguments.sbom_output_file: + if self.arguments.sbom_output_file: # Output to a file - output.output_to_file(filename=self._arguments.sbom_output_file, allow_overwrite=True) + output.output_to_file(filename=self.arguments.sbom_output_file, allow_overwrite=True) else: # Output to STDOUT print(output.output_as_string()) return 0 - def setup_argument_parser(self, subparsers: argparse._SubParsersAction): - parser: argparse.ArgumentParser = subparsers.add_parser( - 'sbom', - help='generate a CycloneDX software-bill-of-materials (no vulnerabilities)', - ) - - parser.add_argument('-i', '--input', action='store', metavar='FILE_PATH', - type=argparse.FileType('r'), default=(None if sys.stdin.isatty() else sys.stdin), - help='Where to get input data from. If a path to a file is not specified directly here,' - 'then we will attempt to read data from STDIN. If there is no data on STDIN, we will ' - 'then fall back to looking for standard files in the current directory that relate ' - 'to the type of input indicated by the -t flag.', dest='sbom_input_source', - required=False) - - parser.add_argument('-t', '--type', '-it', '--input-type', - help='how jake should find the packages from which to generate your SBOM.' - 'ENV = Read from the current Python Environment; ' - 'CONDA = Read output from `conda list --explicit`; ' - 'CONDA_JSON = Read output from `conda list --json`; ' - 'PIP = read from a requirements.txt; ' - 'PIPENV = read from Pipfile.lock; ' - 'POETRY = read from a poetry.lock. ' - '(Default = ENV)', - metavar='TYPE', choices={'CONDA', 'CONDA_JSON', 'ENV', 'PIP', 'PIPENV', 'POETRY'}, - default='ENV', dest='sbom_input_type') - - parser.add_argument('-o', '--output-file', help='Specify a file to output the SBOM to', metavar='PATH/TO/FILE', - dest='sbom_output_file') - parser.add_argument('--output-format', help='SBOM output format (default = xml)', choices={'json', 'xml'}, - default='xml', dest='sbom_output_format') - parser.add_argument('--schema-version', help='CycloneDX schema version to use (default = 1.3)', - choices={'1.4', '1.3', '1.2', '1.1', '1.0'}, default='1.3', - dest='sbom_schema_version') - - def _get_parser(self) -> BaseParser: - if self._arguments.sbom_input_type == 'ENV': + def get_argument_parser_name(self) -> str: + return 'sbom' + + def get_argument_parser_help(self) -> str: + return 'generate a CycloneDX software-bill-of-materials (no vulnerabilities)' + + def setup_argument_parser(self, arg_parser: ArgumentParser) -> None: + arg_parser.add_argument('-i', '--input', action='store', metavar='FILE_PATH', + type=FileType('r'), default=(None if sys.stdin.isatty() else sys.stdin), + help='Where to get input data from. If a path to a file is not specified directly here,' + 'then we will attempt to read data from STDIN. If there is no data on STDIN, we ' + 'will then fall back to looking for standard files in the current directory that ' + 'relate to the type of input indicated by the -t flag.', dest='sbom_input_source', + required=False) + + arg_parser.add_argument('-t', '--type', '-it', '--input-type', + help='how jake should find the packages from which to generate your SBOM.' + 'ENV = Read from the current Python Environment; ' + 'CONDA = Read output from `conda list --explicit`; ' + 'CONDA_JSON = Read output from `conda list --json`; ' + 'PIP = read from a requirements.txt; ' + 'PIPENV = read from Pipfile.lock; ' + 'POETRY = read from a poetry.lock. ' + '(Default = ENV)', + metavar='TYPE', choices={'CONDA', 'CONDA_JSON', 'ENV', 'PIP', 'PIPENV', 'POETRY'}, + default='ENV', dest='sbom_input_type') + + arg_parser.add_argument('-o', '--output-file', help='Specify a file to output the SBOM to', + metavar='PATH/TO/FILE', + dest='sbom_output_file') + arg_parser.add_argument('--output-format', help='SBOM output format (default = xml)', choices={'json', 'xml'}, + default='xml', dest='sbom_output_format') + arg_parser.add_argument('--schema-version', + help=f'CycloneDX schema version to use (default = ' + f'{LATEST_SUPPORTED_SCHEMA_VERSION.to_version()})', + choices={'1.4', '1.3', '1.2', '1.1', '1.0'}, + default=f'{LATEST_SUPPORTED_SCHEMA_VERSION.to_version()}', + dest='sbom_schema_version') + + def _get_parser(self) -> cyclonedx.parser.BaseParser: + if self.arguments.sbom_input_type == 'ENV': return EnvironmentParser() # All other input types require INPUT - let's grab it now if provided via STDIN or supplied FILE - input_data_fh = self._arguments.sbom_input_source + input_data_fh = self.arguments.sbom_input_source if input_data_fh: with input_data_fh: input_data = input_data_fh.read() input_data_fh.close() - if self._arguments.sbom_input_type == 'CONDA': + if self.arguments.sbom_input_type == 'CONDA': return CondaListExplicitParser(conda_data=input_data) - if self._arguments.sbom_input_type == 'CONDA_JSON': + if self.arguments.sbom_input_type == 'CONDA_JSON': return CondaListJsonParser(conda_data=input_data) - if self._arguments.sbom_input_type == 'PIP': + if self.arguments.sbom_input_type == 'PIP': return RequirementsParser(requirements_content=input_data) - if self._arguments.sbom_input_type == 'PIPENV': + if self.arguments.sbom_input_type == 'PIPENV': return PipEnvParser(pipenv_contents=input_data) - if self._arguments.sbom_input_type == 'POETRY': + if self.arguments.sbom_input_type == 'POETRY': return PoetryParser(poetry_lock_contents=input_data) else: # No data available on STDIN or the supplied FILE, so we'll try standard filenames in the current directory - if self._arguments.sbom_input_type == 'PIP': + if self.arguments.sbom_input_type == 'PIP': return RequirementsFileParser(requirements_file='requirements.txt') - if self._arguments.sbom_input_type == 'PIPENV': + if self.arguments.sbom_input_type == 'PIPENV': return PipEnvFileParser(pipenv_lock_filename='Pipfile.lock') - if self._arguments.sbom_input_type == 'POETRY': + if self.arguments.sbom_input_type == 'POETRY': return PoetryFileParser(poetry_lock_filename='poetry.lock') raise NotImplementedError diff --git a/jake/py.typed b/jake/py.typed new file mode 100644 index 0000000..1fd0ed8 --- /dev/null +++ b/jake/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. This package uses inline types. +# This file is needed to allow other packages to type-check their code against this package. diff --git a/poetry.lock b/poetry.lock index 5a3aa82..3f30c9a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,25 +1,3 @@ -[[package]] -name = "atomicwrites" -version = "1.4.0" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "attrs" -version = "21.4.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] - [[package]] name = "certifi" version = "2021.10.8" @@ -30,7 +8,7 @@ python-versions = "*" [[package]] name = "charset-normalizer" -version = "2.0.11" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -71,18 +49,18 @@ toml = ["tomli"] [[package]] name = "cyclonedx-bom" -version = "3.0.0rc0" +version = "3.0.0rc3" description = "CycloneDX Software Bill of Materials (SBOM) generation utility" category = "main" optional = false python-versions = ">=3.6,<4.0" [package.dependencies] -cyclonedx-python-lib = ">=2.0.0rc1,<3.0.0" +cyclonedx-python-lib = ">=2.0.0rc4,<3.0.0" [[package]] name = "cyclonedx-python-lib" -version = "2.0.0rc1" +version = "2.0.0rc4" description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files." category = "main" optional = false @@ -178,39 +156,57 @@ docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" category = "dev" optional = false python-versions = "*" [[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" +name = "mypy" +version = "0.931" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." category = "dev" optional = false python-versions = "*" [[package]] name = "ossindex-lib" -version = "1.0.0rc2" +version = "1.0.0rc5" description = "A library for querying the OSS Index free catalogue of open source components to help developers identify vulnerabilities, understand risk, and keep their software safe." category = "main" optional = false python-versions = ">=3.6,<4.0" [package.dependencies] -packageurl-python = ">=0.9.4,<0.10.0" -requests = ">=2.27.1,<3.0.0" -tinydb = ">=4.5.1,<5.0.0" -types-requests = ">=2.27.8,<3.0.0" +packageurl-python = ">=0.9.0,<0.10.0" +requests = ">=2.20.0,<3.0.0" +tinydb = ">=4.5.0,<5.0.0" +types-requests = ">=2.25.1,<3.0.0" types-setuptools = ">=57.0.0" [[package]] name = "packageurl-python" -version = "0.9.7" +version = "0.9.9" description = "A purl aka. Package URL parser and builder" category = "main" optional = false @@ -316,28 +312,6 @@ python-versions = ">=3.6" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] -[[package]] -name = "pytest" -version = "6.2.5" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - [[package]] name = "requests" version = "2.27.1" @@ -358,7 +332,7 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rich" -version = "11.2.0" +version = "10.16.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "main" optional = false @@ -401,6 +375,14 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "tox" version = "3.24.5" @@ -424,9 +406,17 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] +[[package]] +name = "typed-ast" +version = "1.5.2" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "types-requests" -version = "2.27.9" +version = "2.27.10" description = "Typing stubs for requests" category = "main" optional = false @@ -515,24 +505,16 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "3953052192b6b6ceead8f852e0570dc8770c2189adf32f225c07477e9e708599" +content-hash = "21cd9ebe7237f1631f8a8e95e9ef117b8fb1e4b58f2f34f4c63af286766dab6c" [metadata.files] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, -] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -592,12 +574,12 @@ coverage = [ {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] cyclonedx-bom = [ - {file = "cyclonedx-bom-3.0.0rc0.tar.gz", hash = "sha256:642096a6fb46618fa6a71f593963852293702eee6a0e523e704c7b7ce5eca441"}, - {file = "cyclonedx_bom-3.0.0rc0-py3-none-any.whl", hash = "sha256:e1267d8bf380ad70913dfb40295d0c2fb00d59e711419d8353634cfe4221e5f8"}, + {file = "cyclonedx-bom-3.0.0rc3.tar.gz", hash = "sha256:25a82d417cffc08996544fee82fa7f24c06fee69bb6a36eb86cbe5d4a1d0dd2e"}, + {file = "cyclonedx_bom-3.0.0rc3-py3-none-any.whl", hash = "sha256:50cdafd0b66634d94537faca01e38756b8d22a137be36c9e3092db4a44c2eebd"}, ] cyclonedx-python-lib = [ - {file = "cyclonedx-python-lib-2.0.0rc1.tar.gz", hash = "sha256:5f2badb8f73e88607f9c79fcf2bbb04e69a9dbab610236efbc8114651224e5f8"}, - {file = "cyclonedx_python_lib-2.0.0rc1-py3-none-any.whl", hash = "sha256:c32c9fab754161e19c599a11d23e396f64fa3f7a9541482197c735b9c69056e0"}, + {file = "cyclonedx-python-lib-2.0.0rc4.tar.gz", hash = "sha256:3db3af890c1fd507f6668d8211d2ca3070dbd41ed476c33a1aaccb2b8d88a8fb"}, + {file = "cyclonedx_python_lib-2.0.0rc4-py3-none-any.whl", hash = "sha256:2a2c749129441eca6d04634c85c92dba50e38b71973ed44794da2984d54832d4"}, ] dataclasses = [ {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, @@ -627,21 +609,43 @@ importlib-resources = [ {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, {file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, ] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +mypy = [ + {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, + {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, + {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, + {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, + {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, + {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, + {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, + {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, + {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, + {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, + {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, + {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, + {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, + {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, + {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, + {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, + {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, + {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, + {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, + {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] ossindex-lib = [ - {file = "ossindex-lib-1.0.0rc2.tar.gz", hash = "sha256:cc4fa29ae9dd2fd393bcef9e4a54ca753bbf559f74d333e371186ab1225d0e4d"}, - {file = "ossindex_lib-1.0.0rc2-py3-none-any.whl", hash = "sha256:ace4b6e2e3ffc76f4e33d603fe7508867096f762bec879e27c41456b17e03bba"}, + {file = "ossindex-lib-1.0.0rc5.tar.gz", hash = "sha256:ccc9511f52552aa904095b6366df5e104cb126df43518eea83b3fe8c598ea363"}, + {file = "ossindex_lib-1.0.0rc5-py3-none-any.whl", hash = "sha256:e859d25d6070c4fde18a0146a05adcb30133256287152c98b94a0ecf25b90608"}, ] packageurl-python = [ - {file = "packageurl-python-0.9.7.tar.gz", hash = "sha256:d43db6af6a350099e32e9e05e737013d0409287b761fa59c58a03e55dbc3648a"}, - {file = "packageurl_python-0.9.7-py3-none-any.whl", hash = "sha256:ba971c2f3c5c976574803467e97522ae5f542ade3dfec763192a52abb2d7ea8f"}, + {file = "packageurl-python-0.9.9.tar.gz", hash = "sha256:872a0434b9a448b3fa97571711f69dd2a3fb72345ad66c90b17d827afea82f09"}, + {file = "packageurl_python-0.9.9-py3-none-any.whl", hash = "sha256:07aa852d1c48b0e86e625f6a32d83f96427739806b269d0f8142788ee807114b"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, @@ -683,17 +687,13 @@ pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] -pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, -] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] rich = [ - {file = "rich-11.2.0-py3-none-any.whl", hash = "sha256:d5f49ad91fb343efcae45a2b2df04a9755e863e50413623ab8c9e74f05aee52b"}, - {file = "rich-11.2.0.tar.gz", hash = "sha256:1a6266a5738115017bb64a66c59c717e7aa047b3ae49a011ede4abdeffc6536e"}, + {file = "rich-10.16.2-py3-none-any.whl", hash = "sha256:c59d73bd804c90f747c8d7b1d023b88f2a9ac2454224a4aeaf959b21eeb42d03"}, + {file = "rich-10.16.2.tar.gz", hash = "sha256:720974689960e06c2efdb54327f8bf0cdbdf4eae4ad73b6c94213cad405c371b"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -707,13 +707,43 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tomli = [ + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, +] tox = [ {file = "tox-3.24.5-py2.py3-none-any.whl", hash = "sha256:be3362472a33094bce26727f5f771ca0facf6dafa217f65875314e9a6600c95c"}, {file = "tox-3.24.5.tar.gz", hash = "sha256:67e0e32c90e278251fea45b696d0fef3879089ccbe979b0c556d35d5a70e2993"}, ] +typed-ast = [ + {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, + {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, + {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, + {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, + {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, + {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, + {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, + {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, + {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, + {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, +] types-requests = [ - {file = "types-requests-2.27.9.tar.gz", hash = "sha256:7368974534d297939492efdfdab232930440b11e2203f6df1f0c40e3242a87ea"}, - {file = "types_requests-2.27.9-py3-none-any.whl", hash = "sha256:74070045418faf710f3154403d6a16c9e67db50e5119906ca6955f1658d20f7b"}, + {file = "types-requests-2.27.10.tar.gz", hash = "sha256:5dcb088fcaa778efeee6b7fc46967037e983fbfb9fec02594578bd33fd75e555"}, + {file = "types_requests-2.27.10-py3-none-any.whl", hash = "sha256:6cb4fb0bbcbc585c57eeee6ffe5a47638dc89706b8d290ec89a77213fc5bad1a"}, ] types-setuptools = [ {file = "types-setuptools-57.4.9.tar.gz", hash = "sha256:536ef74744f8e1e4be4fc719887f886e74e4cf3c792b4a06984320be4df450b5"}, diff --git a/pyproject.toml b/pyproject.toml index e7235ff..8783216 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,8 @@ classifiers = [ 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10' + 'Programming Language :: Python :: 3.10', + 'Typing :: Typed' ] keywords = [ "BOM", "SBOM", "SCA", "OWASP" @@ -42,18 +43,19 @@ jake = 'jake.app:main' [tool.poetry.dependencies] python = "^3.6.2" -cyclonedx-bom = "^3.0.0rc0" -ossindex-lib = "^1.0.0rc1" -polling2 = ">= 0.5.0" -pyfiglet = ">= 0.8.post1" -requests = "^>= 2.25.1" -rich = ">= 10.15.2" +cyclonedx-bom = "^3.0.0rc3" +importlib-metadata = { version = ">= 3.4", python = "< 3.8" } +ossindex-lib = "^1.0.0rc5" +polling2 = "^0.5.0" +pyfiglet = ">= 0.7.6, < 1.0.0" +requests = "^2.20.0" +rich = "^10.10.0" [tool.poetry.dev-dependencies] tox = "^3.24.3" coverage = "^6.2" flake8 = "^4.0.1" -pytest = "^6.2.5" +mypy = ">= 0.920, < 1.00" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/requirements.lowest.txt b/requirements.lowest.txt new file mode 100644 index 0000000..08fc46b --- /dev/null +++ b/requirements.lowest.txt @@ -0,0 +1,23 @@ +# +# Copyright 2019-Present Sonatype Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This file records then lowest supported versions for dependenices. +cyclonedx-bom == 3.0.0rc3 +importlib-metadata == 3.4.0 # ; python_version < '3.8' +ossindex-lib == 1.0.0rc5 +polling2 == 0.5.0 +pyfiglet == 0.7.6 +requests == 2.20.0 +rich == 10.10.0 \ No newline at end of file diff --git a/tests/test_jake.py b/tests/test_jake.py index af14f1f..de399a0 100644 --- a/tests/test_jake.py +++ b/tests/test_jake.py @@ -15,38 +15,23 @@ # See the License for the specific language governing permissions and # limitations under the License. # +import os +import subprocess +from unittest import TestCase -import sys -import unittest +from jake.command import jake_version -import pytest as pytest -from unittest.mock import patch -from jake import app +class TestJakeCmd(TestCase): + def test_jake_no_args(self) -> None: + output = subprocess.check_output('jake', shell=True) + self.assertTrue('show this help message and exit' in output.decode('utf-8')) -class TestStringMethods(unittest.TestCase): + def test_jake_help(self): + output = subprocess.check_output('jake -h', shell=True) + self.assertTrue('show this help message and exit' in output.decode('utf-8')) - def setUp(self): - self.cmd = app._create_cmd() - - def tearDown(self): - self.cmd = None - - def test_app_noargs(self): - self.cmd.execute() - - def test_app_bad_arg(self): - test_args = ["prog", "bad-arg-name"] - with patch.object(sys, 'argv', test_args): - with pytest.raises(SystemExit) as pytest_wrapped_e: - # force re-read of args - self.cmd.__init__() - self.cmd.execute() - - assert pytest_wrapped_e.type == SystemExit - assert pytest_wrapped_e.value.code == 2 - - -if __name__ == '__main__': - unittest.main() + def test_jake_version(self): + output = subprocess.check_output('jake -v', shell=True) + self.assertEqual(f'jake {jake_version}', output.decode('utf-8').rstrip(os.linesep)) diff --git a/tox.ini b/tox.ini index 1698797..2882629 100644 --- a/tox.ini +++ b/tox.ini @@ -13,27 +13,52 @@ # See the License for the specific language governing permissions and # limitations under the License. # - [tox] -basepython = python3.9 -envlist = flake8,py3.9,py3.8,py3.7,py3.6 +minversion = 3.10 +envlist = + flake8 + mypy-{locked,lowest} + py{310,39,38,37,36}-{locked,lowest} isolated_build = True +skip_missing_interpreters = True +usedevelop = False +download = False [testenv] +# settings in this category apply to all other testenv, if not overwritten +skip_install = True whitelist_externals = poetry -commands = - pip install poetry +deps = + poetry +commands_pre = + {envpython} --version poetry install -v - poetry run coverage run --source=jake -m unittest + lowest: poetry run pip install -U -r requirements.lowest.txt + poetry run pip freeze +commands = + poetry run coverage run --source=jake -m unittest discover -s tests -v +setenv = + PYTHONHASHSEED = 0 + +[testenv:mypy{,-locked,-lowest}] +commands = + # mypy config is in own file: `.mypy.ini` + !lowest: poetry run mypy + lowest: poetry run mypy --python-version=3.6 [testenv:flake8] -skip_install = True commands = - pip install poetry - poetry install -v poetry run flake8 jake/ tests/ [flake8] -ignore = E305 -exclude = .git,__pycache__ +exclude = + build,dist,__pycache__,.eggs,*_cache + .git,.tox,.venv,venv + _OLD,_TEST, + docs max-line-length = 120 +ignore = + E305 + # ignore `self`, `cls` markers of flake8-annotations>=2.0 + ANN101,ANN102 + From e437bb41ddbb84d7844f40b213878c67c071cc23 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Mon, 21 Feb 2022 17:14:26 +0000 Subject: [PATCH 5/6] chore: bump to latest `cyclonedx-python` BREAKING CHANGE: Notion of default schema version has been removed by upstream library and replaced with latest supported schema version Signed-off-by: Paul Horton --- poetry.lock | 22 +++++++++++----------- pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3f30c9a..8a678ba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -49,18 +49,18 @@ toml = ["tomli"] [[package]] name = "cyclonedx-bom" -version = "3.0.0rc3" +version = "3.0.0" description = "CycloneDX Software Bill of Materials (SBOM) generation utility" category = "main" optional = false python-versions = ">=3.6,<4.0" [package.dependencies] -cyclonedx-python-lib = ">=2.0.0rc4,<3.0.0" +cyclonedx-python-lib = ">=2.0.0,<3.0.0" [[package]] name = "cyclonedx-python-lib" -version = "2.0.0rc4" +version = "2.0.0" description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files." category = "main" optional = false @@ -358,7 +358,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tinydb" -version = "4.6.1" +version = "4.7.0" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" category = "main" optional = false @@ -505,7 +505,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "21cd9ebe7237f1631f8a8e95e9ef117b8fb1e4b58f2f34f4c63af286766dab6c" +content-hash = "1269321668547fe6c2d7a4d601e5a5aa8c8d3a3bf7e44543b8d912a879f1f0d4" [metadata.files] certifi = [ @@ -574,12 +574,12 @@ coverage = [ {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, ] cyclonedx-bom = [ - {file = "cyclonedx-bom-3.0.0rc3.tar.gz", hash = "sha256:25a82d417cffc08996544fee82fa7f24c06fee69bb6a36eb86cbe5d4a1d0dd2e"}, - {file = "cyclonedx_bom-3.0.0rc3-py3-none-any.whl", hash = "sha256:50cdafd0b66634d94537faca01e38756b8d22a137be36c9e3092db4a44c2eebd"}, + {file = "cyclonedx-bom-3.0.0.tar.gz", hash = "sha256:77f9adecc18f9809df9af4fcdb3c5abded6bae314d7693130c2e9056e4a034b2"}, + {file = "cyclonedx_bom-3.0.0-py3-none-any.whl", hash = "sha256:1861e00c1af73df0852541ef7acb5eb1d5fa127a6379d2a15c7124b7b8de9040"}, ] cyclonedx-python-lib = [ - {file = "cyclonedx-python-lib-2.0.0rc4.tar.gz", hash = "sha256:3db3af890c1fd507f6668d8211d2ca3070dbd41ed476c33a1aaccb2b8d88a8fb"}, - {file = "cyclonedx_python_lib-2.0.0rc4-py3-none-any.whl", hash = "sha256:2a2c749129441eca6d04634c85c92dba50e38b71973ed44794da2984d54832d4"}, + {file = "cyclonedx-python-lib-2.0.0.tar.gz", hash = "sha256:32d6f3372b69df5734373a698cce81ffeed65d882a1a9c48891914cb0658279e"}, + {file = "cyclonedx_python_lib-2.0.0-py3-none-any.whl", hash = "sha256:9c5da7c37b54649990029c375b27166f1355a8d3ba43c3cb5e83a4481da64d5b"}, ] dataclasses = [ {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, @@ -700,8 +700,8 @@ six = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] tinydb = [ - {file = "tinydb-4.6.1-py3-none-any.whl", hash = "sha256:ac1fdae2a7d5d7e2ca915d2666e685d63982991827c5dd097b6be4a3b5822dbc"}, - {file = "tinydb-4.6.1.tar.gz", hash = "sha256:0d5400f5e5ae368a84d57cb234456f1cf70430fd39bcd77ccd568fea91ff2a4e"}, + {file = "tinydb-4.7.0-py3-none-any.whl", hash = "sha256:e2cdf6e2dad49813e9b5fceb3c7943387309a8738125fbff0b58d248a033f7a9"}, + {file = "tinydb-4.7.0.tar.gz", hash = "sha256:357eb7383dee6915f17b00596ec6dd2a890f3117bf52be28a4c516aeee581100"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, diff --git a/pyproject.toml b/pyproject.toml index 8783216..532cf53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ jake = 'jake.app:main' [tool.poetry.dependencies] python = "^3.6.2" -cyclonedx-bom = "^3.0.0rc3" +cyclonedx-bom = "^3.0.0" importlib-metadata = { version = ">= 3.4", python = "< 3.8" } ossindex-lib = "^1.0.0rc5" polling2 = "^0.5.0" From dc03aa923cf2a8c48d0ad9e7e30cb188fb3a5a96 Mon Sep 17 00:00:00 2001 From: Paul Horton Date: Thu, 10 Mar 2022 13:26:41 +0000 Subject: [PATCH 6/6] fix: resolve historic oss index caching issues feat: support for oss index authentication Signed-off-by: Paul Horton --- poetry.lock | 86 +++++++++++++++++++++++++++++++++-------- pyproject.toml | 2 +- requirements.lowest.txt | 2 +- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8a678ba..a3f0a13 100644 --- a/poetry.lock +++ b/poetry.lock @@ -191,16 +191,19 @@ python-versions = "*" [[package]] name = "ossindex-lib" -version = "1.0.0rc5" +version = "1.0.0" description = "A library for querying the OSS Index free catalogue of open source components to help developers identify vulnerabilities, understand risk, and keep their software safe." category = "main" optional = false python-versions = ">=3.6,<4.0" [package.dependencies] +importlib-metadata = {version = ">=3.4", markers = "python_version < \"3.8\""} packageurl-python = ">=0.9.0,<0.10.0" +PyYAML = ">=5.4.1,<6.0.0" requests = ">=2.20.0,<3.0.0" tinydb = ">=4.5.0,<5.0.0" +types-PyYAML = ">=5.4.1,<6.0.0" types-requests = ">=2.25.1,<3.0.0" types-setuptools = ">=57.0.0" @@ -312,6 +315,14 @@ python-versions = ">=3.6" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + [[package]] name = "requests" version = "2.27.1" @@ -414,9 +425,17 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "types-pyyaml" +version = "5.4.12" +description = "Typing stubs for PyYAML" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "types-requests" -version = "2.27.10" +version = "2.27.11" description = "Typing stubs for requests" category = "main" optional = false @@ -427,7 +446,7 @@ types-urllib3 = "<1.27" [[package]] name = "types-setuptools" -version = "57.4.9" +version = "57.4.10" description = "Typing stubs for setuptools" category = "main" optional = false @@ -443,7 +462,7 @@ python-versions = "*" [[package]] name = "types-urllib3" -version = "1.26.9" +version = "1.26.10" description = "Typing stubs for urllib3" category = "main" optional = false @@ -472,7 +491,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.13.1" +version = "20.13.3" description = "Virtual Python Environment builder" category = "dev" optional = false @@ -505,7 +524,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "1269321668547fe6c2d7a4d601e5a5aa8c8d3a3bf7e44543b8d912a879f1f0d4" +content-hash = "4b2f8947179661c85e61afb0744f641cd69d8e48ccc3f13d4c75b8d2ae5ad942" [metadata.files] certifi = [ @@ -640,8 +659,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] ossindex-lib = [ - {file = "ossindex-lib-1.0.0rc5.tar.gz", hash = "sha256:ccc9511f52552aa904095b6366df5e104cb126df43518eea83b3fe8c598ea363"}, - {file = "ossindex_lib-1.0.0rc5-py3-none-any.whl", hash = "sha256:e859d25d6070c4fde18a0146a05adcb30133256287152c98b94a0ecf25b90608"}, + {file = "ossindex-lib-1.0.0.tar.gz", hash = "sha256:5a22fcd562b429084f68510519c9ad5503db528f6a88bc5cd2d48056f938550c"}, + {file = "ossindex_lib-1.0.0-py3-none-any.whl", hash = "sha256:0c6d84e932a4e284d78dc28ff6db7df8d02b9a1ecd69f06636a10e325fd932e1"}, ] packageurl-python = [ {file = "packageurl-python-0.9.9.tar.gz", hash = "sha256:872a0434b9a448b3fa97571711f69dd2a3fb72345ad66c90b17d827afea82f09"}, @@ -687,6 +706,37 @@ pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] +pyyaml = [ + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, +] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, @@ -741,21 +791,25 @@ typed-ast = [ {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, ] +types-pyyaml = [ + {file = "types-PyYAML-5.4.12.tar.gz", hash = "sha256:3f4daa754357491625ae8c3a39c9e1b0d7cd5632bc4e1c35e7a7f75a64aa124b"}, + {file = "types_PyYAML-5.4.12-py3-none-any.whl", hash = "sha256:e06083f85375a5678e4c19452ed6467ce2167b71db222313e1792cb8fc76859a"}, +] types-requests = [ - {file = "types-requests-2.27.10.tar.gz", hash = "sha256:5dcb088fcaa778efeee6b7fc46967037e983fbfb9fec02594578bd33fd75e555"}, - {file = "types_requests-2.27.10-py3-none-any.whl", hash = "sha256:6cb4fb0bbcbc585c57eeee6ffe5a47638dc89706b8d290ec89a77213fc5bad1a"}, + {file = "types-requests-2.27.11.tar.gz", hash = "sha256:6a7ed24b21780af4a5b5e24c310b2cd885fb612df5fd95584d03d87e5f2a195a"}, + {file = "types_requests-2.27.11-py3-none-any.whl", hash = "sha256:506279bad570c7b4b19ac1f22e50146538befbe0c133b2cea66a9b04a533a859"}, ] types-setuptools = [ - {file = "types-setuptools-57.4.9.tar.gz", hash = "sha256:536ef74744f8e1e4be4fc719887f886e74e4cf3c792b4a06984320be4df450b5"}, - {file = "types_setuptools-57.4.9-py3-none-any.whl", hash = "sha256:948dc6863373750e2cd0b223a84f1fb608414cde5e55cf38ea657b93aeb411d2"}, + {file = "types-setuptools-57.4.10.tar.gz", hash = "sha256:9a13513679c640f6616e2d9ab50d431c99ca8ae9848a97243f887c80fd5cf294"}, + {file = "types_setuptools-57.4.10-py3-none-any.whl", hash = "sha256:ddc98da82c12e1208012d65276641a132d3aadc78ecfff68fd3e17d85933a3c1"}, ] types-toml = [ {file = "types-toml-0.10.4.tar.gz", hash = "sha256:9340e7c1587715581bb13905b3af30b79fe68afaccfca377665d5e63b694129a"}, {file = "types_toml-0.10.4-py3-none-any.whl", hash = "sha256:4a9ffd47bbcec49c6fde6351a889b2c1bd3c0ef309fa0eed60dc28e58c8b9ea6"}, ] types-urllib3 = [ - {file = "types-urllib3-1.26.9.tar.gz", hash = "sha256:abd2d4857837482b1834b4817f0587678dcc531dbc9abe4cde4da28cef3f522c"}, - {file = "types_urllib3-1.26.9-py3-none-any.whl", hash = "sha256:4a54f6274ab1c80968115634a55fb9341a699492b95e32104a7c513db9fe02e9"}, + {file = "types-urllib3-1.26.10.tar.gz", hash = "sha256:a26898f530e6c3f43f25b907f2b884486868ffd56a9faa94cbf9b3eb6e165d6a"}, + {file = "types_urllib3-1.26.10-py3-none-any.whl", hash = "sha256:d755278d5ecd7a7a6479a190e54230f241f1a99c19b81518b756b19dc69e518c"}, ] typing-extensions = [ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, @@ -767,8 +821,8 @@ urllib3 = [ {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] virtualenv = [ - {file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"}, - {file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"}, + {file = "virtualenv-20.13.3-py2.py3-none-any.whl", hash = "sha256:dd448d1ded9f14d1a4bfa6bfc0c5b96ae3be3f2d6c6c159b23ddcfd701baa021"}, + {file = "virtualenv-20.13.3.tar.gz", hash = "sha256:e9dd1a1359d70137559034c0f5433b34caf504af2dc756367be86a5a32967134"}, ] zipp = [ {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, diff --git a/pyproject.toml b/pyproject.toml index 532cf53..a88b3be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ jake = 'jake.app:main' python = "^3.6.2" cyclonedx-bom = "^3.0.0" importlib-metadata = { version = ">= 3.4", python = "< 3.8" } -ossindex-lib = "^1.0.0rc5" +ossindex-lib = "^1.0.0" polling2 = "^0.5.0" pyfiglet = ">= 0.7.6, < 1.0.0" requests = "^2.20.0" diff --git a/requirements.lowest.txt b/requirements.lowest.txt index 08fc46b..da4ce1a 100644 --- a/requirements.lowest.txt +++ b/requirements.lowest.txt @@ -16,7 +16,7 @@ # This file records then lowest supported versions for dependenices. cyclonedx-bom == 3.0.0rc3 importlib-metadata == 3.4.0 # ; python_version < '3.8' -ossindex-lib == 1.0.0rc5 +ossindex-lib == 1.0.0 polling2 == 0.5.0 pyfiglet == 0.7.6 requests == 2.20.0