Skip to content

Commit 651b35f

Browse files
authored
Merge pull request #233 from CycloneDX/fix/issue-230-hang-with-no-rf-flag
Fix for hang when no `-rf` flag supplied with `-r` flag
2 parents 5587777 + bb7e30a commit 651b35f

File tree

2 files changed

+47
-28
lines changed

2 files changed

+47
-28
lines changed

cyclonedx_py/client.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,24 @@
2020

2121
import argparse
2222
import os
23-
import sys
2423
from datetime import datetime
2524

2625
from cyclonedx.model.bom import Bom
2726
from cyclonedx.output import BaseOutput, get_instance, OutputFormat, SchemaVersion
2827
from cyclonedx.parser import BaseParser
2928
from cyclonedx.parser.environment import EnvironmentParser
30-
from cyclonedx.parser.requirements import RequirementsParser, RequirementsFileParser
29+
from cyclonedx.parser.requirements import RequirementsFileParser
3130

3231

3332
class CycloneDxCmd:
3433
# Whether debug output is enabled
3534
_DEBUG_ENABLED: bool = False
3635

37-
# Argument Parser
38-
_arg_parser: argparse.ArgumentParser
39-
4036
# Parsed Arguments
4137
_arguments: argparse.Namespace
4238

43-
def __init__(self):
44-
# Build and parse command arguments
45-
self._build_arg_parser()
46-
self._parse_arguments()
39+
def __init__(self, args: argparse.Namespace):
40+
self._arguments = args
4741

4842
if self._arguments.debug_enabled:
4943
self._DEBUG_ENABLED = True
@@ -71,10 +65,11 @@ def execute(self):
7165
self._debug_message('Will be outputting SBOM to file at: {}'.format(output_filename))
7266
output.output_to_file(filename=output_filename, allow_overwrite=self._arguments.output_file_overwrite)
7367

74-
def _build_arg_parser(self):
75-
self._arg_parser = argparse.ArgumentParser(description='CycloneDX SBOM Generator')
68+
@staticmethod
69+
def get_arg_parser() -> argparse.ArgumentParser:
70+
arg_parser = argparse.ArgumentParser(description='CycloneDX SBOM Generator')
7671

77-
input_group = self._arg_parser.add_mutually_exclusive_group(required=True)
72+
input_group = arg_parser.add_mutually_exclusive_group(required=True)
7873
input_group.add_argument(
7974
'-e', '--e', '--environment', action='store_true',
8075
help='Build a SBOM based on the packages installed in your current Python environment (default)',
@@ -86,16 +81,16 @@ def _build_arg_parser(self):
8681
dest='input_from_requirements'
8782
)
8883

89-
req_input_group = self._arg_parser.add_argument_group(
84+
req_input_group = arg_parser.add_argument_group(
9085
title='Requirements',
9186
description='Additional optional arguments if you are setting the input type to `requirements`.'
9287
)
9388
req_input_group.add_argument(
94-
'-rf', '--rf', '--requirements-file', action='store', metavar='FILE_PATH',
89+
'-rf', '--rf', '--requirements-file', action='store', metavar='FILE_PATH', default='requirements.txt',
9590
help='Path to a the requirements.txt file you wish to parse', dest='input_requirements_file', required=False
9691
)
9792

98-
output_group = self._arg_parser.add_argument_group(
93+
output_group = arg_parser.add_argument_group(
9994
title='SBOM Output Configuration',
10095
description='Choose the output format and schema version'
10196
)
@@ -118,7 +113,9 @@ def _build_arg_parser(self):
118113
help='If outputting to a file and the stated file already exists, it will be overwritten.'
119114
)
120115

121-
self._arg_parser.add_argument('-X', action='store_true', help='Enable debug output', dest='debug_enabled')
116+
arg_parser.add_argument('-X', action='store_true', help='Enable debug output', dest='debug_enabled')
117+
118+
return arg_parser
122119

123120
def _debug_message(self, message: str):
124121
if self._DEBUG_ENABLED:
@@ -133,23 +130,28 @@ def _get_input_parser(self) -> BaseParser:
133130
if self._arguments.input_from_environment:
134131
return EnvironmentParser()
135132
elif self._arguments.input_from_requirements:
136-
if self._arguments.input_requirements_file:
137-
if os.path.exists(self._arguments.input_requirements_file):
138-
# A requirements.txt path was provided
139-
return RequirementsFileParser(requirements_file=self._arguments.input_requirements_file)
140-
else:
141-
self._error_and_exit('The requirements.txt file path provided does not exist ({})'.format(
142-
self._arguments.input_requirements_file
143-
))
133+
# if self._arguments.input_requirements_file:
134+
requirements_file = os.path.realpath(self._arguments.input_requirements_file)
135+
# requirements_file = self._arguments.input_requirements_file
136+
if CycloneDxCmd._validate_requirements_file(self._arguments.input_requirements_file):
137+
# A requirements.txt path was provided
138+
return RequirementsFileParser(requirements_file=requirements_file)
144139
else:
145-
return RequirementsParser(requirements_content=sys.stdin.readlines())
140+
self._error_and_exit('The requirements.txt file path provided does not exist ({})'.format(
141+
requirements_file
142+
))
143+
else:
144+
raise ValueError('Parser type could not be determined.')
146145

147-
def _parse_arguments(self):
148-
self._arguments = self._arg_parser.parse_args()
146+
@staticmethod
147+
def _validate_requirements_file(requirements_file_path: str) -> bool:
148+
return os.path.exists(requirements_file_path)
149149

150150

151151
def main():
152-
CycloneDxCmd().execute()
152+
parser = CycloneDxCmd.get_arg_parser()
153+
args = parser.parse_args()
154+
CycloneDxCmd(args).execute()
153155

154156

155157
if __name__ == "__main__":

tests/test_cyclonedx.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import os.path
2121
import subprocess
2222
import tempfile
23+
from unittest.mock import mock_open, patch
24+
25+
from cyclonedx_py.client import CycloneDxCmd
2326

2427
from base import BaseXmlTestCase
2528

@@ -30,6 +33,20 @@
3033

3134
class TestCycloneDxXml(BaseXmlTestCase):
3235

36+
def test_run(self):
37+
parser = CycloneDxCmd.get_arg_parser()
38+
with tempfile.TemporaryDirectory() as dirname:
39+
args = parser.parse_args(args=('-r', '-o', os.path.join(dirname, 'sbom.xml')))
40+
with open(os.path.join(FIXTURES_DIRECTORY, 'requirements-simple.txt'), 'r') as req_file:
41+
with patch('builtins.open', mock_open(read_data=req_file.read())) as mock_req_file:
42+
with patch('cyclonedx_py.client.CycloneDxCmd._validate_requirements_file') as mock_exists:
43+
CycloneDxCmd(args).execute()
44+
45+
mock_exists.assert_called_with('requirements.txt')
46+
mock_req_file.assert_called()
47+
48+
req_file.close()
49+
3350
def test_requirements_txt_file(self):
3451
with tempfile.TemporaryDirectory() as dirname:
3552
# Run command to generate latest 1.3 XML SBOM from Requirements File

0 commit comments

Comments
 (0)