Skip to content

Commit a8b9a76

Browse files
committed
Added a regex in the config file for each resource to exclude certain types of files
1 parent 3f1e9c9 commit a8b9a76

11 files changed

Lines changed: 121 additions & 41 deletions

File tree

‎hashtheplanet/config/config.py‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
"""
22
This module handles the config file.
33
"""
4+
from enum import Enum
45
import json
56
from typing import Dict, List
67

78
from hashtheplanet.resources.git_resource import GitResource
89
from hashtheplanet.resources.npm_resource import NpmResource
910

11+
class ConfigField(Enum):
12+
"""
13+
This enum contains every field that can be found in the config file
14+
"""
15+
TARGETS = "targets"
16+
EXCLUDE_REGEX = "exclude_regex"
17+
1018
class Config():
1119
"""
1220
This class implements methods to manipulate the config file.
@@ -25,15 +33,15 @@ def parse(self, config_path: str):
2533
with open(config_path, "r", encoding="utf-8") as file_fp:
2634
self._config = json.load(file_fp)
2735

28-
def get_targets(self, resource_name: str) -> List[str]:
36+
def get(self, resource_name: str, config_field: ConfigField):
2937
"""
30-
This methods returns the targets used by the given resource.
38+
This methods returns a field content used by the given resource.
3139
"""
32-
module_info: Dict = self._config.get(resource_name)
40+
field_content: Dict = self._config.get(resource_name)
3341

34-
if module_info is None:
35-
return []
36-
return module_info.get("targets")
42+
if not config_field or not field_content:
43+
return None
44+
return field_content.get(config_field.value)
3745

3846
def get_used_resources(self) -> List[str]:
3947
"""

‎hashtheplanet/core/hashtheplanet.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from sqlalchemy.orm import sessionmaker
1616

1717
# project imports
18-
from hashtheplanet.config.config import Config
18+
from hashtheplanet.config.config import Config, ConfigField
1919
from hashtheplanet.executor.executor import Executor
2020
from hashtheplanet.sql.db_connector import Base, DbConnector, Hash
2121

@@ -90,10 +90,11 @@ def compute_hashs(self):
9090
self._config.parse(self._input_file)
9191

9292
for resource_name in self._config.get_used_resources():
93-
targets = self._config.get_targets(resource_name)
93+
targets = self._config.get(resource_name, ConfigField.TARGETS) or []
94+
exclude_regex = self._config.get(resource_name, ConfigField.EXCLUDE_REGEX)
9495

9596
for target in targets:
96-
self._executor.execute(resource_name, target)
97+
self._executor.execute(resource_name, target, exclude_regex)
9798

9899
logger.info("Computing done")
99100

