raijin-server 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
raijin_server/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """Pacote principal do CLI Raijin Server."""
2
2
 
3
- __version__ = "0.2.3"
3
+ __version__ = "0.2.4"
4
4
 
5
5
  __all__ = ["__version__"]
raijin_server/cli.py CHANGED
@@ -470,6 +470,83 @@ def sanitize(ctx: typer.Context) -> None:
470
470
  _run_module(ctx, "sanitize")
471
471
 
472
472
 
473
+ # ============================================================================
474
+ # Subcomandos Cert-Manager
475
+ # ============================================================================
476
+ cert_app = typer.Typer(help="Comandos para gerenciamento do cert-manager")
477
+ app.add_typer(cert_app, name="cert")
478
+
479
+
480
+ @cert_app.command(name="install")
481
+ def cert_install(ctx: typer.Context) -> None:
482
+ """Instala cert-manager e configura ClusterIssuer interativamente."""
483
+ _run_module(ctx, "cert_manager")
484
+
485
+
486
+ @cert_app.command(name="status")
487
+ def cert_status(ctx: typer.Context) -> None:
488
+ """Exibe status detalhado do cert-manager, pods, webhook e certificados."""
489
+ exec_ctx = ctx.obj or ExecutionContext()
490
+ cert_manager.status(exec_ctx)
491
+
492
+
493
+ @cert_app.command(name="diagnose")
494
+ def cert_diagnose(ctx: typer.Context) -> None:
495
+ """Executa diagnóstico completo para troubleshooting do cert-manager."""
496
+ exec_ctx = ctx.obj or ExecutionContext()
497
+ cert_manager.diagnose(exec_ctx)
498
+
499
+
500
+ @cert_app.command(name="list-certs")
501
+ def cert_list(ctx: typer.Context) -> None:
502
+ """Lista todos os certificados no cluster."""
503
+ import subprocess
504
+
505
+ typer.secho("\n📜 Certificados no Cluster", fg=typer.colors.CYAN, bold=True)
506
+ try:
507
+ result = subprocess.run(
508
+ [
509
+ "kubectl", "get", "certificates", "-A",
510
+ "-o", "wide"
511
+ ],
512
+ capture_output=False,
513
+ timeout=15,
514
+ )
515
+ if result.returncode != 0:
516
+ typer.secho("Nenhum certificado encontrado ou erro ao listar.", fg=typer.colors.YELLOW)
517
+ except Exception as e:
518
+ typer.secho(f"Erro: {e}", fg=typer.colors.RED)
519
+
520
+
521
+ @cert_app.command(name="list-issuers")
522
+ def cert_list_issuers(ctx: typer.Context) -> None:
523
+ """Lista todos os ClusterIssuers e Issuers."""
524
+ import subprocess
525
+
526
+ typer.secho("\n🔐 ClusterIssuers", fg=typer.colors.CYAN, bold=True)
527
+ try:
528
+ subprocess.run(
529
+ ["kubectl", "get", "clusterissuers", "-o", "wide"],
530
+ timeout=15,
531
+ )
532
+ except Exception:
533
+ pass
534
+
535
+ typer.secho("\n🔐 Issuers (por namespace)", fg=typer.colors.CYAN, bold=True)
536
+ try:
537
+ subprocess.run(
538
+ ["kubectl", "get", "issuers", "-A", "-o", "wide"],
539
+ timeout=15,
540
+ )
541
+ except Exception:
542
+ pass
543
+
544
+
545
+ # ============================================================================
546
+ # Comandos Existentes
547
+ # ============================================================================
548
+
549
+
473
550
  @app.command(name="bootstrap")
474
551
  def bootstrap_cmd(ctx: typer.Context) -> None:
475
552
  """Instala todas as ferramentas necessarias: helm, kubectl, istioctl, velero, containerd."""
@@ -264,8 +264,67 @@ def verify_helm_chart(release: str, namespace: str, ctx: ExecutionContext) -> bo
264
264
 
265
265
 
266
266
  def verify_cert_manager(ctx: ExecutionContext) -> bool:
267
- """Health check para cert-manager."""
268
- return verify_helm_chart("cert-manager", CERT_NS, ctx)
267
+ """Health check completo para cert-manager."""
268
+ logger.info("Verificando health check: cert-manager")
269
+ typer.secho("\n=== Health Check: Cert-Manager ===", fg=typer.colors.CYAN)
270
+
271
+ all_ok = True
272
+
273
+ # Verifica release Helm
274
+ ok, status = check_helm_release("cert-manager", CERT_NS, ctx)
275
+ if ok:
276
+ typer.secho(f" ✓ Release cert-manager: {status}", fg=typer.colors.GREEN)
277
+ else:
278
+ typer.secho(f" ✗ Release cert-manager: {status}", fg=typer.colors.RED)
279
+ return False
280
+
281
+ # Verifica pods
282
+ if not check_k8s_pods_in_namespace(CERT_NS, ctx, timeout=180):
283
+ all_ok = False
284
+
285
+ # Verifica CRDs
286
+ if not ctx.dry_run:
287
+ try:
288
+ import subprocess
289
+ result = subprocess.run(
290
+ ["kubectl", "get", "crd", "certificates.cert-manager.io"],
291
+ capture_output=True,
292
+ timeout=10,
293
+ )
294
+ if result.returncode == 0:
295
+ typer.secho(" ✓ CRDs instalados", fg=typer.colors.GREEN)
296
+ else:
297
+ typer.secho(" ✗ CRDs não encontrados", fg=typer.colors.RED)
298
+ all_ok = False
299
+ except Exception as e:
300
+ typer.secho(f" ✗ Erro ao verificar CRDs: {e}", fg=typer.colors.RED)
301
+ all_ok = False
302
+
303
+ # Verifica webhook ready
304
+ if not ctx.dry_run:
305
+ try:
306
+ import subprocess
307
+ result = subprocess.run(
308
+ [
309
+ "kubectl", "get", "deployment", "cert-manager-webhook",
310
+ "-n", CERT_NS,
311
+ "-o", "jsonpath={.status.readyReplicas}"
312
+ ],
313
+ capture_output=True,
314
+ text=True,
315
+ timeout=10,
316
+ )
317
+ ready = result.returncode == 0 and result.stdout.strip() and int(result.stdout.strip()) >= 1
318
+ if ready:
319
+ typer.secho(" ✓ Webhook pronto", fg=typer.colors.GREEN)
320
+ else:
321
+ typer.secho(" ✗ Webhook não está pronto", fg=typer.colors.RED)
322
+ all_ok = False
323
+ except Exception as e:
324
+ typer.secho(f" ✗ Erro ao verificar webhook: {e}", fg=typer.colors.RED)
325
+ all_ok = False
326
+
327
+ return all_ok
269
328
 
