|
19 | 19 | # |
20 | 20 | # ScanCode.io is a free software code scanning tool from nexB Inc. and others. |
21 | 21 | # Visit https://github.com/aboutcode-org/scancode.io for support and download. |
22 | | -import json |
23 | | -import subprocess |
24 | | -import urllib.parse |
25 | | -from os import environ |
26 | 22 |
|
27 | 23 | from scanpipe.pipelines import Pipeline |
28 | | -from scanpipe.pipes import run_command_safely |
29 | | - |
30 | | -GRIMOIRELAB_METRICS_EXECUTABLE = environ.get("GRIMOIRELAB_METRICS_EXECUTABLE", "") |
31 | | -GRIMOIRELAB_OPENSEARCH_INDEX = environ.get("GRIMOIRELAB_OPENSEARCH_INDEX", "") |
32 | | -GRIMOIRELAB_OPENSEARCH_PASSWORD = environ.get("GRIMOIRELAB_OPENSEARCH_PASSWORD", "") |
33 | | -GRIMOIRELAB_OPENSEARCH_URL = environ.get("GRIMOIRELAB_OPENSEARCH_URL", "") |
34 | | -GRIMOIRELAB_OPENSEARCH_USERNAME = environ.get("GRIMOIRELAB_OPENSEARCH_USERNAME", "") |
35 | | -GRIMOIRELAB_PASSWORD = environ.get("GRIMOIRELAB_PASSWORD", "") |
36 | | -GRIMOIRELAB_URL = environ.get("GRIMOIRELAB_URL", "") |
37 | | -GRIMOIRELAB_USERNAME = environ.get("GRIMOIRELAB_USERNAME", "") |
38 | | -GRIMOIRELAB_ECOSYSTEM = environ.get("GRIMOIRELAB_ECOSYSTEM", "") |
39 | | -GRIMOIRELAB_PROJECT = environ.get("GRIMOIRELAB_PROJECT", "") |
| 24 | +from scanpipe.pipes import repo_health |
| 25 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_ECOSYSTEM |
| 26 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_METRICS_EXECUTABLE |
| 27 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_OPENSEARCH_INDEX |
| 28 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_OPENSEARCH_PASSWORD |
| 29 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_OPENSEARCH_URL |
| 30 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_OPENSEARCH_USERNAME |
| 31 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_PASSWORD |
| 32 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_PROJECT |
| 33 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_URL |
| 34 | +from scanpipe.pipes.repo_health import GRIMOIRELAB_USERNAME |
40 | 35 |
|
41 | 36 |
|
42 | 37 | class ScanRepoHealth(Pipeline): |
@@ -71,126 +66,23 @@ def get_availability(cls): |
71 | 66 |
|
72 | 67 | def get_repo_url_input(self): |
73 | 68 | """Validate and extract the repository URL from the project's input sources""" |
74 | | - if len(self.project.input_sources) != 1: |
75 | | - raise ValueError("Expected exactly one input source") |
76 | | - |
77 | | - self.repo_url = self.project.input_sources[0]["download_url"] |
78 | | - if not is_valid_vcs_url(self.repo_url): |
79 | | - raise ValueError( |
80 | | - "Invalid input source: the pipeline accepts only a valid repository URL" |
81 | | - ) |
82 | | - |
83 | | - self.repo_url = self.repo_url.replace("git://", "https://") |
84 | | - if not self.repo_url.endswith(".git"): |
85 | | - self.repo_url += ".git" |
| 69 | + self.repo_url = repo_health.get_repo_url_input(project=self.project) |
86 | 70 |
|
87 | 71 | def collect_and_store_grimoire_metric(self): |
88 | 72 | """ |
89 | 73 | Run the grimoirelab-metrics command against the input source. |
90 | 74 | Save the generated metrics JSON to the project output directory. |
91 | 75 | """ |
92 | | - self.metrics_output_path = self.project.get_output_file_path("metrics", "json") |
93 | | - command_args = [ |
94 | | - GRIMOIRELAB_METRICS_EXECUTABLE, |
95 | | - self.repo_url, |
96 | | - "--grimoirelab-url", |
97 | | - GRIMOIRELAB_URL, |
98 | | - "--grimoirelab-user", |
99 | | - GRIMOIRELAB_USERNAME, |
100 | | - "--grimoirelab-password", |
101 | | - GRIMOIRELAB_PASSWORD, |
102 | | - "--grimoirelab-ecosystem", |
103 | | - GRIMOIRELAB_ECOSYSTEM, |
104 | | - "--grimoirelab-project", |
105 | | - GRIMOIRELAB_PROJECT, |
106 | | - "--opensearch-url", |
107 | | - GRIMOIRELAB_OPENSEARCH_URL, |
108 | | - "--opensearch-index", |
109 | | - GRIMOIRELAB_OPENSEARCH_INDEX, |
110 | | - "--opensearch-user", |
111 | | - GRIMOIRELAB_OPENSEARCH_USERNAME, |
112 | | - "--opensearch-password", |
113 | | - GRIMOIRELAB_OPENSEARCH_PASSWORD, |
114 | | - "--output", |
115 | | - str(self.metrics_output_path), |
116 | | - ] |
117 | | - |
118 | | - try: |
119 | | - run_command_safely(command_args=command_args) |
120 | | - self.log("GrimoireLab metrics pipeline completed successfully") |
121 | | - except subprocess.SubprocessError: |
122 | | - raise RuntimeError("Grimoirelab-metrics client failure") |
123 | | - except FileNotFoundError: |
124 | | - raise FileNotFoundError( |
125 | | - "Grimoirelab-metrics not found. " |
126 | | - "Please ensure grimoirelab-metrics is correctly configured." |
127 | | - ) |
| 76 | + self.metrics_output_path = repo_health.collect_and_store_grimoire_metric( |
| 77 | + project=self.project, repo_url=self.repo_url, logger=self.log |
| 78 | + ) |
128 | 79 |
|
129 | 80 | def format_metrics_output(self): |
130 | 81 | """ |
131 | 82 | Format the GrimoireLab metrics output by extracting the repository URL, |
132 | 83 | score, and metrics from the generated JSON and overwriting it with a |
133 | 84 | simplified structure, and updating the project's extra data. |
134 | 85 | """ |
135 | | - if not self.metrics_output_path.exists(): |
136 | | - raise FileNotFoundError( |
137 | | - "GrimoireLab client did not return a valid metrics JSON file" |
138 | | - ) |
139 | | - |
140 | | - with open(self.metrics_output_path) as f: |
141 | | - data = json.load(f) |
142 | | - |
143 | | - if not isinstance(data, dict): |
144 | | - raise ValueError("Invalid metrics JSON: Expected a JSON object.") |
145 | | - |
146 | | - package_data = data.get("packages") |
147 | | - if not package_data or not isinstance(package_data, dict): |
148 | | - raise ValueError( |
149 | | - "Invalid metrics JSON: Missing or malformed 'packages' section." |
150 | | - ) |
151 | | - |
152 | | - packages = list(package_data.values()) |
153 | | - if not packages: |
154 | | - raise ValueError("Invalid metrics JSON: 'packages' contains no data.") |
155 | | - |
156 | | - target_package = packages[0] |
157 | | - repository = target_package.get("repository") |
158 | | - score = target_package.get("score") |
159 | | - metrics = target_package.get("metrics") |
160 | | - |
161 | | - if repository is None or score is None or metrics is None: |
162 | | - raise ValueError( |
163 | | - f"Invalid metrics JSON. missing or null field(s): " |
164 | | - f"repository: {repository}, score: {score}, metrics: {metrics}" |
165 | | - ) |
166 | | - |
167 | | - result = { |
168 | | - "repository": repository, |
169 | | - "score": score, |
170 | | - "metrics": metrics, |
171 | | - } |
172 | | - |
173 | | - with open(self.metrics_output_path, "w") as f: |
174 | | - json.dump(result, f) |
175 | | - |
176 | | - self.project.update_extra_data(result) |
177 | | - |
178 | | - |
179 | | -def is_valid_vcs_url(url): |
180 | | - """Determine whether the URL string has the expected syntax of a VCS repository.""" |
181 | | - if not isinstance(url, str) or not url: |
182 | | - return False |
183 | | - |
184 | | - if any(char.isspace() for char in url): |
185 | | - return False |
186 | | - |
187 | | - forbidden_chars = ["|", ";", "&", "`", "$(", ">", "<", "&&", "||"] |
188 | | - if any(char in url for char in forbidden_chars): |
189 | | - return False |
190 | | - |
191 | | - parsed = urllib.parse.urlparse(url) |
192 | | - valid_schemes = {"https", "http", "git"} |
193 | | - if parsed.scheme in valid_schemes and parsed.netloc: |
194 | | - return True |
195 | | - |
196 | | - return False |
| 86 | + repo_health.format_metrics_output( |
| 87 | + project=self.project, metrics_output_path=self.metrics_output_path |
| 88 | + ) |
0 commit comments