Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have more type support #53

Merged
merged 24 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8c15148
feat: have more type support
tngraf Dec 13, 2023
ba43c37
style: isort fixes
tngraf Dec 13, 2023
fac67ce
feat: even more type support
tngraf Dec 13, 2023
40117ca
feat: even more type support (2)
t-graf Dec 15, 2023
041ef17
Merge branch 'main' into have_more_type_support
tngraf Dec 16, 2023
62fb1f3
feta: all mypy issues fixed
tngraf Dec 17, 2023
26c7388
feat: use cli_support 2.0.0 and update stubs
tngraf Dec 25, 2023
a6e1947
chores: have correct TOML file syntax
tngraf Dec 25, 2023
6ca4ee3
feat: use sw360 1.4.0 and update stubs
tngraf Dec 25, 2023
a393aac
chores: add missing type hints
tngraf Dec 25, 2023
cf1aaae
style: isort fixes
tngraf Dec 26, 2023
47e22a0
feat: update dependencies and remove stubs that are no longer used
tngraf Dec 26, 2023
1e1f099
style: mypy updates
tngraf Dec 26, 2023
a307786
chores: ensure that we run all checks in the correct environment
tngraf Dec 26, 2023
338d757
chores: have an updated requirement.txt file
tngraf Dec 26, 2023
45c34c6
style: isort fix
tngraf Jan 28, 2024
4d2c25a
Merge branch 'main' into have_more_type_support
tngraf Jan 28, 2024
ab7f542
style: flake8 fixes
tngraf Jan 28, 2024
953ea91
feat: add typing information
tngraf Jan 28, 2024
3471d91
feat: release version 2.2.0.dev1
tngraf Jan 28, 2024
4b3decb
fix: fix type info for python < 3.10
tngraf Jan 28, 2024
6ef4e14
docs: update readme
tngraf Jan 28, 2024
67b6846
fix: fix type info for python < 3.10
tngraf Jan 28, 2024
546d971
fix: fix type info for python < 3.9
tngraf Jan 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ htmlcov/
coverage.xml

# preliminary
sw360/
cli/
xxsw360/
xxcli/
Releases/

# internal stuff
Expand Down
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

# CaPyCli - Clearing Automation Python Command Line Tool for SW360

## 2.2.0 (2024-01-28)

* `getdependencies javascript` can now handle package-lock.json files of version 3.
* `bom findsources` can do source URL discovery using sw360 lookup, perform extensive
GitLab deep search, and adapt search strategy based on diverse programming languages.
* Have type support.

## 2.1.0 (2023-12-16)

* Be more resilient about missing metadata in CycloneDX SBOMs.
Expand Down
7 changes: 6 additions & 1 deletion RunChecks.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ Write-Host "markdownlint ..."
npx -q markdownlint-cli *.md --disable MD041

Write-Host "isort ..."
isort .
poetry run isort .

Write-Host "mypy ..."
poetry run mypy .

Write-Host "Done."

# -----------------------------------
# -----------------------------------
14 changes: 8 additions & 6 deletions capycli/bom/bom_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from enum import Enum
from typing import Any

from sortedcontainers import SortedSet

