diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31ae0cd..cf277c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ on: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/CHANGES b/CHANGES index 9030fe0..6a00819 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +1.12.0 +----- +- Add command aliases with dashes (e.g., analyze-by-list) while maintaining backward compatibility with underscore versions + 1.11.2 ----- - Upgrade intezer-SDK to 1.21.4 diff --git a/README.md b/README.md index d02e4b5..6cdffe0 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ For complete documentation please run `intezer-analyze analyze --help` Send a text file with list of hashes ### Usage -`intezer-analyze analyze_by_list PATH` +`intezer-analyze analyze-by-list PATH` ### Parameters PATH: Path to txt file. @@ -57,9 +57,9 @@ PATH: Path to txt file. ### Example Send txt file with hashes for analysis: - $ intezer-analyze analyze_by_list ~/files/hashes.txt + $ intezer-analyze analyze-by-list ~/files/hashes.txt -For complete documentation please run `intezer-analyze analyze_by_list --help` +For complete documentation please run `intezer-analyze analyze-by-list --help` ## Index Send a file or a directory for indexing @@ -89,7 +89,7 @@ For complete documentation please run `intezer-analyze index --help` Send a text file with list of hashes to index ### Usage -`intezer-analyze index_by_list PATH --index-as=INDEX [FAMILY_NAME]` +`intezer-analyze index-by-list PATH --index-as=INDEX [FAMILY_NAME]` ### Parameters PATH: Path to txt file @@ -101,15 +101,15 @@ FAMILY_NAME: The family name (optional) ### Example Send a file with hashes and verdict for indexing: - $ intezer-analyze index_by_list ~/files/hashes.txt --index-as=malicious family_name + $ intezer-analyze index-by-list ~/files/hashes.txt --index-as=malicious family_name -For complete documentation please run `intezer-analyze index --help` +For complete documentation please run `intezer-analyze index-by-list --help` ## Upload offline endpoint scan Upload an offline scan created by running the Intezer Endpoint Scanner with '-o' flag ### Usage -`intezer-analyze upload_endpoint_scan OFFLINE_SCAN_DIRECTORY` +`intezer-analyze upload-endpoint-scan OFFLINE_SCAN_DIRECTORY` ### Parameters OFFLINE_SCAN_DIRECTORY: Path to directory with offline endpoint scan results @@ -117,15 +117,15 @@ OFFLINE_SCAN_DIRECTORY: Path to directory with offline endpoint scan results ### Examples: Upload a directory with offline endpoint scan results: - $ intezer-analyze upload_endpoint_scan /home/user/offline_scans/scan_MYPC_2019-01-01_00-00-00 + $ intezer-analyze upload-endpoint-scan /home/user/offline_scans/scan_MYPC_2019-01-01_00-00-00 -For complete documentation plrase run `intezer-analyze upload_endpoint_scan --help` +For complete documentation please run `intezer-analyze upload-endpoint-scan --help` ## Upload multiple offline endpoint scans Upload multiple offline scans created by running the Intezer Endpoint Scanner with '-o' flag ### Usage -`intezer-analyze upload_endpoint_scans_in_directory OFFLINE_SCANS_ROOT_DIRECTORY` +`intezer-analyze upload-endpoint-scans-in-directory OFFLINE_SCANS_ROOT_DIRECTORY` ### Parameters OFFLINE_SCANS_ROOT_DIRECTORY: Path to root directory containing offline endpoint scan results @@ -133,18 +133,18 @@ OFFLINE_SCANS_ROOT_DIRECTORY: Path to root directory containing offline endpoint ### Examples: Upload a directory with offline endpoint scan results: - $ intezer-analyze upload_endpoint_scans /home/user/offline_scans + $ intezer-analyze upload-endpoint-scans-in-directory /home/user/offline_scans -For complete documentation please run `intezer-analyze upload_endpoint_scans_in_directory --help` +For complete documentation please run `intezer-analyze upload-endpoint-scans-in-directory --help` ## Upload all subdirectories with .eml files to analyze Upload a directory with .eml files ### Parameter -UPLOAD_EMAILS_IN_DIRECTORY: Path to root directory containing the .eml fiels +UPLOAD_EMAILS_IN_DIRECTORY: Path to root directory containing the .eml files ### Examples: - $ intezer-analyze upload_emails_in_directory /path/to/emails_root_directory + $ intezer-analyze upload-emails-in-directory /path/to/emails_root_directory # Troubleshooting The cli produce a log file named `intezer-analyze-cli.log` in the current working directory. diff --git a/intezer_analyze_cli/__init__.py b/intezer_analyze_cli/__init__.py index b9e1578..666b2f7 100644 --- a/intezer_analyze_cli/__init__.py +++ b/intezer_analyze_cli/__init__.py @@ -1 +1 @@ -__version__ = '1.11.3' +__version__ = '1.12.0' diff --git a/intezer_analyze_cli/cli.py b/intezer_analyze_cli/cli.py index 98bd80d..99c5103 100644 --- a/intezer_analyze_cli/cli.py +++ b/intezer_analyze_cli/cli.py @@ -17,6 +17,25 @@ logger = logging.getLogger('intezer_cli') +class AliasedGroup(click.Group): + def get_command(self, ctx, cmd_name): + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + matches = [x for x in self.list_commands(ctx) + if x.replace('-', '_') == cmd_name.replace('-', '_')] + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + ctx.fail(f"Too many matches: {', '.join(sorted(matches))}") + + def resolve_command(self, ctx, args): + # always return the full command name + _, cmd, args = super().resolve_command(ctx, args) + return cmd.name, cmd, args + + def create_global_api(): try: api_key = key_store.get_stored_api_key() @@ -41,7 +60,7 @@ def create_global_api(): raise click.Abort() -@click.group(context_settings=dict(help_option_names=['-h', '--help'], max_content_width=120), +@click.group(cls=AliasedGroup, context_settings=dict(help_option_names=['-h', '--help'], max_content_width=120), help=f'Intezer Labs Ltd. Intezer Analyze CLI {__version__}') def main_cli(): pass @@ -135,7 +154,7 @@ def analyze(path: str, f'and attach the log file in {utilities.log_file_path}') -@main_cli.command('analyze_by_list', short_help='Send a text file with list of hashes') +@main_cli.command('analyze-by-list', short_help='Send a text file with list of hashes') @click.argument('path', type=click.Path(exists=True, dir_okay=False)) def analyze_by_list(path): """ Send a text file with hashes for analysis in Intezer Analyze. @@ -146,7 +165,7 @@ def analyze_by_list(path): \b Examples: Send txt file with hashes for analysis: - $ intezer-analyze analyze_by_list ~/files/hashes.txt + $ intezer-analyze analyze-by-list ~/files/hashes.txt """ try: create_global_api() @@ -159,8 +178,7 @@ def analyze_by_list(path): click.echo('Unexpected error occurred, please contact us at support@intezer.com ' f'and attach the log file in {utilities.log_file_path}') - -@main_cli.command('index_by_list', short_help='Send a text file with list of hashes, verdict, family name if malicious') +@main_cli.command('index-by-list', short_help='Send a text file with list of hashes, verdict, family name if malicious') @click.argument('path', type=click.Path(exists=True, dir_okay=False)) @click.option('--index-as', type=click.Choice(['malicious', 'trusted'], case_sensitive=True)) @click.argument('family_name', required=False, type=click.STRING, default=None) @@ -173,7 +191,7 @@ def index_by_list(path: str, index_as: str, family_name: str): \b Examples: - $ intezer-analyze index_by_list ~/files/hashes.txt malicious family_name + $ intezer-analyze index-by-list ~/files/hashes.txt malicious family_name \b """ try: @@ -193,7 +211,6 @@ def index_by_list(path: str, index_as: str, family_name: str): click.echo('Unexpected error occurred, please contact us at support@intezer.com ' f'and attach the log file in {utilities.log_file_path}') - @main_cli.command('index', short_help='index a file or a directory') @click.argument('path', type=click.Path(exists=True)) @click.option('--index-as', type=click.Choice(['malicious', 'trusted'], case_sensitive=True)) @@ -239,7 +256,7 @@ def index(path: str, index_as: str, family_name: str, ignore_directory_count_lim f'and attach the log file in {utilities.log_file_path}') -@main_cli.command('upload_endpoint_scan', short_help='upload a directory with offline endpoint scan results') +@main_cli.command('upload-endpoint-scan', short_help='upload a directory with offline endpoint scan results') @click.argument('offline_scan_directory', type=click.Path(exists=True)) @click.option('--force', is_flag=True, default=False, help='Upload scan even if it was already uploaded') @click.option('--max-concurrent', default=0, type=int, help='Maximum number of concurrent uploads.') @@ -253,7 +270,7 @@ def upload_endpoint_scan(offline_scan_directory: str, force: bool, max_concurren Examples: upload a directory with offline endpoint scan results: - $ intezer-analyze upload_endpoint_scan /path/to/endpoint_scan_results + $ intezer-analyze upload-endpoint-scan /path/to/endpoint_scan_results """ try: create_global_api() @@ -267,9 +284,8 @@ def upload_endpoint_scan(offline_scan_directory: str, force: bool, max_concurren click.echo('Unexpected error occurred, please contact us at support@intezer.com ' f'and attach the log file in {utilities.log_file_path}') - -@main_cli.command('upload_endpoint_scans_in_directory', - short_help='upload all subdirectories with offline endpoint scan results') +@main_cli.command('upload-endpoint-scans-in-directory', + short_help='upload all subdirectories with offline endpoint scan results') @click.argument('offline_scans_root_directory', type=click.Path(exists=True)) @click.option('--force', is_flag=True, default=False, help='Upload scans even if they were already uploaded') @click.option('--max-concurrent', default=0, type=int, help='Maximum number of concurrent uploads.') @@ -283,7 +299,7 @@ def upload_endpoint_scans_in_directory(offline_scans_root_directory: str, force: Examples: upload a directory with offline endpoint scan results: - $ intezer-analyze upload_endpoint_scans_in_directory /path/to/endpoint_scan_results_root + $ intezer-analyze upload-endpoint-scans-in-directory /path/to/endpoint_scan_results_root """ try: create_global_api() @@ -297,9 +313,8 @@ def upload_endpoint_scans_in_directory(offline_scans_root_directory: str, force: click.echo('Unexpected error occurred, please contact us at support@intezer.com ' f'and attach the log file in {utilities.log_file_path}') - -@main_cli.command('upload_emails_in_directory', - short_help='upload all subdirectories with .emal files') +@main_cli.command('upload-emails-in-directory', + short_help='upload all subdirectories with .emal files') @click.argument('emails_root_directory', type=click.Path(exists=True, file_okay=False, dir_okay=True)) @click.option('--ignore-directory-count-limit', is_flag=True, @@ -314,7 +329,7 @@ def upload_emails_in_directory(emails_root_directory: str, ignore_directory_coun Examples: upload a directory with .eml files: - $ intezer-analyze upload_emails_in_directory /path/to/emails_root_directory + $ intezer-analyze upload-emails-in-directory /path/to/emails_root_directory """ try: create_global_api() @@ -327,11 +342,9 @@ def upload_emails_in_directory(emails_root_directory: str, ignore_directory_coun click.echo('Unexpected error occurred, please contact us at support@intezer.com ' f'and attach the log file in {utilities.log_file_path}') - if __name__ == '__main__': try: main_cli() - except Exception as e: logger.exception(f'Unexpected error occurred {e}') click.echo('Unexpected error occurred') diff --git a/tests/unit/cli_test.py b/tests/unit/cli_test.py index 89bde3d..b5a886e 100644 --- a/tests/unit/cli_test.py +++ b/tests/unit/cli_test.py @@ -370,7 +370,7 @@ def test_index_by_txt_file_command_wrong_index(self): # Assert self.assertEqual(result.exit_code, 2, result.exception) - self.assertTrue(b'Usage: main-cli index_by_list [OPTIONS] PATH [FAMILY_NAME]' in result.stdout_bytes) - self.assertTrue(b'Try \'main-cli index_by_list -h\' for help.' in result.stdout_bytes) + self.assertTrue(b'Usage: main-cli index-by-list [OPTIONS] PATH [FAMILY_NAME]' in result.stdout_bytes) + self.assertTrue(b'Try \'main-cli index-by-list -h\' for help.' in result.stdout_bytes) self.assertTrue(b'Error: Invalid value for \'--index-as\': invalid choice: wrong_index_name. ' b'(choose from malicious, trusted)' in result.stdout_bytes)