1919#
2020# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
2121# Visit https://github.com/aboutcode-org/scancode.io for support and download.
22-
22+ import json
2323import subprocess
24- from datetime import date
24+ import urllib . parse
2525
26- from scancodeio .settings import GRIMOIRELAB_BINARY_FILE_PATTERN
27- from scancodeio .settings import GRIMOIRELAB_CODE_FILE_PATTERN
28- from scancodeio .settings import GRIMOIRELAB_DEVELOPER_CATEGORIES_THRESHOLDS
29- from scancodeio .settings import GRIMOIRELAB_ELEPHANT_THRESHOLD
30- from scancodeio .settings import GRIMOIRELAB_FROM_DATE
3126from scancodeio .settings import GRIMOIRELAB_METRICS_EXECUTABLE
3227from scancodeio .settings import GRIMOIRELAB_OPENSEARCH_INDEX
3328from scancodeio .settings import GRIMOIRELAB_OPENSEARCH_PASSWORD
3429from scancodeio .settings import GRIMOIRELAB_OPENSEARCH_URL
3530from scancodeio .settings import GRIMOIRELAB_OPENSEARCH_USERNAME
3631from scancodeio .settings import GRIMOIRELAB_PASSWORD
37- from scancodeio .settings import GRIMOIRELAB_PONY_THRESHOLD
38- from scancodeio .settings import GRIMOIRELAB_REPOSITORY_TIMEOUT
3932from scancodeio .settings import GRIMOIRELAB_URL
4033from scancodeio .settings import GRIMOIRELAB_USERNAME
4134from scanpipe .pipelines import Pipeline
@@ -49,57 +42,98 @@ class ScanRepoGrimoirelab(Pipeline):
4942
5043 @classmethod
5144 def steps (cls ):
52- return (cls .collect_and_store_grimoire_metric ,)
45+ return (
46+ cls .collect_and_store_grimoire_metric ,
47+ cls .format_metrics_output ,
48+ )
5349
5450 def collect_and_store_grimoire_metric (self ):
55- for input_source in self .project .input_sources :
56- repo_url = input_source ["download_url" ]
57- metrics_output_path = self .project .get_output_file_path ("metrics" , "json" )
58- grimoirelab_to_date = date .today ().isoformat ()
59-
60- command_args = [
61- GRIMOIRELAB_METRICS_EXECUTABLE ,
62- repo_url ,
63- "--grimoirelab-url" ,
64- GRIMOIRELAB_URL ,
65- "--grimoirelab-user" ,
66- GRIMOIRELAB_USERNAME ,
67- "--grimoirelab-password" ,
68- GRIMOIRELAB_PASSWORD ,
69- "--opensearch-url" ,
70- GRIMOIRELAB_OPENSEARCH_URL ,
71- "--opensearch-index" ,
72- GRIMOIRELAB_OPENSEARCH_INDEX ,
73- "--opensearch-user" ,
74- GRIMOIRELAB_OPENSEARCH_USERNAME ,
75- "--opensearch-password" ,
76- GRIMOIRELAB_OPENSEARCH_PASSWORD ,
77- "--from-date" ,
78- GRIMOIRELAB_FROM_DATE ,
79- "--to-date" ,
80- grimoirelab_to_date ,
81- "--repository-timeout" ,
82- GRIMOIRELAB_REPOSITORY_TIMEOUT ,
83- "--code-file-pattern" ,
84- GRIMOIRELAB_CODE_FILE_PATTERN ,
85- "--binary-file-pattern" ,
86- GRIMOIRELAB_BINARY_FILE_PATTERN ,
87- "--pony-threshold" ,
88- GRIMOIRELAB_PONY_THRESHOLD ,
89- "--elephant-threshold" ,
90- GRIMOIRELAB_ELEPHANT_THRESHOLD ,
91- "--dev-categories-thresholds" ,
92- * GRIMOIRELAB_DEVELOPER_CATEGORIES_THRESHOLDS ,
93- "--output" ,
94- str (metrics_output_path ),
95- ]
96-
97- try :
98- run_command_safely (command_args = command_args )
99- self .log (f"Metrics successfully saved to { metrics_output_path } " )
100- except subprocess .CalledProcessError as e :
101- raise RuntimeError (
102- f"grimoirelab-metrics pipeline failed { e .returncode } for { repo_url } "
103- )
104- except subprocess .TimeoutExpired :
105- raise RuntimeError ("grimoirelab-metrics pipeline timed out" )
51+ """
52+ Run the grimoirelab-metrics command against the input source.
53+ Save the generated metrics JSON to the project output directory.
54+ """
55+ if len (self .project .input_sources ) != 1 :
56+ raise ValueError ("Expected exactly one input source" )
57+
58+ repo_url = self .project .input_sources [0 ]["download_url" ]
59+ if not is_valid_vcs_url (repo_url ):
60+ raise ValueError (
61+ "Invalid input source: the pipeline accepts only a valid repository URL"
62+ )
63+
64+ self .metrics_output_path = self .project .get_output_file_path ("metrics" , "json" )
65+ command_args = [
66+ GRIMOIRELAB_METRICS_EXECUTABLE ,
67+ repo_url ,
68+ "--grimoirelab-url" ,
69+ GRIMOIRELAB_URL ,
70+ "--grimoirelab-user" ,
71+ GRIMOIRELAB_USERNAME ,
72+ "--grimoirelab-password" ,
73+ GRIMOIRELAB_PASSWORD ,
74+ "--opensearch-url" ,
75+ GRIMOIRELAB_OPENSEARCH_URL ,
76+ "--opensearch-index" ,
77+ GRIMOIRELAB_OPENSEARCH_INDEX ,
78+ "--opensearch-user" ,
79+ GRIMOIRELAB_OPENSEARCH_USERNAME ,
80+ "--opensearch-password" ,
81+ GRIMOIRELAB_OPENSEARCH_PASSWORD ,
82+ "--output" ,
83+ str (self .metrics_output_path ),
84+ ]
85+
86+ try :
87+ run_command_safely (command_args = command_args )
88+ self .log ("GrimoireLab metrics pipeline completed successfully" )
89+ except subprocess .SubprocessError :
90+ raise RuntimeError ("Grimoirelab-metrics pipeline failed" )
91+ except FileNotFoundError :
92+ raise FileNotFoundError (
93+ "Grimoirelab-metrics not found. "
94+ "Please ensure grimoirelab-metrics is correctly configured."
95+ )
96+
97+ def format_metrics_output (self ):
98+ """
99+ Format the GrimoireLab metrics output by extracting the repository URL,
100+ score, and metrics from the generated JSON and overwriting it with a
101+ simplified structure.
102+ """
103+ with open (self .metrics_output_path ) as f :
104+ data = json .load (f )
105+
106+ package = list (data ["packages" ].values ())[0 ]
107+
108+ repository = package ["repository" ]
109+ score = package ["score" ]
110+ metrics = package ["metrics" ]
111+
112+ result = {
113+ "repository" : repository ,
114+ "npm_health_score" : score ,
115+ "metrics" : metrics ,
116+ }
117+
118+ with open (self .metrics_output_path , "w" ) as f :
119+ json .dump (result , f )
120+
121+
122+ def is_valid_vcs_url (url ):
123+ """Determine whether the URL is a valid VCS repository URL."""
124+ if not isinstance (url , str ) or not url :
125+ return False
126+
127+ if any (char .isspace () for char in url ):
128+ return False
129+
130+ forbidden_chars = ["|" , ";" , "&" , "`" , "$(" , ">" , "<" , "&&" , "||" ]
131+ if any (char in forbidden_chars for char in url ):
132+ return False
133+
134+ parsed = urllib .parse .urlparse (url )
135+ valid_schemes = {"https" , "git" , "ssh" , "git+https" , "git+ssh" }
136+ if parsed .scheme in valid_schemes and parsed .netloc :
137+ return True
138+
139+ return False
0 commit comments