Saltar al contenido
# 🌐 Fase 4 — Multi-org Scanner ## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago? ### ¿Qué hago? Extiendo Klaus-antipatterns-search con capacidad de escanear **todas las organizaciones y repositorios** de GitHub en un único comando, con ejecución paralela y un panel agregado de resultados. ### ¿Cómo lo hago? 1. 🌐 **`scan-org <perfil>`** — nuevo subcomando que lee el perfil de org desde `.antipatterns.yml`, enumera repos via GitHub API y los escanea en paralelo. 2. ⚡ **Worker pool** — semáforo de goroutines (`--workers N`, default 4) protege contra saturación de red y CPU. 3. 📊 **Panel agregado** — renderers `OrgConsole` y `OrgJSON` presentan resultados por repo con rollup de totales. 4. 🧹 **Clone + cleanup** — cada repo se clona shallow (`--depth=1`) en `os.MkdirTemp` y se limpia al terminar. ### ¿Para qué lo hago? Porque gestionar >5 repositorios de múltiples orgs (Ka0s-Klaus, Mango, MasOrange) manualmente es inviable. `scan-org` da visibilidad de deuda técnica a escala de organización en un solo comando, directamente desde CI/CD o desde local. --- ## 🏗️ Arquitectura ```mermaid graph TD CLI["🖥️ antipatterns scan-org ka0s-klaus\n--workers 4"] --> ORGSCANNER["🌐 OrgScanner.Run()"] ORGSCANNER --> FETCH["📡 fetchRepos()\ngh api /orgs/ka0s-klaus/repos\n--paginate --jq '.[}'"] FETCH --> FILTER["🧹 Filtro: archivados, forks, globs"] FILTER --> REPOS["📦 []GhRepo"] REPOS --> POOL["⚡ Worker Pool\nsem chan struct{}"] POOL --> W1["goroutine 1\ngit clone --depth=1"] POOL --> W2["goroutine 2\ngit clone --depth=1"] POOL --> WN["goroutine N"] W1 --> SCAN1["🎛️ scanner.Run(cloneDir)"] W2 --> SCAN2["🎛️ scanner.Run(cloneDir)"] WN --> SCANN["🎛️ scanner.Run(cloneDir)"] SCAN1 --> MU["sync.Mutex\n[]RepoResult"] SCAN2 --> MU SCANN --> MU MU --> REPORT["📊 OrgReport"] REPORT --> CONSOLE["💻 OrgConsole\n(tabla por repo)"] REPORT --> JSON["📄 OrgJSON\n(aggregate JSON)"] ``` --- ## ⚙️ Configuración — `.antipatterns.yml` ```yaml orgs: ka0s-klaus: token_env: GH_TOKEN_KA0S # var de entorno con el token; vacío = gh auth output: reports/ka0s/ publish: true exclude_repos: # globs de filepath.Match - "mirror-*" - "archived-*" include_forks: false # excluir forks (default) include_archived: false # excluir repos archivados (default) masorange: token_env: GH_TOKEN_MASORANGE output: reports/masorange/ publish: false # datos de cliente: nunca se publican exclude_repos: [] ``` ### Campos de `OrgConfig` | Campo | Tipo | Default | Descripción | | --- | --- | --- | --- | | `token_env` | string | `""` | Nombre de la var de entorno con el GH token | | `output` | string | `""` | Directorio destino para reportes | | `publish` | bool | `false` | Si los reportes son publicables | | `exclude_repos` | []string | `[]` | Globs de repos a excluir (usa `filepath.Match`) | | `include_forks` | bool | `false` | Incluir repos fork | | `include_archived` | bool | `false` | Incluir repos archivados | --- ## 🖥️ Uso desde CLI ```bash # Scan básico (formato console) antipatterns scan-org ka0s-klaus # Con más workers y salida JSON antipatterns scan-org ka0s-klaus --workers 8 --format json --output reports/ka0s.json # Cliente con token específico (el token vive en la var de entorno configurada en token_env) GH_TOKEN_MASORANGE=ghp_xxx antipatterns scan-org masorange ``` ### Flags de `scan-org` | Flag | Default | Descripción | | --- | --- | --- | | `--format` | `console` | Formato de salida: `console`, `json` | | `--output`, `-o` | `""` (stdout) | Fichero de destino | | `--workers` | `4` | Goroutines paralelas de scan | --- ## 📊 Ejemplo de salida — console ``` 🌐 Org scan: ka0s-klaus (20 repos) ──────────────────────────────────────────────────────────────────────── REPO FINDINGS TOP RULE ──────────────────────────────────────────────────────────────────────── Klaus-antipatterns-search 8 magic_number ⚠️ ka0s 19 magic_number ⚠️ klaude-code-local 0 — ✅ ka0s.github.io 0 — ✅ ──────────────────────────────────────────────────────────────────────── TOTAL 27 magic_number ──────────────────────────────────────────────────────────────────────── ``` --- ## 🏗️ Diseño interno ### `OrgScanner` — inyección de dependencias El `OrgScanner` tiene dos funciones configurables para facilitar los tests: ```go type OrgScanner struct { workers int fetchFn FetcherFn // func(orgName string, profile OrgConfig) ([]GhRepo, error) scanFn ScannerFn // func(repo GhRepo, cfg *config.Config) model.RepoResult } ``` `New(workers)` inyecta las implementaciones reales. Los tests usan `.WithFetcher()` y `.WithScanner()` para inyectar stubs sin tocar la red ni el disco. ### Worker pool con semáforo ```mermaid sequenceDiagram participant Main as Run() participant Sem as sem chan[workers] participant G as goroutine N participant Mu as sync.Mutex Main->>Sem: sem <- struct{}{} (acquire) Main->>G: go func() G->>G: scanRepo() — clone + scan G->>Mu: mu.Lock() — append result G->>Sem: <-sem (release) G->>Main: wg.Done() ``` ### Filtrado de repos ```mermaid graph LR REPO["ghRepo"] --> A{Archived?} A -->|sí + !IncludeArchived| SKIP["⏭️ skip"] A -->|no| B{Fork?} B -->|sí + !IncludeForks| SKIP B -->|no| C{ExcludeRepos glob?} C -->|match| SKIP C -->|no match| SCAN["✅ escanear"] ``` --- ## 🧪 Cobertura de tests | Test | Qué verifica | | --- | --- | | `TestScanOrgParallel` | 20 repos concurrentes sin data races (`-race`) | | `TestScanOrgEmpty` | Org vacía → report vacío, sin llamar al scanner | | `TestRepoFilter` | 7 casos de IsExcluded con globs y patrones múltiples | | `TestAggregateReport` | TotalFindings() + TopRule() con repos mixtos | | `TestScanOrgWorkersCapped` | Más workers que repos no produce deadlock | --- ## 🔒 Seguridad - Los tokens **nunca se hardcodean** — siempre via `token_env` → variable de entorno. - Los reportes de clientes tienen `publish: false` — nunca se comprometen a git. - Los repos clonados se limpian con `defer os.RemoveAll(tmpDir)` incluso ante panic. - `gh api` usa la autenticación activa de `gh auth` si `token_env` está vacío. --- ## 🔗 Documentos relacionados - [SARIF + GitHub Action (Fase 3)](fase-3-sarif-action.md) — formato de salida por repo - [Adaptadores OSS (Fase 2)](fase-2-adaptadores-oss.md) — detección usada por cada scanRepo - [Detectores nativos (Fase 1)](fase-1-detectores-nativos.md) — detectores Go usados en cada repo - [README principal](../README.md) — roadmap completo del proyecto