‎hashtheplanet/executor/executor.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
This module handles the resource executions.
33
"""
44
from importlib import import_module
5+
from typing import Optional
56

67
from loguru import logger
78

@@ -17,7 +18,7 @@ def __init__(self, database: DbConnector, session_scope):
1718
self._database = database
1819
self._session_scope = session_scope
1920

20-
def execute(self, resource_name: str, target: str):
21+
def execute(self, resource_name: str, target: str, exclude_regex: Optional[str] = None):
2122
"""
2223
This method executes a resource to compute hashes.
2324
"""
@@ -31,4 +32,4 @@ def execute(self, resource_name: str, target: str):
3132
return
3233

3334
resource_instance: Resource = getattr(module, resource_class_name)(self._database)
34-
resource_instance.compute_hashes(self._session_scope, target)
35+
resource_instance.compute_hashes(self._session_scope, target, exclude_regex)

‎hashtheplanet/resources/git_resource.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import subprocess
77
import tempfile
88
from stat import S_ISDIR, S_ISREG
9-
from typing import List, Tuple
9+
from typing import List, Optional, Tuple
1010

1111
# third party imports
1212
from git import GitCommandError, Repo
@@ -54,10 +54,11 @@ def get_all_files_from_commit(commit: Commit) -> List[Tuple[FilePath, BlobHash]]
5454
file_list.append((blob.path, blob.hexsha))
5555
return file_list
5656

57-
@staticmethod
5857
def _hash_files(
58+
self,
5959
files: List[GitFileMetadata],
60-
repo_dir_path: str
60+
repo_dir_path: str,
61+
exclude_regex: Optional[str]
6162
) -> List[FileMetadata]:
6263
"""
6364
This method calculates the SHA256 hashes of input files.
@@ -69,6 +70,8 @@ def _hash_files(
6970
os.chdir(repo_dir_path)
7071

7172
for (file_path, tag_name, blob_hash) in files:
73+
if not self.can_save(exclude_regex, file_path):
74+
continue
7275
try:
7376
# We need to use a subprocess and not the GitPython library
7477
# because when we execute "git cat-file -p [blob]" with it, it always removes the \n from the last line.
@@ -192,7 +195,7 @@ def _filter_stored_tags(stored_versions: List[VersionTable], found_tags: List[Ta
192195
result.append(found_tag)
193196
return result
194197

195-
def compute_hashes(self, session_scope, target: str):
198+
def compute_hashes(self, session_scope, target: str, exclude_regex: Optional[str]):
196199
"""
197200
This method clones the repository from url, retrieves tags, compares each tags to retrieve only modified files,
198201
computes their hashes and then stores the tags & files information in the database.
@@ -203,6 +206,7 @@ def compute_hashes(self, session_scope, target: str):
203206

204207
with tempfile.TemporaryDirectory() as tmp_dir_name:
205208
try:
209+
logger.info(f"Cloning {target}")
206210
repo = self.clone_repository(target, tmp_dir_name)
207211
except GitCommandError as error:
208212
logger.warning(f"Error while cloning repository on {target}: {error}")
@@ -225,7 +229,7 @@ def compute_hashes(self, session_scope, target: str):
225229
files += self._get_diff_files(tags)
226230

227231
logger.info("Generating hashes ...")
228-
files_info = self._hash_files(files, tmp_dir_name)
232+
files_info = self._hash_files(files, tmp_dir_name, exclude_regex)
229233

230234
logger.info("Saving hashes ...")
231235
self._save_hashes(session_scope, files_info, tags, technology)

‎hashtheplanet/resources/npm_resource.py‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#standard imports
55
import tarfile
66
import tempfile
7-
from typing import Dict, List, Set, Tuple
7+
from typing import Dict, List, Optional, Set, Tuple
88
import requests
99

1010
# third party imports
@@ -61,8 +61,7 @@ def save_tar_to_disk(file_path: str, npm_module_name: str, version: str):
6161
with open(file_path, 'wb') as file_fd:
6262
file_fd.write(request.content)
6363

64-
@staticmethod
65-
def extract_hashes_from_tar(file_path: str) -> List[FileMetadata]:
64+
def extract_hashes_from_tar(self, file_path: str, exclude_regex: Optional[str]) -> List[FileMetadata]:
6665
"""
6766
This method returns all hashes of all files contained in a tar file.
6867
"""
@@ -72,7 +71,7 @@ def extract_hashes_from_tar(file_path: str) -> List[FileMetadata]:
7271
for member in tar.getmembers():
7372
file = tar.extractfile(member)
7473

75-
if file is None:
74+
if file is None or not self.can_save(exclude_regex, member.path):
7675
continue
7776
files.append((member.path, Hash.hash_bytes(file.read())))
7877
return files
@@ -94,7 +93,7 @@ def _save_hashes(
9493
self._database.insert_file(session, npm_module_name, file_path)
9594
self._database.insert_or_update_hash(session, file_hash, npm_module_name, [version])
9695

97-
def compute_hashes(self, session_scope, target: str):
96+
def compute_hashes(self, session_scope, target: str, exclude_regex: Optional[str]):
9897
"""
9998
This method downloads all versions of an npm module and stores all the versions with their associated files
10099
and hashes and stores them in the database.
@@ -108,6 +107,6 @@ def compute_hashes(self, session_scope, target: str):
108107
file_path = f"{tmp_dir_name}/{target}-{version}.tgz"
109108

110109
self.save_tar_to_disk(file_path, target, version)
111-
files_info[version] = self.extract_hashes_from_tar(file_path)
110+
files_info[version] = self.extract_hashes_from_tar(file_path, exclude_regex)
112111

113112
self._save_hashes(session_scope, files_info, versions, target)

‎hashtheplanet/resources/resource.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"""
22
This module contains the base class for the resources.
33
"""
4+
import re
5+
from typing import Optional
6+
47
from hashtheplanet.sql.db_connector import DbConnector
58

69
class Resource(): # pylint: disable=too-few-public-methods
@@ -12,8 +15,19 @@ class Resource(): # pylint: disable=too-few-public-methods
1215
def __init__(self, database: DbConnector):
1316
self._database = database
1417

15-
def compute_hashes(self, session_scope, target: str):
18+
def compute_hashes(self, session_scope, target: str, exclude_regex: Optional[str]):
1619
"""
1720
This method computes all the versions and their associated files & hashes and stores them in the database.
1821
"""
1922
raise NotImplementedError()
23+
24+
@staticmethod
25+
def can_save(exclude_regex: str, file_path: str):
26+
"""
27+
This method permits to verify if the specified file can be saved in the database or not
28+
"""
29+
if not file_path:
30+
return False
31+
if not exclude_regex:
32+
return True
33+
return not re.search(exclude_regex, file_path)

‎src/tech_list.json‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
"https://github.com/drupal/drupal.git",
88
"https://github.com/magento/magento2.git",
99
"https://github.com/joomla/joomla-cms.git"
10-
]
10+
],
11+
"exclude_regex": "\\.php|tests\\/|test\\/|\\.gitignore|\\.package|\\.idea|qunit|\\.editorconfig|vendor\\/|\\.vue|\\.scss|\\.less"
1112
},
1213
"npm": {
1314
"targets": [
1415
"underscore",
1516
"jquery"
16-
]
17+
],
18+
"exclude_regex": "tests\\/|test\\/|\\.jshintrc|\\.npmignore|\\.bowerrc|\\.jscsrc|\\.bower.json|\\.eslintrc.json|\\.jshintignore|\\.eslintrc|\\.scss|\\.less|\\.vue|\\.scss|\\.less|\\.idea"
1719
}
1820
}

