-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpipeline_maestro.py
More file actions
338 lines (293 loc) · 13.1 KB
/
Copy pathpipeline_maestro.py
File metadata and controls
338 lines (293 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#!/usr/bin/env python3
"""
Pipeline Maestro - Orquestra coleta de dados GitLab e sincronizacao com o Supabase
Integra: coleta_git_contratos.py (MULTIPLOS REPOS) + JSON issues + sync_supabase.py
Fluxo:
1. Coleta dados Git de contratos_v2 E contratos (consolidado)
2. Carrega issues do JSON (gitlab_issues_raw.json)
3. Processa issues em memoria (taxonomia + detectores Git)
4. Sincroniza issues e releases direto no Supabase (sem Excel)
"""
import json
import os
import sys
from datetime import datetime
from pathlib import Path
# Importa os modulos locais
sys.path.insert(0, str(Path(__file__).parent))
try:
import config as mgi_config
from atualizar_gitlab_issues import validar_json_local
from coleta_git_contratos import GitColeta
from log_maintenance import limpar_logs_antigos
from logging_utils import configure_logging, get_logger
from processar_issues_memoria import resolve_enable_git
from sync_supabase import sync_issues_to_supabase
except ImportError as e:
print(f"ERRO importando modulos: {e}")
sys.exit(1)
class PipelineMaestro:
def __init__(
self,
config,
data_input=None,
all_modules: bool = False,
initial_load: bool = False,
full_refresh: bool = False,
):
self.config = config
self.repo_path = Path(config["repo_path"])
self.output_dir = Path(config["output_dir"])
self.issues_json = Path(config["issues_json_path"])
self.data_input = data_input # Data do batch script
self.all_modules = all_modules
self.initial_load = initial_load
self.full_refresh = full_refresh
mgi_config.apply_pipeline_runtime_flags(
all_modules=all_modules,
initial_load=initial_load,
full_refresh=full_refresh,
)
# Logging central (console em stdout + arquivo rotacionado)
configure_logging()
self.logger = get_logger(__name__)
def validar_ambiente(self) -> bool:
"""Valida existencia de arquivos e diretorios necessarios"""
self.logger.info("\n[VALIDACAO] Validando ambiente...")
self.logger.info(f" [INFO] Repositorio: {self.repo_path}")
# Validar JSON de issues
if not self.issues_json.exists():
self.logger.error(f"ERRO: JSON de issues nao encontrado: {self.issues_json}")
return False
# Criar diretorio de saida se necessario
self.output_dir.mkdir(parents=True, exist_ok=True)
self.logger.info("OK - Ambiente validado com sucesso")
return True
def executar_coleta_git(self):
"""Executa coleta de dados Git (ambos repositorios)"""
self.logger.info("\n[COLETA GIT] ETAPA 1: Coleta Git - Multiplos Repositorios")
self.logger.info("=" * 70)
try:
git_output = self.output_dir / "gitlab_git_data.json"
# Coleta de multiplos repositorios (configuravel em config.py)
repos = mgi_config.REPOS
dados_consolidados = {
"timestamp": datetime.now().isoformat(),
"repositorios": [],
"total_commits": 0,
"total_branches": 0,
"total_releases": 0,
}
for repo_path, repo_name in repos:
self.logger.info(f" [INFO] {repo_name}...")
try:
coleta = GitColeta(repo_path, repo_name)
if not coleta.validar_repo():
self.logger.warning(
f" AVISO: repositorio inacessivel ({repo_name}) - {repo_path}"
)
dados_consolidados["repositorios"].append(coleta.data)
continue
coleta.processar_completo(
None, since_days=mgi_config.SINCE_DAYS
) # None = nao exporta individual
dados_consolidados["repositorios"].append(coleta.data)
dados_consolidados["total_commits"] += len(coleta.data["commits"])
dados_consolidados["total_branches"] += len(coleta.data["branches"])
dados_consolidados["total_releases"] += len(coleta.data["releases"])
self.logger.info(f" OK - {repo_name} concluido")
except Exception as e:
self.logger.error(f" ERRO ao coletar {repo_name}: {e}")
# Exportar consolidado
with open(str(git_output), "w", encoding="utf-8") as f:
json.dump(dados_consolidados, f, indent=2, ensure_ascii=False)
self.logger.info(f"OK - Coleta Git consolidada: {git_output}")
self.logger.info(
f"\n [RESUMO] {len(dados_consolidados['repositorios'])} repos, "
f"{dados_consolidados['total_commits']} commits"
)
return git_output
except Exception as e:
self.logger.error(f"ERRO na coleta Git: {e}")
return None
def carregar_issues_json(self) -> list[dict]:
"""Carrega issues do JSON exportado"""
self.logger.info("\n[ISSUES] ETAPA 2: Carregamento de Issues")
self.logger.info("=" * 70)
try:
with open(self.issues_json, encoding="utf-8") as f:
issues_data = json.load(f)
if isinstance(issues_data, list):
issues = issues_data
elif isinstance(issues_data, dict) and "issues" in issues_data:
issues = issues_data["issues"]
else:
issues = []
# Filtros (fechadas antigas e data de corte) sao aplicados no sync.
self.logger.info(f"OK - Issues carregadas: {len(issues)}")
return issues
except Exception as e:
self.logger.error(f"ERRO carregando issues: {e}")
return []
def sincronizar_supabase(self, issues: list[dict]) -> bool:
"""Processa issues em memoria e sincroniza direto no Supabase (sem Excel)."""
self.logger.info("\n[SUPABASE] ETAPA 3: Processamento e sync de Issues")
self.logger.info("=" * 70)
try:
fast = os.environ.get("MGI_FAST_REPO_SYNC", "0").lower() not in ("0", "false", "no")
git_enabled = resolve_enable_git(not fast)
if not fast and not git_enabled:
self.logger.warning(
"WSL/Git indisponivel - detectores Git desativados (titulo/labels)"
)
upserted = sync_issues_to_supabase(
issues=issues,
include_releases=True,
enable_git=git_enabled,
)
self.issues_sincronizadas = upserted
self.logger.info(f"OK - {upserted} issues sincronizadas no Supabase")
self.logger.info("OK - Processamento concluido")
return True
except SystemExit as e:
self.logger.error(f"ERRO de configuracao no sync: {e}")
return False
except Exception as e:
self.logger.error(f"ERRO sincronizando issues: {e}", exc_info=True)
return False
def gerar_relatorio_final(self, git_stats, issues_count):
"""Gera relatorio final de execucao"""
relatorio = {
"timestamp": datetime.now().isoformat(),
"data_entrada": self.data_input,
"status": "sucesso",
"etapas": {
"coleta_git": {
"commits": git_stats.get("commits_total", 0),
"branches": git_stats.get("branches_total", 0),
"releases": git_stats.get("releases_total", 0),
},
"processamento_issues": {"total": issues_count},
"supabase": {
"issues_sincronizadas": getattr(self, "issues_sincronizadas", 0),
"atualizado": datetime.now().isoformat(),
},
},
}
logs_dir = self.output_dir / "Logs"
logs_dir.mkdir(parents=True, exist_ok=True)
relatorio_file = logs_dir / f"relatorio_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(relatorio_file, "w", encoding="utf-8") as f:
json.dump(relatorio, f, indent=2, ensure_ascii=False)
return relatorio
def executar_pipeline(self) -> bool:
"""Executa pipeline completo"""
self.logger.info("\n" + "=" * 70)
self.logger.info("PIPELINE MAESTRO - CONTRATOS v2")
self.logger.info("=" * 70)
self.logger.info(f"Data/Hora Inicio: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}")
if self.data_input:
self.logger.info(f"Data Entrada: {self.data_input}")
if self.all_modules:
self.logger.info("Modo modulos: TODOS (MGI_ALL_MODULES=1)")
if self.initial_load:
self.logger.info("Modo carga: INICIAL (sem filtro de issues fechadas > 60 dias)")
if self.full_refresh:
self.logger.info(
"Modo atualizacao: EXECUCAO COMPLETA (reprocessa metadados e enriquecimentos)"
)
removed_logs = limpar_logs_antigos(Path(self.output_dir))
if removed_logs:
self.logger.info(
f"OK - {removed_logs} arquivo(s) de log com mais de {mgi_config.LOG_RETENTION_DAYS} dias removidos"
)
# Validacao
if not self.validar_ambiente():
self.logger.error("\nERRO: Validacao falhou. Abortando.")
return False
# Coleta Git (OPCIONAL)
git_data_file = None
git_stats = {}
# Tentar coletar dados Git (pode falhar gracefully se repo nao existir)
git_data_file = self.executar_coleta_git()
if git_data_file:
# Carregar dados Git para estatisticas (totais consolidados na raiz)
try:
with open(git_data_file, encoding="utf-8") as f:
git_data = json.load(f)
git_stats = {
"commits_total": git_data.get("total_commits", 0),
"branches_total": git_data.get("total_branches", 0),
"releases_total": git_data.get("total_releases", 0),
}
except Exception as e:
self.logger.warning(f"Aviso ao carregar stats Git: {e}")
git_stats = {}
# GitLab: atualizado pelo executar_pipeline.bat (etapa 0).
# Se pipeline rodar direto, apenas valida JSON local.
validar_json_local(self.issues_json)
# Carregar issues
issues = self.carregar_issues_json()
if not issues:
self.logger.error("\nERRO: Nenhuma issue carregada. Abortando.")
return False
# Processar issues e sincronizar no Supabase
if not self.sincronizar_supabase(issues):
self.logger.error("\nERRO: Sincronizacao de issues falhou. Abortando.")
return False
# Gerar relatorio final
self.gerar_relatorio_final(git_stats, len(issues))
self.logger.info("\n" + "=" * 70)
self.logger.info("OK - PIPELINE CONCLUIDO COM SUCESSO")
self.logger.info("=" * 70)
self.logger.info("\nResumo Final:")
self.logger.info(f" Commits: {git_stats.get('commits_total', 0)}")
self.logger.info(f" Branches: {git_stats.get('branches_total', 0)}")
self.logger.info(f" Releases: {git_stats.get('releases_total', 0)}")
self.logger.info(f" Issues: {len(issues)}")
self.logger.info(
f" Sincronizadas no Supabase: {getattr(self, 'issues_sincronizadas', 0)}"
)
self.logger.info(f"\nData/Hora Fim: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}")
return True
def main():
"""Funcao principal"""
configure_logging()
all_modules = os.environ.get("MGI_ALL_MODULES", "1").lower() not in ("0", "false", "no")
initial_load = os.environ.get("MGI_INITIAL_LOAD", "0").lower() not in ("0", "false", "no")
full_refresh = mgi_config.is_full_refresh()
argv = [arg for arg in sys.argv[1:] if arg not in ("--all-modules", "--initial-load", "--full")]
if "--all-modules" in sys.argv[1:]:
all_modules = True
if "--initial-load" in sys.argv[1:]:
initial_load = True
if "--full" in sys.argv[1:]:
full_refresh = True
# Data via stdin era usada pelo fluxo Excel legado; ignorada no sync Supabase.
data_input = None
# Configuracao padrao (centralizada em config.py / variaveis de ambiente)
default_repo_path = mgi_config.REPOS[0][0] if mgi_config.REPOS else ""
pipeline_config = {
"repo_path": default_repo_path,
"output_dir": str(mgi_config.BASE_DIR),
"issues_json_path": str(mgi_config.ISSUES_JSON),
}
# Permitir override via argumentos
if len(argv) > 0:
pipeline_config["repo_path"] = argv[0]
if len(argv) > 1:
pipeline_config["output_dir"] = argv[1]
if len(argv) > 2:
pipeline_config["issues_json_path"] = argv[2]
# Executar pipeline
maestro = PipelineMaestro(
pipeline_config,
data_input,
all_modules=all_modules,
initial_load=initial_load,
full_refresh=full_refresh,
)
success = maestro.executar_pipeline()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()