import capycli.common.json_support
import capycli.common.script_base
from capycli import get_logger
Expand Down Expand Up @@ -58,23 +60,23 @@ def convert(self,
# default is CaPyCLI
outputformat = BomFormat.CAPYCLI

cdx_components = []
cdx_components: SortedSet
project = None
sbom = None
try:
if inputformat == BomFormat.TEXT:
cdx_components = PlainTextSupport.flatlist_to_cdx_components(inputfile)
cdx_components = SortedSet(PlainTextSupport.flatlist_to_cdx_components(inputfile))
print_text(f" {len(cdx_components)} components read from file {inputfile}")
elif inputformat == BomFormat.CSV:
cdx_components = CsvSupport.csv_to_cdx_components(inputfile)
cdx_components = SortedSet(CsvSupport.csv_to_cdx_components(inputfile))
print_text(f" {len(cdx_components)} components read from file {inputfile}")
elif (inputformat == BomFormat.CAPYCLI) or (inputformat == BomFormat.SBOM):
sbom = CaPyCliBom.read_sbom(inputfile)
cdx_components = sbom.components
project = sbom.metadata.component
print_text(f" {len(cdx_components)} components read from file {inputfile}")
elif inputformat == BomFormat.LEGACY:
cdx_components = LegacySupport.legacy_to_cdx_components(inputfile)
cdx_components = SortedSet(LegacySupport.legacy_to_cdx_components(inputfile))
print_text(f" {len(cdx_components)} components read from file {inputfile}")
elif inputformat == BomFormat.LEGACY_CX:
sbom = LegacyCx.read_sbom(inputfile)
Expand All @@ -89,7 +91,7 @@ def convert(self,

try:
if outputformat == BomFormat.TEXT:
PlainTextSupport.write_cdx_components_as_flatlist(cdx_components, outputfile)
PlainTextSupport.write_cdx_components_as_flatlist2(cdx_components, outputfile)
print_text(f" {len(cdx_components)} components written to file {outputfile}")
elif outputformat == BomFormat.CSV:
CsvSupport.write_cdx_components_as_csv(cdx_components, outputfile)
Expand Down Expand Up @@ -147,7 +149,7 @@ def display_help(self) -> None:
print(" -if INPUTFORMAT Specify input file format: capycli|sbom|text|csv|legacy|legacy-cx")
print(" -of OUTPUTFORMAT Specify output file format: capycli|text|csv|legacy|html")

def run(self, args):
def run(self, args: Any) -> None:
"""Main method()"""
print("\n" + capycli.APP_NAME + ", " + capycli.get_app_version() + " - Convert SBOM formats\n")

Expand Down
55 changes: 38 additions & 17 deletions capycli/bom/check_bom.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -------------------------------------------------------------------------------
# Copyright (c) 2019-23 Siemens
# Copyright (c) 2019-2024 Siemens
# All Rights Reserved.
# Author: thomas.graf@siemens.com
#
Expand All @@ -9,12 +9,13 @@
import logging
import os
import sys
from typing import Any, Dict, Optional

import requests
import sw360.sw360_api
from colorama import Fore, Style
from cyclonedx.model.bom import Bom
from cyclonedx.model.component import Component
from sw360 import SW360Error

import capycli.common.script_base
from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport
Expand All @@ -39,57 +40,73 @@ def _bom_has_items_without_id(self, bom: Bom) -> bool:

return False

def _find_by_id(self, component: Component) -> dict:
def _find_by_id(self, component: Component) -> Optional[Dict[str, Any]]:
if not self.client:
print_red(" No client!")
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)

sw360id = CycloneDxSupport.get_property_value(component, CycloneDxSupport.CDX_PROP_SW360ID)
version = component.version or ""
for step in range(3):
try:
release_details = self.client.get_release(sw360id)
return release_details
except sw360.sw360_api.SW360Error as swex:
if swex.response.status_code == requests.codes['not_found']:
except SW360Error as swex:
if swex.response is None:
print_red(" Unknown error: " + swex.message)
elif swex.response.status_code == requests.codes['not_found']:
print_yellow(
" Not found " + component.name +
", " + component.version + ", " + sw360id)
", " + version + ", " + sw360id)
break

# only report other errors if this is the third attempt
if step >= 2:
print(Fore.LIGHTRED_EX + " Error retrieving release data: ")
print(
" " + component.name + ", " + component.version +
" " + component.name + ", " + version +
", " + sw360id)
print(" Status Code: " + str(swex.response.status_code))
if swex.response:
print(" Status Code: " + str(swex.response.status_code))
if swex.message:
print(" Message: " + swex.message)
print(Style.RESET_ALL)

return None

def _find_by_name(self, component: Component) -> dict:
def _find_by_name(self, component: Component) -> Optional[Dict[str, Any]]:
if not self.client:
print_red(" No client!")
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)

version = component.version or ""
for step in range(3):
try:
releases = self.client.get_releases_by_name(component.name)
if not releases:
return None

for r in releases:
if r.get("version", "") == component.version:
if r.get("version", "") == version:
return r

return None
except sw360.sw360_api.SW360Error as swex:
if swex.response.status_code == requests.codes['not_found']:
except SW360Error as swex:
if swex.response is None:
print_red(" Unknown error: " + swex.message)
elif swex.response.status_code == requests.codes['not_found']:
print_yellow(
" Not found " + component.name +
", " + component.version)
", " + version)
break

# only report other errors if this is the third attempt
if step >= 2:
print(Fore.LIGHTRED_EX + " Error retrieving release data: ")
print(
" " + component.name + ", " + component.version)
print(" Status Code: " + str(swex.response.status_code))
" " + component.name + ", " + version)
if swex.response:
print(" Status Code: " + str(swex.response.status_code))
if swex.message:
print(" Message: " + swex.message)
print(Style.RESET_ALL)
Expand All @@ -99,6 +116,10 @@ def _find_by_name(self, component: Component) -> dict:
def check_releases(self, bom: Bom) -> int:
"""Checks for each release in the list whether it can be found on the specified
SW360 instance."""
if not self.client:
print_red(" No client!")
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)

found_count = 0
for component in bom.components:
release_details = None
Expand All @@ -116,7 +137,7 @@ def check_releases(self, bom: Bom) -> int:
found_count += 1
continue

