raijin-server 0.2.4__py3-none-any.whl → 0.2.6__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.
@@ -432,38 +432,347 @@ def _wait_for_webhook_ready(ctx: ExecutionContext, timeout: int = WEBHOOK_READY_
432
432
  # Instalação e Configuração
433
433
  # =============================================================================
434
434
 
435
- def _install_cert_manager_helm(ctx: ExecutionContext) -> bool:
436
- """Instala cert-manager via Helm."""
437
- typer.secho("\n📦 Instalando cert-manager via Helm...", fg=typer.colors.CYAN, bold=True)
435
+ def _test_helm_repo_connectivity(ctx: ExecutionContext) -> bool:
436
+ """Testa conectividade com o repositório Helm antes de instalar."""
437
+ if ctx.dry_run:
438
+ return True
439
+
440
+ logger.info("Testando conectividade com charts.jetstack.io")
441
+ typer.echo(" [1/5] Testando conectividade com charts.jetstack.io...")
438
442
 
439
443
  try:
440
- helm_upgrade_install(
441
- release="cert-manager",
442
- chart=CHART_NAME,
443
- namespace=NAMESPACE,
444
- ctx=ctx,
445
- repo="jetstack",
446
- repo_url=CHART_REPO,
447
- create_namespace=True,
448
- extra_args=[
449
- "--set", "installCRDs=true",
450
- "--set", "webhook.timeoutSeconds=30",
451
- "--set", "startupapicheck.timeout=5m",
452
- "--set", "startupapicheck.enabled=true",
453
- # Aumenta recursos para ambientes mais lentos
454
- "--set", "webhook.replicaCount=1",
455
- "--set", "cainjector.replicaCount=1",
456
- "--wait", # Espera o Helm considerar o release deployed
457
- "--timeout", "10m",
458
- ],
444
+ start = time.time()
445
+ result = subprocess.run(
446
+ ["curl", "-sI", "--connect-timeout", "15", f"{CHART_REPO}/index.yaml"],
447
+ capture_output=True,
448
+ text=True,
449
+ timeout=20,
459
450
  )
451
+ elapsed = time.time() - start
452
+
453
+ if result.returncode == 0 and "200" in result.stdout:
454
+ logger.info(f"Repositório Helm acessível em {elapsed:.2f}s")
455
+ typer.secho(f" ✓ Repositório Helm acessível ({elapsed:.2f}s)", fg=typer.colors.GREEN)
456
+ return True
457
+ else:
458
+ logger.error(f"Repositório retornou erro: {result.stdout[:200]}")
459
+ typer.secho(f" ✗ Repositório retornou erro: {result.stdout[:100]}", fg=typer.colors.RED)
460
+ return False
461
+ except subprocess.TimeoutExpired:
462
+ logger.error("Timeout ao conectar com charts.jetstack.io")
463
+ typer.secho(" ✗ Timeout ao conectar com charts.jetstack.io (>15s)", fg=typer.colors.RED)
464
+ return False
465
+ except Exception as e:
466
+ logger.error(f"Erro de conectividade: {e}")
467
+ typer.secho(f" ✗ Erro de conectividade: {e}", fg=typer.colors.RED)
468
+ return False
469
+
470
+
471
+ def _test_image_registry_connectivity(ctx: ExecutionContext) -> bool:
472
+ """Testa conectividade com o registry de imagens."""
473
+ if ctx.dry_run:
460
474
  return True
475
+
476
+ logger.info("Testando conectividade com quay.io (registry de imagens)")
477
+ typer.echo(" [2/5] Testando conectividade com quay.io (registry de imagens)...")
478
+
479
+ try:
480
+ start = time.time()
481
+ result = subprocess.run(
482
+ ["curl", "-sI", "--connect-timeout", "15", "https://quay.io/v2/"],
483
+ capture_output=True,
484
+ text=True,
485
+ timeout=20,
486
+ )
487
+ elapsed = time.time() - start
488
+
489
+ # quay.io retorna 401 para /v2/ sem auth, mas isso significa que está acessível
490
+ if result.returncode == 0 and ("200" in result.stdout or "401" in result.stdout):
491
+ logger.info(f"Registry quay.io acessível em {elapsed:.2f}s")
492
+ typer.secho(f" ✓ Registry quay.io acessível ({elapsed:.2f}s)", fg=typer.colors.GREEN)
493
+ return True
494
+ else:
495
+ logger.warning(f"Registry pode estar inacessível: {result.stdout[:100]}")
496
+ typer.secho(f" ⚠ Registry pode estar inacessível", fg=typer.colors.YELLOW)
497
+ return True # Não bloqueia, apenas avisa
461
498
  except Exception as e:
462
- typer.secho(f" Erro na instalação do Helm: {e}", fg=typer.colors.RED)
463
- ctx.errors.append(f"cert-manager: falha na instalação Helm - {e}")
499
+ logger.warning(f"Não foi possível testar registry: {e}")
500
+ typer.secho(f" Não foi possível testar registry: {e}", fg=typer.colors.YELLOW)
501
+ return True # Não bloqueia
502
+
503
+
504
+ def _add_helm_repo(ctx: ExecutionContext) -> bool:
505
+ """Adiciona e atualiza o repositório Helm."""
506
+ if ctx.dry_run:
507
+ typer.echo(" [3/5] [dry-run] Adicionando repositório Helm jetstack...")
508
+ return True
509
+
510
+ logger.info("Adicionando repositório Helm jetstack")
511
+ typer.echo(" [3/5] Adicionando repositório Helm jetstack...")
512
+
513
+ try:
514
+ start = time.time()
515
+
516
+ # Adiciona repo
517
+ result = subprocess.run(
518
+ ["helm", "repo", "add", "jetstack", CHART_REPO, "--force-update"],
519
+ capture_output=True,
520
+ text=True,
521
+ timeout=60,
522
+ )
523
+
524
+ if result.returncode != 0:
525
+ logger.error(f"Falha ao adicionar repo: {result.stderr}")
526
+ typer.secho(f" ✗ Falha ao adicionar repo: {result.stderr[:100]}", fg=typer.colors.RED)
527
+ return False
528
+
529
+ elapsed_add = time.time() - start
530
+ logger.info(f"Repo adicionado em {elapsed_add:.2f}s")
531
+ typer.echo(f" Repo adicionado ({elapsed_add:.2f}s)")
532
+
533
+ # Atualiza repo
534
+ typer.echo(" Atualizando índice do repositório...")
535
+ start = time.time()
536
+
537
+ result = subprocess.run(
538
+ ["helm", "repo", "update", "jetstack"],
539
+ capture_output=True,
540
+ text=True,
541
+ timeout=120,
542
+ )
543
+
544
+ elapsed_update = time.time() - start
545
+
546
+ if result.returncode != 0:
547
+ logger.error(f"Falha ao atualizar repo: {result.stderr}")
548
+ typer.secho(f" ✗ Falha ao atualizar repo: {result.stderr[:100]}", fg=typer.colors.RED)
549
+ return False
550
+
551
+ logger.info(f"Repo atualizado em {elapsed_update:.2f}s")
552
+ typer.secho(f" ✓ Repositório configurado ({elapsed_add + elapsed_update:.2f}s total)", fg=typer.colors.GREEN)
553
+ return True
554
+
555
+ except subprocess.TimeoutExpired:
556
+ logger.error("Timeout ao configurar repositório Helm")
557
+ typer.secho(" ✗ Timeout ao configurar repositório (>60s)", fg=typer.colors.RED)
558
+ return False
559
+ except Exception as e:
560
+ logger.error(f"Erro ao configurar repo: {e}")
561
+ typer.secho(f" ✗ Erro: {e}", fg=typer.colors.RED)
464
562
  return False
465
563
 
466
564
 
565
+ def _run_helm_install(ctx: ExecutionContext) -> bool:
566
+ """Executa o helm upgrade --install."""
567
+ if ctx.dry_run:
568
+ typer.echo(" [4/5] [dry-run] Executando helm upgrade --install...")
569
+ return True
570
+
571
+ logger.info("Executando helm upgrade --install cert-manager")
572
+ typer.echo(" [4/5] Executando helm upgrade --install cert-manager...")
573
+ typer.echo(" (isso pode levar vários minutos)")
574
+
575
+ cmd = [
576
+ "helm", "upgrade", "--install", "cert-manager", "jetstack/cert-manager",
577
+ "-n", NAMESPACE,
578
+ "--create-namespace",
579
+ "--set", "installCRDs=true",
580
+ "--set", "webhook.timeoutSeconds=30",
581
+ "--set", "startupapicheck.timeout=5m",
582
+ "--set", "startupapicheck.enabled=true",
583
+ "--set", "webhook.replicaCount=1",
584
+ "--set", "cainjector.replicaCount=1",
585
+ "--wait",
586
+ "--timeout", "15m",
587
+ "--debug", # Mais logs
588
+ ]
589
+
590
+ logger.info(f"Comando: {' '.join(cmd)}")
591
+
592
+ try:
593
+ start = time.time()
594
+
595
+ # Executa com output em tempo real para ver progresso
596
+ process = subprocess.Popen(
597
+ cmd,
598
+ stdout=subprocess.PIPE,
599
+ stderr=subprocess.STDOUT,
600
+ text=True,
601
+ )
602
+
603
+ output_lines = []
604
+ last_log_time = time.time()
605
+
606
+ while True:
607
+ line = process.stdout.readline()
608
+ if not line and process.poll() is not None:
609
+ break
610
+ if line:
611
+ output_lines.append(line)
612
+ logger.debug(line.strip())
613
+
614
+ # Mostra progresso a cada 30s
615
+ if time.time() - last_log_time > 30:
616
+ elapsed = int(time.time() - start)
617
+ typer.echo(f" ... ainda instalando ({elapsed}s)")
618
+ last_log_time = time.time()
619
+
620
+ elapsed = time.time() - start
621
+ return_code = process.poll()
622
+
623
+ if return_code == 0:
624
+ logger.info(f"Helm install concluído em {elapsed:.2f}s")
625
+ typer.secho(f" ✓ Helm install concluído ({elapsed:.2f}s)", fg=typer.colors.GREEN)
626
+ return True
627
+ else:
628
+ output = "".join(output_lines[-20:]) # Últimas 20 linhas
629
+ logger.error(f"Helm install falhou (código {return_code}): {output}")
630
+ typer.secho(f" ✗ Helm install falhou (código {return_code})", fg=typer.colors.RED)
631
+
632
+ # Mostra as últimas linhas do erro
633
+ typer.echo("\n Últimas linhas do log:")
634
+ for line in output_lines[-10:]:
635
+ typer.echo(f" {line.strip()}")
636
+
637
+ return False
638
+
639
+ except Exception as e:
640
+ logger.error(f"Erro durante helm install: {e}")
641
+ typer.secho(f" ✗ Erro: {e}", fg=typer.colors.RED)
642
+ return False
643
+
644
+
645
+ def _verify_installation(ctx: ExecutionContext) -> bool:
646
+ """Verifica se os pods estão rodando."""
647
+ if ctx.dry_run:
648
+ typer.echo(" [5/5] [dry-run] Verificando instalação...")
649
+ return True
650
+
651
+ logger.info("Verificando pods do cert-manager")
652
+ typer.echo(" [5/5] Verificando pods do cert-manager...")
653
+
654
+ try:
655
+ result = subprocess.run(
656
+ ["kubectl", "get", "pods", "-n", NAMESPACE, "-o", "wide"],
657
+ capture_output=True,
658
+ text=True,
659
+ timeout=30,
660
+ )
661
+
662
+ if result.returncode == 0:
663
+ typer.echo(f"\n{result.stdout}")
664
+
665
+ # Verifica se todos estão Running
666
+ if "Running" in result.stdout and "0/1" not in result.stdout:
667
+ logger.info("Todos os pods estão Running")
668
+ typer.secho(" ✓ Todos os pods estão Running", fg=typer.colors.GREEN)
669
+ return True
670
+ else:
671
+ logger.warning("Alguns pods podem não estar prontos")
672
+ typer.secho(" ⚠ Alguns pods podem não estar prontos", fg=typer.colors.YELLOW)
673
+ return True # Não falha, o wait_for_webhook vai verificar
674
+ else:
675
+ logger.error(f"Erro ao verificar pods: {result.stderr}")
676
+ return False
677
+
678
+ except Exception as e:
679
+ logger.error(f"Erro ao verificar pods: {e}")
680
+ return False
681
+
682
+
683
+ def _install_cert_manager_helm(ctx: ExecutionContext) -> bool:
684
+ """Instala cert-manager via Helm com logs detalhados."""
685
+ typer.secho("\n📦 Instalando cert-manager via Helm...", fg=typer.colors.CYAN, bold=True)
686
+ logger.info("Iniciando instalação do cert-manager")
687
+
688
+ start_total = time.time()
689
+
690
+ # Etapa 1: Testa conectividade com repo Helm
691
+ if not _test_helm_repo_connectivity(ctx):
692
+ typer.secho(
693
+ "\n⚠ Problema de conectividade com o repositório Helm.",
694
+ fg=typer.colors.YELLOW,
695
+ )
696
+ typer.echo(" Teste manual: curl -sI https://charts.jetstack.io/index.yaml")
697
+ if not typer.confirm("Tentar instalar mesmo assim?", default=False):
698
+ return False
699
+
700
+ # Etapa 2: Testa conectividade com registry de imagens
701
+ _test_image_registry_connectivity(ctx)
702
+
703
+ # Etapa 3: Adiciona e atualiza repo Helm
704
+ if not _add_helm_repo(ctx):
705
+ return False
706
+
707
+ # Etapa 4: Executa helm install
708
+ if not _run_helm_install(ctx):
709
+ _show_diagnostic_info(ctx)
710
+ return False
711
+
712
+ # Etapa 5: Verifica instalação
713
+ _verify_installation(ctx)
714
+
715
+ elapsed_total = time.time() - start_total
716
+ logger.info(f"Instalação do cert-manager concluída em {elapsed_total:.2f}s")
717
+ typer.secho(f"\n✓ Instalação concluída em {elapsed_total:.2f}s", fg=typer.colors.GREEN)
718
+
719
+ return True
720
+
721
+
722
+ def _show_diagnostic_info(ctx: ExecutionContext) -> None:
723
+ """Mostra informações de diagnóstico quando falha."""
724
+ if ctx.dry_run:
725
+ return
726
+
727
+ typer.secho("\n🔍 Informações de diagnóstico:", fg=typer.colors.YELLOW, bold=True)
728
+
729
+ # Pods
730
+ typer.echo("\n Pods:")
731
+ try:
732
+ result = subprocess.run(
733
+ ["kubectl", "get", "pods", "-n", NAMESPACE, "-o", "wide"],
734
+ capture_output=True,
735
+ text=True,
736
+ timeout=15,
737
+ )
738
+ if result.stdout:
739
+ for line in result.stdout.strip().split("\n"):
740
+ typer.echo(f" {line}")
741
+ except Exception:
742
+ typer.echo(" (não foi possível obter pods)")
743
+
744
+ # Eventos recentes
745
+ typer.echo("\n Eventos recentes:")
746
+ try:
747
+ result = subprocess.run(
748
+ ["kubectl", "get", "events", "-n", NAMESPACE, "--sort-by=.lastTimestamp"],
749
+ capture_output=True,
750
+ text=True,
751
+ timeout=15,
752
+ )
753
+ if result.stdout:
754
+ lines = result.stdout.strip().split("\n")
755
+ for line in lines[-10:]: # Últimos 10 eventos
756
+ typer.echo(f" {line[:120]}")
757
+ except Exception:
758
+ typer.echo(" (não foi possível obter eventos)")
759
+
760
+ # Helm status
761
+ typer.echo("\n Helm release status:")
762
+ try:
763
+ result = subprocess.run(
764
+ ["helm", "status", "cert-manager", "-n", NAMESPACE],
765
+ capture_output=True,
766
+ text=True,
767
+ timeout=15,
768
+ )
769
+ if result.stdout:
770
+ for line in result.stdout.strip().split("\n")[:15]:
771
+ typer.echo(f" {line}")
772
+ except Exception:
773
+ typer.echo(" (não foi possível obter status do Helm)")
774
+
775
+
467
776
  def _apply_manifest_with_retry(
468
777
  manifest: str,
469
778
  ctx: ExecutionContext,
File without changes
File without changes
File without changes
raijin_server/utils.py CHANGED
@@ -299,6 +299,210 @@ def helm_repo_update(ctx: ExecutionContext) -> None:
299
299
  run_cmd(["helm", "repo", "update"], ctx)
300
300
 
301
301
 
302
+ def _get_helm_release_status(release: str, namespace: str) -> str:
303
+ """Retorna status do release Helm (lowercased) ou string vazia se nao existir."""
304
+ try:
305
+ import json
306
+ result = subprocess.run(
307
+ ["helm", "status", release, "-n", namespace, "-o", "json"],
308
+ capture_output=True,
309
+ text=True,
310
+ timeout=30,
311
+ )
312
+ if result.returncode != 0 or not result.stdout:
313
+ return ""
314
+ data = json.loads(result.stdout)
315
+ return str(data.get("info", {}).get("status", "")).lower()
316
+ except Exception:
317
+ return ""
318
+
319
+
320
+ def _get_helm_release_history(release: str, namespace: str) -> list:
321
+ """Retorna histórico do release Helm."""
322
+ try:
323
+ import json
324
+ result = subprocess.run(
325
+ ["helm", "history", release, "-n", namespace, "-o", "json"],
326
+ capture_output=True,
327
+ text=True,
328
+ timeout=30,
329
+ )
330
+ if result.returncode != 0 or not result.stdout:
331
+ return []
332
+ return json.loads(result.stdout)
333
+ except Exception:
334
+ return []
335
+
336
+
337
+ def _diagnose_helm_release(release: str, namespace: str) -> None:
338
+ """Mostra diagnóstico detalhado de um release Helm."""
339
+ typer.secho(f"\n🔍 Diagnóstico do release '{release}':", fg=typer.colors.YELLOW)
340
+
341
+ # Status atual
342
+ status = _get_helm_release_status(release, namespace)
343
+ typer.echo(f" Status atual: {status or '(não encontrado)'}")
344
+
345
+ # Histórico
346
+ history = _get_helm_release_history(release, namespace)
347
+ if history:
348
+ typer.echo(f" Histórico ({len(history)} revisões):")
349
+ for rev in history[-5:]: # Últimas 5 revisões
350
+ typer.echo(f" Rev {rev.get('revision')}: {rev.get('status')} - {rev.get('description', '')[:50]}")
351
+
352
+ # Secrets do Helm (onde guarda estado)
353
+ try:
354
+ result = subprocess.run(
355
+ ["kubectl", "get", "secrets", "-n", namespace, "-l", f"name={release},owner=helm", "-o", "name"],
356
+ capture_output=True,
357
+ text=True,
358
+ timeout=15,
359
+ )
360
+ if result.stdout.strip():
361
+ secrets = result.stdout.strip().split("\n")
362
+ typer.echo(f" Secrets do Helm: {len(secrets)}")
363
+ for s in secrets[-5:]:
364
+ typer.echo(f" {s}")
365
+ except Exception:
366
+ pass
367
+
368
+ # Pods relacionados
369
+ try:
370
+ result = subprocess.run(
371
+ ["kubectl", "get", "pods", "-n", namespace, "-o", "wide", "--no-headers"],
372
+ capture_output=True,
373
+ text=True,
374
+ timeout=15,
375
+ )
376
+ if result.stdout.strip():
377
+ typer.echo(" Pods:")
378
+ for line in result.stdout.strip().split("\n")[:5]:
379
+ typer.echo(f" {line}")
380
+ except Exception:
381
+ pass
382
+
383
+
384
+ def _force_cleanup_helm_release(release: str, namespace: str) -> bool:
385
+ """Limpeza forçada de release Helm travado - remove secrets diretamente."""
386
+ typer.secho(f" Limpeza forçada do release '{release}'...", fg=typer.colors.YELLOW)
387
+ logger.warning(f"Executando limpeza forçada do release {release} em {namespace}")
388
+
389
+ try:
390
+ # 1. Primeiro tenta uninstall normal com --no-hooks (pula hooks que podem estar travando)
391
+ result = subprocess.run(
392
+ ["helm", "uninstall", release, "-n", namespace, "--no-hooks", "--wait", "--timeout", "2m"],
393
+ capture_output=True,
394
+ text=True,
395
+ timeout=150,
396
+ )
397
+
398
+ if result.returncode == 0:
399
+ typer.secho(f" ✓ Release removido via helm uninstall", fg=typer.colors.GREEN)
400
+ time.sleep(3)
401
+ return True
402
+
403
+ # 2. Se falhou, remove os secrets do Helm diretamente
404
+ typer.echo(" Helm uninstall falhou, removendo secrets diretamente...")
405
+ logger.warning("Removendo secrets do Helm diretamente")
406
+
407
+ # Lista secrets do Helm para este release
408
+ result = subprocess.run(
409
+ ["kubectl", "get", "secrets", "-n", namespace, "-l", f"name={release},owner=helm", "-o", "name"],
410
+ capture_output=True,
411
+ text=True,
412
+ timeout=15,
413
+ )
414
+
415
+ if result.stdout.strip():
416
+ secrets = result.stdout.strip().split("\n")
417
+ for secret in secrets:
418
+ secret_name = secret.replace("secret/", "")
419
+ subprocess.run(
420
+ ["kubectl", "delete", "secret", secret_name, "-n", namespace],
421
+ capture_output=True,
422
+ timeout=30,
423
+ )
424
+ typer.echo(f" Removido: {secret_name}")
425
+
426
+ time.sleep(3)
427
+ typer.secho(f" ✓ Secrets do Helm removidos", fg=typer.colors.GREEN)
428
+ return True
429
+ else:
430
+ typer.echo(" Nenhum secret do Helm encontrado")
431
+ return True
432
+
433
+ except Exception as e:
434
+ logger.error(f"Erro na limpeza forçada: {e}")
435
+ typer.secho(f" ✗ Erro na limpeza: {e}", fg=typer.colors.RED)
436
+ return False
437
+
438
+
439
+ def _cleanup_pending_helm_release(release: str, namespace: str, ctx: ExecutionContext) -> None:
440
+ """Remove release Helm em estado pendente que bloqueia novas operacoes."""
441
+ if ctx.dry_run:
442
+ return
443
+
444
+ status = _get_helm_release_status(release, namespace)
445
+ if not status:
446
+ return
447
+
448
+ # Estados que bloqueiam: pending-install, pending-upgrade, pending-rollback
449
+ if not status.startswith("pending"):
450
+ return
451
+
452
+ typer.secho(
453
+ f"\n⚠ Release '{release}' em estado '{status}' - bloqueando novas operações",
454
+ fg=typer.colors.YELLOW,
455
+ )
456
+
457
+ # Mostra diagnóstico
458
+ _diagnose_helm_release(release, namespace)
459
+
460
+ typer.echo("\n Tentando recuperar...")
461
+
462
+ # 1. Tenta rollback primeiro (funciona para pending-upgrade)
463
+ if status == "pending-upgrade":
464
+ typer.echo(" Tentando rollback para versão anterior...")
465
+ result = subprocess.run(
466
+ ["helm", "rollback", release, "-n", namespace, "--wait", "--timeout", "2m"],
467
+ capture_output=True,
468
+ text=True,
469
+ timeout=150,
470
+ )
471
+
472
+ if result.returncode == 0:
473
+ new_status = _get_helm_release_status(release, namespace)
474
+ if not new_status.startswith("pending"):
475
+ typer.secho(f" ✓ Rollback bem-sucedido (status: {new_status})", fg=typer.colors.GREEN)
476
+ return
477
+
478
+ typer.echo(" Rollback não resolveu...")
479
+
480
+ # 2. Tenta uninstall normal
481
+ typer.echo(" Tentando helm uninstall...")
482
+ result = subprocess.run(
483
+ ["helm", "uninstall", release, "-n", namespace, "--wait", "--timeout", "3m"],
484
+ capture_output=True,
485
+ text=True,
486
+ timeout=200,
487
+ )
488
+
489
+ if result.returncode == 0:
490
+ typer.secho(f" ✓ Release removido com sucesso", fg=typer.colors.GREEN)
491
+ time.sleep(3)
492
+ return
493
+
494
+ # 3. Se ainda falhou, força limpeza
495
+ typer.echo(" Uninstall normal falhou, tentando limpeza forçada...")
496
+ _force_cleanup_helm_release(release, namespace)
497
+
498
+ # Verifica resultado final
499
+ final_status = _get_helm_release_status(release, namespace)
500
+ if final_status:
501
+ typer.secho(f" ⚠ Release ainda existe com status: {final_status}", fg=typer.colors.YELLOW)
502
+ else:
503
+ typer.secho(f" ✓ Release '{release}' limpo com sucesso", fg=typer.colors.GREEN)
504
+
505
+
302
506
  def helm_upgrade_install(
303
507
  release: str,
304
508
  chart: str,
@@ -311,9 +515,16 @@ def helm_upgrade_install(
311
515
  create_namespace: bool = True,
312
516
  extra_args: list[str] | None = None,
313
517
  ) -> None:
314
- """Executa helm upgrade --install com opcoes comuns."""
518
+ """Executa helm upgrade --install com opcoes comuns.
519
+
520
+ Automaticamente detecta e limpa releases em estado pendente antes de instalar.
521
+ """
315
522
 
316
523
  ensure_tool("helm", ctx, install_hint="Instale helm ou habilite dry-run para so visualizar.")
524
+
525
+ # Limpa releases pendentes antes de tentar instalar
526
+ _cleanup_pending_helm_release(release, namespace, ctx)
527
+
317
528
  if repo and repo_url:
318
529
  helm_repo_add(repo, repo_url, ctx)
319
530
  helm_repo_update(ctx)
@@ -328,7 +539,21 @@ def helm_upgrade_install(
328
539
  cmd.extend(["--set", value])
329
540
  if extra_args:
330
541
  cmd.extend(extra_args)
331
- run_cmd(cmd, ctx)
542
+
543
+ try:
544
+ run_cmd(cmd, ctx)
545
+ except Exception as e:
546
+ err_text = str(e).lower()
547
+ # Se falhou por operacao em progresso, tenta limpar e reinstalar uma vez
548
+ if "another operation" in err_text and "in progress" in err_text:
549
+ typer.secho(
550
+ f"⚠ Helm detectou operacao pendente em '{release}'. Limpando e tentando novamente...",
551
+ fg=typer.colors.YELLOW,
552
+ )
553
+ _cleanup_pending_helm_release(release, namespace, ctx)
554
+ run_cmd(cmd, ctx)
555
+ else:
556
+ raise
332
557
 
333
558
 
334
559
  def kubectl_apply(target: str, ctx: ExecutionContext) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: raijin-server
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: CLI para automacao de setup e hardening de servidores Ubuntu Server.
5
5
  Home-page: https://example.com/raijin-server
6
6
  Author: Equipe Raijin
@@ -37,6 +37,14 @@ CLI em Python (Typer) para automatizar setup e hardening de servidores Ubuntu Se
37
37
 
38
38
  **✨ Versão Auditada e Resiliente para Produção**
39
39
 
40
+ ## Links úteis
41
+
42
+ - Repositório: https://github.com/rafaelluisdacostacoelho/raijin-server
43
+ - Documentação completa: [docs/INFRASTRUCTURE_GUIDE.md](docs/INFRASTRUCTURE_GUIDE.md)
44
+ - Arquitetura: [ARCHITECTURE.md](ARCHITECTURE.md)
45
+ - Auditoria: [AUDIT.md](AUDIT.md)
46
+ - Segurança: [SECURITY.md](SECURITY.md)
47
+
40
48
  ## Destaques
41
49
 
42
50
  - ✅ **Validações de Pré-requisitos**: OS, espaço em disco, memória, conectividade, ambiente Python (venv)
@@ -405,6 +413,32 @@ pytest
405
413
  ruff check src tests
406
414
  ```
407
415
 
416
+ ## Publicar no PyPI (Twine)
417
+
418
+ O Twine é a ferramenta oficial para enviar pacotes Python ao PyPI com upload seguro (HTTPS e checagem de hash). Use sempre um token de API.
419
+
420
+ Passo a passo:
421
+ ```bash
422
+ # 1) Gere artefatos
423
+ python -m build --sdist --wheel --outdir dist/
424
+
425
+ # 2) Configure o token (crie em https://pypi.org/manage/account/token/)
426
+ export TWINE_USERNAME=__token__
427
+ export TWINE_PASSWORD="<seu-token>"
428
+
429
+ # 3) Envie para o PyPI
430
+ python -m twine upload dist/*
431
+
432
+ # 4) Verifique instalação
433
+ python -m pip install -U raijin-server
434
+ raijin-server --version
435
+ ```
436
+
437
+ Boas práticas:
438
+ - Use venv dedicado para publicar (`python -m venv ~/.venvs/publish && source ~/.venvs/publish/bin/activate`).
439
+ - Nunca commite ou exponha o token; mantenha em variável de ambiente/secret manager.
440
+ - Sempre suba primeiro para TestPyPI se quiser validar (`--repository testpypi`).
441
+
408
442
  ## Acesso remoto seguro (VPN + SSH)
409
443
 
410
444
  Execute `raijin-server ssh-hardening` para aplicar as politicas abaixo automaticamente e `raijin-server vpn` para subir o servidor WireGuard com um cliente inicial. Use `--dry-run` se quiser apenas revisar os comandos.
@@ -2,13 +2,13 @@ raijin_server/__init__.py,sha256=7-69Vj-HYrv98hWrKmwDqDQ-ehtTqJebx1JeP4St6Q4,94
2
2
  raijin_server/cli.py,sha256=PfuIXc-pw1yZtJzCrxDVSWSsPAVBt9wqZBF-dWh6mwo,19274
3
3
  raijin_server/config.py,sha256=Dta2CS1d6RgNiQ84P6dTXk98boFrjzuvhs_fCdlm0I4,4810
4
4
  raijin_server/healthchecks.py,sha256=BJyWyUDtEswEblvGwWMejtMnsUb8kJcULVdS9iycrcc,14565
5
- raijin_server/utils.py,sha256=oQM-NGL_kmlNZejFvxXk85MI_WkcxNfwaw5LeAsKUFU,11476
5
+ raijin_server/utils.py,sha256=Gs182mcLVM3ClCADFIK9Qi1fQA7BfunaTu0ie-8pAvo,19692
6
6
  raijin_server/validators.py,sha256=qOZMHgwjHogVf17UPlxfUCpQd9qAGQW7tycd8mUvnEs,9404
7
7
  raijin_server/modules/__init__.py,sha256=e_IbkhLGPcF8to9QUmIESP6fpcTOYcIhaXLKIvqRJMY,920
8
8
  raijin_server/modules/apokolips_demo.py,sha256=8ltsXRbVDwlDwLMIvh02NG-FeAfBWw_v6lh7IGOyNqs,13725
9
9
  raijin_server/modules/bootstrap.py,sha256=oVIGNRW_JbgY8zXNHGAIP0vGbbHNHyQexthxo5zhbcw,9762
10
10
  raijin_server/modules/calico.py,sha256=a8N7YYv7NoaspPKdhRtwHy3V2mM4cP5xA1H8BwslB18,4139
11
- raijin_server/modules/cert_manager.py,sha256=3aXK2ivh0eCFLMllpWjUWS36UA3sWplP40daQRfWv14,34393
11
+ raijin_server/modules/cert_manager.py,sha256=Kb8N60j3BDjkNS8t8aTsdsKy5syRWobccP3PBpv-Q8E,45887
12
12
  raijin_server/modules/essentials.py,sha256=2xUXCyCQtFGd2DnCKV81N1R6bEJqH8zaet8mLovtQ1I,689
13
13
  raijin_server/modules/firewall.py,sha256=h6AISqiZeTinVT7BjmQIS872qRAFZJLg7meqlth3cfw,757
14
14
  raijin_server/modules/full_install.py,sha256=aR3yOuD7y0KLI20eMrxuFBNrWWn7JMpI4HFKNizEF3o,7464
@@ -36,9 +36,9 @@ raijin_server/scripts/checklist.sh,sha256=j6E0Kmk1EfjLvKK1VpCqzXJAXI_7Bm67LK4ndy
36
36
  raijin_server/scripts/install.sh,sha256=IZOTujOSGmKpznwgL59picsQNVzYkai6FtfFS3Klf34,3908
37
37
  raijin_server/scripts/log_size_metric.sh,sha256=rC2Ck4xnYVJV4Qymu24-indC8bkzfZs4FBqqxGPRl1I,1143
38
38
  raijin_server/scripts/pre-deploy-check.sh,sha256=naPUgKjnKgsh-eGDH2623C7zcr9VjDEw1H0lfYaXW8c,4853
39
- raijin_server-0.2.4.dist-info/licenses/LICENSE,sha256=kJsMCjOiRZE0AQNtxWqBa32z9kMAaF4EUxyHj3hKaJo,1105
40
- raijin_server-0.2.4.dist-info/METADATA,sha256=4X4baNp5EyOCEl916XlHFbXtd25KWwhtwPky5nzT0lU,17772
41
- raijin_server-0.2.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
42
- raijin_server-0.2.4.dist-info/entry_points.txt,sha256=3ZvxDX4pvcjkIRsXAJ69wIfVmKa78LKo-C3QhqN2KVM,56
43
- raijin_server-0.2.4.dist-info/top_level.txt,sha256=Yz1xneCRtsZOzbPIcTAcrSxd-1p80pohMXYAZ74dpok,14
44
- raijin_server-0.2.4.dist-info/RECORD,,
39
+ raijin_server-0.2.6.dist-info/licenses/LICENSE,sha256=kJsMCjOiRZE0AQNtxWqBa32z9kMAaF4EUxyHj3hKaJo,1105
40
+ raijin_server-0.2.6.dist-info/METADATA,sha256=KXv3RV6GSO2qQJ85n_SFJP6h10rbph0WbTJ611fG-M4,18925
41
+ raijin_server-0.2.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
42
+ raijin_server-0.2.6.dist-info/entry_points.txt,sha256=3ZvxDX4pvcjkIRsXAJ69wIfVmKa78LKo-C3QhqN2KVM,56
43
+ raijin_server-0.2.6.dist-info/top_level.txt,sha256=Yz1xneCRtsZOzbPIcTAcrSxd-1p80pohMXYAZ74dpok,14
44
+ raijin_server-0.2.6.dist-info/RECORD,,