‎tests/config/test_config.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from unittest.mock import MagicMock, mock_open, patch
99

1010
# project imports
11-
from hashtheplanet.config.config import Config
11+
from hashtheplanet.config.config import Config, ConfigField
1212

1313
def get_mock_open(files: Dict[str, str]):
1414
def open_mock(filename, *args, **kwargs):
@@ -61,9 +61,9 @@ def test_get_targets():
6161
config = Config()
6262

6363
with patch.dict(config._config, {"git": {"targets": ["target1", "target2"]}}):
64-
assert len(config.get_targets("git")) == 2
64+
assert len(config.get("git", ConfigField.TARGETS)) == 2
6565

66-
assert len(config.get_targets("npm")) == 0
66+
assert not config.get("npm", ConfigField.TARGETS)
6767

6868
def test_get_used_resources():
6969
config = Config()

‎tests/resources/test_git_resource.py‎

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,11 @@ def test_hash_files():
9191

9292
git_resource = GitResource(None)
9393

94+
# when there are no excluded files
9495
with mock.patch("subprocess.check_output", subprocess_mock(blobs)) as sp_mock, \
9596
mock.patch("os.getcwd", return_value="/foobar/") as getcwd_mock, \
9697
mock.patch("os.chdir", return_value=None) as chdir_mock:
97-
files_metadata = git_resource._hash_files(git_files_metadata, "repo_dir_path")
98+
files_metadata = git_resource._hash_files(git_files_metadata, "repo_dir_path", None)
9899

99100
assert sp_mock.call_count == 3
100101
sp_mock.assert_called_with(['git', 'cat-file', '-p', 'e42f952edc48e2c085c206166bf4f1ead4d4b058'], shell=False)
@@ -114,11 +115,31 @@ def test_hash_files():
114115
assert files_metadata[1][1] == "1.2.5"
115116
assert files_metadata[1][2] == hashlib.sha256(blobs.get("e42f952edc48e2c085c206166bf4f1ead4d4b058")).hexdigest()
116117