if not id:
if not sw360id:
print_yellow(
" " + component.name +
", " + component.version +
Expand All @@ -125,7 +146,7 @@ def check_releases(self, bom: Bom) -> int:

return found_count

def run(self, args):
def run(self, args: Any) -> None:
"""Main method()"""
if args.debug:
global LOG
Expand Down
49 changes: 36 additions & 13 deletions capycli/bom/check_bom_item_status.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -------------------------------------------------------------------------------
# Copyright (c) 2019-23 Siemens
# Copyright (c) 2019-2024 Siemens
# All Rights Reserved.
# Author: thomas.graf@siemens.com
#
Expand All @@ -12,10 +12,10 @@
from typing import Any, Dict, Optional

import requests
import sw360.sw360_api
from colorama import Fore, Style
from cyclonedx.model.bom import Bom
from cyclonedx.model.component import Component
from sw360 import SW360Error

import capycli.common.script_base
from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport
Expand All @@ -39,20 +39,26 @@ def _bom_has_items_without_id(self, bom: Bom) -> bool:
return False

def _find_by_id(self, component: Component) -> Optional[Dict[str, Any]]:
if not self.client:
print_red(" No client!")
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)

sw360id = CycloneDxSupport.get_property_value(component, CycloneDxSupport.CDX_PROP_SW360ID)
version = component.version or ""
try:
release_details = self.client.get_release(sw360id)
return release_details
except sw360.sw360_api.SW360Error as swex:
if swex.response.status_code == requests.codes['not_found']:
except SW360Error as swex:
if swex.response is None:
print_red(" Unknown error: " + swex.message)
elif swex.response.status_code == requests.codes['not_found']:
print(
Fore.LIGHTYELLOW_EX + " Not found " + component.name +
", " + component.version + ", " +
sw360id + Style.RESET_ALL)
", " + version + ", " + sw360id + Style.RESET_ALL)
else:
print(Fore.LIGHTRED_EX + " Error retrieving release data: ")
print(
" " + str(component.name) + ", " + str(component.version) +
" " + component.name + ", " + version +
", " + sw360id)
print(" Status Code: " + str(swex.response.status_code))
if swex.message:
Expand All @@ -62,25 +68,32 @@ def _find_by_id(self, component: Component) -> Optional[Dict[str, Any]]:
return None

def _find_by_name(self, component: Component) -> Optional[Dict[str, Any]]:
if not self.client:
print_red(" No client!")
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)

version = component.version or ""
try:
releases = self.client.get_releases_by_name(component.name)
if not releases:
return None

for r in releases:
if r.get("version", "") == component.version:
if r.get("version", "") == version:
return self.client.get_release_by_url(r["_links"]["self"]["href"])

return None
except sw360.sw360_api.SW360Error as swex:
if swex.response.status_code == requests.codes['not_found']:
except SW360Error as swex:
if swex.response is None:
print_red(" Unknown error: " + swex.message)
elif swex.response.status_code == requests.codes['not_found']:
print(
Fore.LIGHTYELLOW_EX + " Not found " + component.name +
", " + component.version + ", " +
", " + version + ", " +
Style.RESET_ALL)
else:
print(Fore.LIGHTRED_EX + " Error retrieving release data: ")
print(" " + str(component.name) + ", " + str(component.version))
print(" " + str(component.name) + ", " + str(version))
print(" Status Code: " + str(swex.response.status_code))
if swex.message:
print(" Message: " + swex.message)
Expand All @@ -89,6 +102,10 @@ def _find_by_name(self, component: Component) -> Optional[Dict[str, Any]]:
return None

def show_bom_item_status(self, bom: Bom, all: bool = False) -> None:
if not self.client:
print_red(" No client!")
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)

for component in bom.components:
release = None
id = CycloneDxSupport.get_property_value(component, CycloneDxSupport.CDX_PROP_SW360ID)
Expand Down Expand Up @@ -117,13 +134,19 @@ def show_bom_item_status(self, bom: Bom, all: bool = False) -> None:
release["_links"]["sw360:component"]["href"]
)
)
if not comp_sw360:
print_red("Error accessing component")
continue

rel_list = comp_sw360["_embedded"]["sw360:releases"]
print(" " + component.name + ", " + component.version + " => ", end="", flush=True)
print("releases for component found = " + str(len(rel_list)))
for orel in rel_list:
href = orel["_links"]["self"]["href"]
rel = self.client.get_release_by_url(href)
if not rel:
print_red("Error accessing release " + href)
continue
cs = rel.get("clearingState", "(unkown clearing state)")
if cs == "APPROVED":
print(Fore.LIGHTGREEN_EX, end="", flush=True)
Expand All @@ -141,7 +164,7 @@ def show_bom_item_status(self, bom: Bom, all: bool = False) -> None:
" => --- no id ---")
continue

def run(self, args) -> None:
def run(self, args: Any) -> None:
"""Main method()"""
if args.debug:
global LOG
Expand Down
Loading
Loading