raijin-server 0.2.5__py3-none-any.whl → 0.2.7__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.
@@ -18,6 +18,8 @@ from enum import Enum
18
18
  from pathlib import Path
19
19
  from typing import Callable, Optional, List
20
20
 
21
+ import os
22
+
21
23
  import typer
22
24
 
23
25
  from raijin_server.utils import (
@@ -34,11 +36,14 @@ CHART_REPO = "https://charts.jetstack.io"
34
36
  CHART_NAME = "cert-manager"
35
37
  NAMESPACE = "cert-manager"
36
38
  MANIFEST_PATH = Path("/tmp/raijin-cert-manager-issuer.yaml")
39
+ HELM_DATA_DIR = Path("/tmp/raijin-helm")
40
+ HELM_REPO_CONFIG = HELM_DATA_DIR / "repositories.yaml"
41
+ HELM_REPO_CACHE = HELM_DATA_DIR / "cache"
37
42
 
38
- # Timeouts mais generosos para ambientes lentos
39
- WEBHOOK_READY_TIMEOUT = 600 # 10 minutos
40
- POD_READY_TIMEOUT = 300 # 5 minutos
41
- CRD_READY_TIMEOUT = 180 # 3 minutos
43
+ # Timeouts enxutos (falha rápida em redes rápidas)
44
+ WEBHOOK_READY_TIMEOUT = 240 # 4 minutos
45
+ POD_READY_TIMEOUT = 180 # 3 minutos
46
+ CRD_READY_TIMEOUT = 120 # 2 minutos
42
47
 
43
48
 
44
49
  class DNSProvider(str, Enum):
@@ -82,6 +87,17 @@ def _get_acme_server(staging: bool) -> str:
82
87
  return "https://acme-v02.api.letsencrypt.org/directory"
83
88
 
84
89
 
90
+ def _helm_env() -> dict:
91
+ """Garante diretórios de cache/config do Helm isolados em /tmp para evitar erros de permissão."""
92
+ HELM_DATA_DIR.mkdir(parents=True, exist_ok=True)
93
+ HELM_REPO_CACHE.mkdir(parents=True, exist_ok=True)
94
+ return {
95
+ **os.environ,
96
+ "HELM_REPOSITORY_CONFIG": str(HELM_REPO_CONFIG),
97
+ "HELM_REPOSITORY_CACHE": str(HELM_REPO_CACHE),
98
+ }
99
+
100
+
85
101
  # =============================================================================
86
102
  # Builders de Manifests YAML
87
103
  # =============================================================================
@@ -432,37 +448,355 @@ def _wait_for_webhook_ready(ctx: ExecutionContext, timeout: int = WEBHOOK_READY_
432
448
  # Instalação e Configuração
433
449
  # =============================================================================
434
450
 
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)
451
+ def _test_helm_repo_connectivity(ctx: ExecutionContext) -> bool:
452
+ """Testa conectividade com o repositório Helm antes de instalar."""
453
+ if ctx.dry_run:
454
+ return True
455
+
456
+ logger.info("Testando conectividade com charts.jetstack.io")
457
+ typer.echo(" [1/5] Testando conectividade com charts.jetstack.io...")
438
458
 
439
459
  try:
440
- # O helm_upgrade_install agora limpa releases pendentes automaticamente
441
- helm_upgrade_install(
442
- release="cert-manager",
443
- chart=CHART_NAME,
444
- namespace=NAMESPACE,
445
- ctx=ctx,
446
- repo="jetstack",
447
- repo_url=CHART_REPO,
448
- create_namespace=True,
449
- extra_args=[
450
- "--set", "installCRDs=true",
451
- "--set", "webhook.timeoutSeconds=30",
452
- "--set", "startupapicheck.timeout=5m",
453
- "--set", "startupapicheck.enabled=true",
454
- # Aumenta recursos para ambientes mais lentos
455
- "--set", "webhook.replicaCount=1",
456
- "--set", "cainjector.replicaCount=1",
457
- "--wait", # Espera o Helm considerar o release deployed
458
- "--timeout", "10m",
459
- ],
460
+ start = time.time()
461
+ result = subprocess.run(
462
+ ["curl", "-sI", "--connect-timeout", "15", f"{CHART_REPO}/index.yaml"],
463
+ capture_output=True,
464
+ text=True,
465
+ timeout=20,
466
+ )
467
+ elapsed = time.time() - start
468
+
469
+ if result.returncode == 0 and "200" in result.stdout:
470
+ logger.info(f"Repositório Helm acessível em {elapsed:.2f}s")
471
+ typer.secho(f" ✓ Repositório Helm acessível ({elapsed:.2f}s)", fg=typer.colors.GREEN)
472
+ return True
473
+ else:
474
+ logger.error(f"Repositório retornou erro: {result.stdout[:200]}")
475
+ typer.secho(f" ✗ Repositório retornou erro: {result.stdout[:100]}", fg=typer.colors.RED)
476
+ return False
477
+ except subprocess.TimeoutExpired:
478
+ logger.error("Timeout ao conectar com charts.jetstack.io")
479
+ typer.secho(" ✗ Timeout ao conectar com charts.jetstack.io (>15s)", fg=typer.colors.RED)
480
+ return False
481
+ except Exception as e:
482
+ logger.error(f"Erro de conectividade: {e}")
483
+ typer.secho(f" ✗ Erro de conectividade: {e}", fg=typer.colors.RED)
484
+ return False
485
+
486
+
487
+ def _test_image_registry_connectivity(ctx: ExecutionContext) -> bool:
488
+ """Testa conectividade com o registry de imagens."""
489
+ if ctx.dry_run:
490
+ return True
491
+
492
+ logger.info("Testando conectividade com quay.io (registry de imagens)")
493
+ typer.echo(" [2/5] Testando conectividade com quay.io (registry de imagens)...")
494
+
495
+ try:
496
+ start = time.time()
497
+ result = subprocess.run(
498
+ ["curl", "-sI", "--connect-timeout", "15", "https://quay.io/v2/"],
499
+ capture_output=True,
500
+ text=True,
501
+ timeout=20,
502
+ )
503
+ elapsed = time.time() - start
504
+
505
+ # quay.io retorna 401 para /v2/ sem auth, mas isso significa que está acessível
506
+ if result.returncode == 0 and ("200" in result.stdout or "401" in result.stdout):
507
+ logger.info(f"Registry quay.io acessível em {elapsed:.2f}s")
508
+ typer.secho(f" ✓ Registry quay.io acessível ({elapsed:.2f}s)", fg=typer.colors.GREEN)
509
+ return True
510
+ else:
511
+ logger.warning(f"Registry pode estar inacessível: {result.stdout[:100]}")
512
+ typer.secho(f" ⚠ Registry pode estar inacessível", fg=typer.colors.YELLOW)
513
+ return True # Não bloqueia, apenas avisa
514
+ except Exception as e:
515
+ logger.warning(f"Não foi possível testar registry: {e}")
516
+ typer.secho(f" ⚠ Não foi possível testar registry: {e}", fg=typer.colors.YELLOW)
517
+ return True # Não bloqueia
518
+
519
+
520
+ def _add_helm_repo(ctx: ExecutionContext) -> bool:
521
+ """Adiciona e atualiza o repositório Helm."""
522
+ if ctx.dry_run:
523
+ typer.echo(" [3/5] [dry-run] Adicionando repositório Helm jetstack...")
524
+ return True
525
+
526
+ logger.info("Adicionando repositório Helm jetstack")
527
+ typer.echo(" [3/5] Adicionando repositório Helm jetstack...")
528
+
529
+ try:
530
+ start = time.time()
531
+
532
+ # Adiciona repo
533
+ result = subprocess.run(
534
+ ["helm", "repo", "add", "jetstack", CHART_REPO, "--force-update"],
535
+ capture_output=True,
536
+ text=True,
537
+ timeout=60,
538
+ env=_helm_env(),
539
+ )
540
+
541
+ if result.returncode != 0:
542
+ logger.error(f"Falha ao adicionar repo: {result.stderr}")
543
+ typer.secho(f" ✗ Falha ao adicionar repo: {result.stderr[:100]}", fg=typer.colors.RED)
544
+ return False
545
+
546
+ elapsed_add = time.time() - start
547
+ logger.info(f"Repo adicionado em {elapsed_add:.2f}s")
548
+ typer.echo(f" Repo adicionado ({elapsed_add:.2f}s)")
549
+
550
+ # Atualiza repo
551
+ typer.echo(" Atualizando índice do repositório...")
552
+ start = time.time()
553
+
554
+ result = subprocess.run(
555
+ ["helm", "repo", "update", "jetstack"],
556
+ capture_output=True,
557
+ text=True,
558
+ timeout=120,
559
+ env=_helm_env(),
560
+ )
561
+
562
+ elapsed_update = time.time() - start
563
+
564
+ if result.returncode != 0:
565
+ logger.error(f"Falha ao atualizar repo: {result.stderr}")
566
+ typer.secho(f" ✗ Falha ao atualizar repo: {result.stderr[:100]}", fg=typer.colors.RED)
567
+ return False
568
+
569
+ logger.info(f"Repo atualizado em {elapsed_update:.2f}s")
570
+ typer.secho(f" ✓ Repositório configurado ({elapsed_add + elapsed_update:.2f}s total)", fg=typer.colors.GREEN)
571
+ return True
572
+
573
+ except subprocess.TimeoutExpired:
574
+ logger.error("Timeout ao configurar repositório Helm")
575
+ typer.secho(" ✗ Timeout ao configurar repositório (>60s)", fg=typer.colors.RED)
576
+ return False
577
+ except Exception as e:
578
+ logger.error(f"Erro ao configurar repo: {e}")
579
+ typer.secho(f" ✗ Erro: {e}", fg=typer.colors.RED)
580
+ return False
581
+
582
+
583
+ def _run_helm_install(ctx: ExecutionContext, attempt: int = 1) -> bool:
584
+ """Executa o helm upgrade --install, com uma tentativa de retry para repo/config."""
585
+ if ctx.dry_run:
586
+ typer.echo(" [4/5] [dry-run] Executando helm upgrade --install...")
587
+ return True
588
+
589
+ logger.info("Executando helm upgrade --install cert-manager")
590
+ typer.echo(" [4/5] Executando helm upgrade --install cert-manager...")
591
+ typer.echo(" (isso pode levar vários minutos)")
592
+
593
+ cmd = [
594
+ "helm", "upgrade", "--install", "cert-manager", "jetstack/cert-manager",
595
+ "--repo", CHART_REPO,
596
+ "-n", NAMESPACE,
597
+ "--create-namespace",
598
+ "--set", "installCRDs=true",
599
+ "--set", "webhook.timeoutSeconds=30",
600
+ "--set", "startupapicheck.timeout=5m",
601
+ "--set", "startupapicheck.enabled=true",
602
+ "--set", "webhook.replicaCount=1",
603
+ "--set", "cainjector.replicaCount=1",
604
+ "--wait",
605
+ "--timeout", "15m",
606
+ "--debug", # Mais logs
607
+ ]
608
+
609
+ logger.info(f"Comando: {' '.join(cmd)}")
610
+
611
+ try:
612
+ start = time.time()
613
+
614
+ # Executa com output em tempo real para ver progresso
615
+ process = subprocess.Popen(
616
+ cmd,
617
+ stdout=subprocess.PIPE,
618
+ stderr=subprocess.STDOUT,
619
+ text=True,
620
+ env=_helm_env(),
460
621
  )
622
+
623
+ output_lines = []
624
+ last_log_time = time.time()
625
+
626
+ while True:
627
+ line = process.stdout.readline()
628
+ if not line and process.poll() is not None:
629
+ break
630
+ if line:
631
+ output_lines.append(line)
632
+ logger.debug(line.strip())
633
+
634
+ # Mostra progresso a cada 30s
635
+ if time.time() - last_log_time > 30:
636
+ elapsed = int(time.time() - start)
637
+ typer.echo(f" ... ainda instalando ({elapsed}s)")
638
+ last_log_time = time.time()
639
+
640
+ elapsed = time.time() - start
641
+ return_code = process.poll()
642
+
643
+ if return_code == 0:
644
+ logger.info(f"Helm install concluído em {elapsed:.2f}s")
645
+ typer.secho(f" ✓ Helm install concluído ({elapsed:.2f}s)", fg=typer.colors.GREEN)
646
+ return True
647
+ else:
648
+ output = "".join(output_lines[-20:]) # Últimas 20 linhas
649
+ logger.error(f"Helm install falhou (código {return_code}): {output}")
650
+ typer.secho(f" ✗ Helm install falhou (código {return_code})", fg=typer.colors.RED)
651
+
652
+ needs_repo_retry = "repo jetstack not found" in output.lower() or "repositories.yaml" in output.lower()
653
+ if needs_repo_retry and attempt == 1:
654
+ typer.echo(" → Reconfigurando repositório Helm e tentando novamente...")
655
+ if _add_helm_repo(ctx):
656
+ return _run_helm_install(ctx, attempt=2)
657
+
658
+ # Mostra as últimas linhas do erro
659
+ typer.echo("\n Últimas linhas do log:")
660
+ for line in output_lines[-10:]:
661
+ typer.echo(f" {line.strip()}")
662
+
663
+ return False
664
+
665
+ except Exception as e:
666
+ logger.error(f"Erro durante helm install: {e}")
667
+ typer.secho(f" ✗ Erro: {e}", fg=typer.colors.RED)
668
+ return False
669
+
670
+
671
+ def _verify_installation(ctx: ExecutionContext) -> bool:
672
+ """Verifica se os pods estão rodando."""
673
+ if ctx.dry_run:
674
+ typer.echo(" [5/5] [dry-run] Verificando instalação...")
461
675
  return True
676
+
677
+ logger.info("Verificando pods do cert-manager")
678
+ typer.echo(" [5/5] Verificando pods do cert-manager...")
679
+
680
+ try:
681
+ result = subprocess.run(
682
+ ["kubectl", "get", "pods", "-n", NAMESPACE, "-o", "wide"],
683
+ capture_output=True,
684
+ text=True,
685
+ timeout=30,
686
+ )
687
+
688
+ if result.returncode == 0:
689
+ typer.echo(f"\n{result.stdout}")
690
+
691
+ # Verifica se todos estão Running
692
+ if "Running" in result.stdout and "0/1" not in result.stdout:
693
+ logger.info("Todos os pods estão Running")
694
+ typer.secho(" ✓ Todos os pods estão Running", fg=typer.colors.GREEN)
695
+ return True
696
+ else:
697
+ logger.warning("Alguns pods podem não estar prontos")
698
+ typer.secho(" ⚠ Alguns pods podem não estar prontos", fg=typer.colors.YELLOW)
699
+ return True # Não falha, o wait_for_webhook vai verificar
700
+ else:
701
+ logger.error(f"Erro ao verificar pods: {result.stderr}")
702
+ return False
703
+
462
704
  except Exception as e:
463
- typer.secho(f"Erro na instalação do Helm: {e}", fg=typer.colors.RED)
464
- ctx.errors.append(f"cert-manager: falha na instalação Helm - {e}")
705
+ logger.error(f"Erro ao verificar pods: {e}")
706
+ return False
707
+
708
+
709
+ def _install_cert_manager_helm(ctx: ExecutionContext) -> bool:
710
+ """Instala cert-manager via Helm com logs detalhados."""
711
+ typer.secho("\n📦 Instalando cert-manager via Helm...", fg=typer.colors.CYAN, bold=True)
712
+ logger.info("Iniciando instalação do cert-manager")
713
+
714
+ start_total = time.time()
715
+
716
+ # Etapa 1: Testa conectividade com repo Helm
717
+ if not _test_helm_repo_connectivity(ctx):
718
+ typer.secho(
719
+ "\n⚠ Problema de conectividade com o repositório Helm.",
720
+ fg=typer.colors.YELLOW,
721
+ )
722
+ typer.echo(" Teste manual: curl -sI https://charts.jetstack.io/index.yaml")
723
+ if not typer.confirm("Tentar instalar mesmo assim?", default=False):
724
+ return False
725
+
726
+ # Etapa 2: Testa conectividade com registry de imagens
727
+ _test_image_registry_connectivity(ctx)
728
+
729
+ # Etapa 3: Adiciona e atualiza repo Helm
730
+ if not _add_helm_repo(ctx):
731
+ return False
732
+
733
+ # Etapa 4: Executa helm install
734
+ if not _run_helm_install(ctx):
735
+ _show_diagnostic_info(ctx)
465
736
  return False
737
+
738
+ # Etapa 5: Verifica instalação
739
+ _verify_installation(ctx)
740
+
741
+ elapsed_total = time.time() - start_total
742
+ logger.info(f"Instalação do cert-manager concluída em {elapsed_total:.2f}s")
743
+ typer.secho(f"\n✓ Instalação concluída em {elapsed_total:.2f}s", fg=typer.colors.GREEN)
744
+
745
+ return True
746
+
747
+
748
+ def _show_diagnostic_info(ctx: ExecutionContext) -> None:
749
+ """Mostra informações de diagnóstico quando falha."""
750
+ if ctx.dry_run:
751
+ return
752
+
753
+ typer.secho("\n🔍 Informações de diagnóstico:", fg=typer.colors.YELLOW, bold=True)
754
+
755
+ # Pods
756
+ typer.echo("\n Pods:")
757
+ try:
758
+ result = subprocess.run(
759
+ ["kubectl", "get", "pods", "-n", NAMESPACE, "-o", "wide"],
760
+ capture_output=True,
761
+ text=True,
762
+ timeout=15,
763
+ )
764
+ if result.stdout:
765
+ for line in result.stdout.strip().split("\n"):
766
+ typer.echo(f" {line}")
767
+ except Exception:
768
+ typer.echo(" (não foi possível obter pods)")
769
+
770
+ # Eventos recentes
771
+ typer.echo("\n Eventos recentes:")
772
+ try:
773
+ result = subprocess.run(
774
+ ["kubectl", "get", "events", "-n", NAMESPACE, "--sort-by=.lastTimestamp"],
775
+ capture_output=True,
776
+ text=True,
777
+ timeout=15,
778
+ )
779
+ if result.stdout:
780
+ lines = result.stdout.strip().split("\n")
781
+ for line in lines[-10:]: # Últimos 10 eventos
782
+ typer.echo(f" {line[:120]}")
783
+ except Exception:
784
+ typer.echo(" (não foi possível obter eventos)")
785
+
786
+ # Helm status
787
+ typer.echo("\n Helm release status:")
788
+ try:
789
+ result = subprocess.run(
790
+ ["helm", "status", "cert-manager", "-n", NAMESPACE],
791
+ capture_output=True,
792
+ text=True,
793
+ timeout=15,
794
+ )
795
+ if result.stdout:
796
+ for line in result.stdout.strip().split("\n")[:15]:
797
+ typer.echo(f" {line}")
798
+ except Exception:
799
+ typer.echo(" (não foi possível obter status do Helm)")
466
800
 
467
801
 
468
802
  def _apply_manifest_with_retry(