270
329
 
271
330
  def verify_secrets(ctx: ExecutionContext) -> bool:
@@ -244,7 +244,15 @@ def _resolve_tls_secret() -> str | None:
244
244
  return secret.strip() or None
245
245
 
246
246
 
247
- def _build_manifest(host: str, tls_secret: str | None) -> str:
247
+ def _resolve_ip_access() -> bool:
248
+ """Pergunta se deseja acesso via IP direto (para testes)."""
249
+ env_ip = os.environ.get("APOKOLIPS_IP_ACCESS")
250
+ if env_ip:
251
+ return env_ip.strip().lower() in ("1", "true", "yes")
252
+ return typer.confirm("Habilitar acesso via IP direto? (apenas para testes)", default=True)
253
+
254
+
255
+ def _build_manifest(host: str, tls_secret: str | None, ip_access: bool = False) -> str:
248
256
  html_block = indent(HTML_TEMPLATE.strip("\n"), " " * 4)
249
257
  tls_block = ""
250
258
  if tls_secret:
@@ -254,6 +262,20 @@ def _build_manifest(host: str, tls_secret: str | None) -> str:
254
262
  f" - {host}\n"
255
263
  f" secretName: {tls_secret}\n"
256
264
  )
265
+
266
+ # Regra adicional para acesso via IP (sem host)
267
+ ip_rule = ""
268
+ if ip_access:
269
+ ip_rule = """
270
+ - http:
271
+ paths:
272
+ - path: /
273
+ pathType: Prefix
274
+ backend:
275
+ service:
276
+ name: apokolips-demo
277
+ port:
278
+ number: 80"""
257
279
 
258
280
  template = """\
259
281
  apiVersion: v1
@@ -340,12 +362,13 @@ spec:
340
362
  service:
341
363
  name: apokolips-demo
342
364
  port:
343
- number: 80
365
+ number: 80__IP_RULE__
344
366
  __TLS__
345
367
  """
346
368
 
347
369
  manifest = template.format(namespace=NAMESPACE, host=host)
348
370
  manifest = manifest.replace("__HTML__", html_block)
371
+ manifest = manifest.replace("__IP_RULE__", ip_rule)
349
372
  manifest = manifest.replace("__TLS__", tls_block.rstrip())
350
373
  return f"{manifest.strip()}\n"
351
374
 
@@ -354,7 +377,8 @@ def run(ctx: ExecutionContext) -> None:
354
377
  ensure_tool("kubectl", ctx, install_hint="Instale kubectl para aplicar o manifesto do site.")
355
378
  host = _resolve_host()
356
379
  tls_secret = _resolve_tls_secret()
357
- manifest = _build_manifest(host, tls_secret)
380
+ ip_access = _resolve_ip_access()
381
+ manifest = _build_manifest(host, tls_secret, ip_access)
358
382
 
359
383
  typer.echo("Gerando manifesto Apokolips...")
360
384
  write_file(TMP_MANIFEST, manifest, ctx)
@@ -369,10 +393,22 @@ def run(ctx: ExecutionContext) -> None:
369
393
  typer.echo(f" • Host: {host}")
370
394
  if tls_secret:
371
395
  typer.echo(f" • Secret TLS: {tls_secret}")
396
+ if ip_access:
397
+ typer.secho(" • Acesso via IP: HABILITADO (apenas para testes)", fg=typer.colors.YELLOW)
398
+
372
399
  typer.echo("\nTestes sugeridos:")
373
- typer.echo(f" curl -H 'Host: {host}' https://<IP_DO_LOAD_BALANCER>/ --insecure")
400
+ if ip_access:
401
+ typer.echo(" # Acesso direto via IP (teste):")
402
+ typer.echo(" curl http://<IP_DO_SERVIDOR>/")
403
+ typer.echo("")
404
+ typer.echo(f" # Acesso via DNS (produção):")
405
+ typer.echo(f" curl -H 'Host: {host}' http://<IP_DO_LOAD_BALANCER>/")
374
406
  typer.echo(f" kubectl -n {NAMESPACE} get ingress {NAMESPACE}")
375
407
  typer.echo(f" kubectl -n {NAMESPACE} get pods")
376
408
 
409
+ if ip_access:
410
+ typer.secho("\n⚠️ Lembre-se de desabilitar o acesso via IP após configurar o DNS!", fg=typer.colors.YELLOW)
411
+ typer.echo(" Rode novamente com APOKOLIPS_IP_ACCESS=false ou responda 'não' na pergunta.")
412
+
377
413
  typer.echo("\nPara remover:")
378
414
  typer.echo(f" kubectl delete namespace {NAMESPACE}")