118+
git_resource = GitResource(None)
119+
120+
# When the *.cfg files are excluded
121+
with mock.patch("subprocess.check_output", subprocess_mock(blobs)) as sp_mock, \
122+
mock.patch("os.getcwd", return_value="/foobar/") as getcwd_mock, \
123+
mock.patch("os.chdir", return_value=None) as chdir_mock:
124+
files_metadata = git_resource._hash_files(git_files_metadata, "repo_dir_path", "\\.cfg$")
125+
126+
assert sp_mock.call_count == 2
127+
128+
getcwd_mock.assert_called_once()
129+
130+
assert chdir_mock.call_count == 2
131+
chdir_mock.assert_called_with("/foobar/")
132+
133+
assert len(files_metadata) == 1
134+
135+
assert files_metadata[0][0] == "LICENSE"
136+
assert files_metadata[0][1] == "1.2.3"
137+
assert files_metadata[0][2] == hashlib.sha256(blobs.get("d159169d1050894d3ea3b98e1c965c4058208fe1")).hexdigest()
117138

118139
with mock.patch("subprocess.check_output", subprocess_mock(blobs)) as sp_mock, \
119140
mock.patch("os.getcwd", return_value="/foobar/") as getcwd_mock, \
120141
mock.patch("os.chdir", return_value=None) as chdir_mock:
121-
files_metadata = git_resource._hash_files([["empty", "1.2.1", "empty"]], "repo_dir_path")
142+
files_metadata = git_resource._hash_files([["empty", "1.2.1", "empty"]], "repo_dir_path", None)
122143

123144
assert sp_mock.call_count == 1
124145
sp_mock.assert_called_with(['git', 'cat-file', '-p', 'empty'], shell=False)
@@ -134,7 +155,7 @@ def test_hash_files():
134155
with mock.patch.object(subprocess, "check_output", MagicMock(side_effect=ValueError("error"))) as mock_exec, \
135156
mock.patch("os.getcwd", return_value="/foobar/") as getcwd_mock, \
136157
mock.patch("os.chdir", return_value=None) as chdir_mock:
137-
git_resource._hash_files(git_files_metadata, "repo_dir_path")
158+
git_resource._hash_files(git_files_metadata, "repo_dir_path", None)
138159

139160
getcwd_mock.assert_called_once()
140161

@@ -411,7 +432,7 @@ def mock_tmp_dir():
411432
session = MagicMock()
412433
git_resource = GitResource(DbConnector())
413434

414-
git_resource.compute_hashes(session, repo_url)
435+
git_resource.compute_hashes(session, repo_url, None)
415436

416437
# In this situation, we verify that by giving a good repo_url & a good tmp_dir_path
417438
# we download the tags, calculate hash & store them in the database
@@ -420,7 +441,7 @@ def mock_tmp_dir():
420441
mock_get_tag_files.assert_called_once_with(tags[0])
421442
mock_filter_stored_tags.assert_called_once_with([], tags)
422443
mock_get_diff_files.assert_called_once_with(tags)
423-
mock_hash_files.assert_called_once_with([1, 2], tmp_dir_path)
444+
mock_hash_files.assert_called_once_with([1, 2], tmp_dir_path, None)
424445
mock_save_hashes.assert_called_once_with(session, "hashed files", tags, "foobar")
425446

426447
with patch.object(
@@ -435,7 +456,7 @@ def mock_tmp_dir():
435456
patch.object(GitResource, "_hash_files", return_value="hashed files") as mock_hash_files, \
436457
patch.object(GitResource, "_save_hashes") as mock_save_hashes, \
437458
patch.object(DbConnector, "get_versions") as mock_get_versions:
438-
git_resource.compute_hashes(MagicMock(), repo_url)
459+
git_resource.compute_hashes(MagicMock(), repo_url, None)
439460
mock_clone_repo.assert_called_once_with(repo_url, tmp_dir_path)
440461

441462
# In this situation, we verify that by giving a wrong repository we stop the function

0 commit comments

Comments
 (0)