raijin-server 0.2.17__py3-none-any.whl → 0.2.19__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.17"
3
+ __version__ = "0.2.19"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -1,6 +1,7 @@
1
1
  """Provisiona MetalLB (L2) com pool de IPs para LoadBalancer em ambientes bare metal."""
2
2
 
3
3
  import socket
4
+ import time
4
5
 
5
6
  import typer
6
7
 
@@ -28,46 +29,168 @@ def _detect_node_name(ctx: ExecutionContext) -> str:
28
29
  return socket.gethostname()
29
30
 
30
31
 
31
- def _rollout_wait(kind: str, name: str, ctx: ExecutionContext) -> None:
32
- run_cmd([
33
- "kubectl",
34
- "-n",
35
- "metallb-system",
36
- "rollout",
37
- "status",
38
- f"{kind}/{name}",
39
- "--timeout",
40
- "180s",
41
- ], ctx, check=False)
32
+ def _uninstall_metallb(ctx: ExecutionContext) -> None:
33
+ """Remove instalacao anterior do MetalLB completamente."""
34
+ typer.echo("Removendo instalacao anterior do MetalLB...")
35
+
36
+ # Helm uninstall
37
+ run_cmd(
38
+ ["helm", "uninstall", "metallb", "-n", "metallb-system"],
39
+ ctx,
40
+ check=False,
41
+ )
42
+
43
+ # Remove CRDs que podem ficar orfaos
44
+ run_cmd(
45
+ ["kubectl", "delete", "crd",
46
+ "ipaddresspools.metallb.io",
47
+ "l2advertisements.metallb.io",
48
+ "bgpadvertisements.metallb.io",
49
+ "bgppeers.metallb.io",
50
+ "bfdprofiles.metallb.io",
51
+ "communities.metallb.io",
52
+ "servicel2statuses.metallb.io",
53
+ "--ignore-not-found"],
54
+ ctx,
55
+ check=False,
56
+ )
57
+
58
+ # Remove namespace se existir
59
+ run_cmd(
60
+ ["kubectl", "delete", "namespace", "metallb-system", "--ignore-not-found"],
61
+ ctx,
62
+ check=False,
63
+ )
64
+
65
+ # Aguarda limpeza
66
+ time.sleep(5)
42
67
 
43
68
 
44
- def _wait_webhook(ctx: ExecutionContext) -> None:
45
- # Descobre o nome do deployment do webhook (varia conforme chart), entao aguarda disponibilidade
69
+ def _check_existing_metallb(ctx: ExecutionContext) -> bool:
70
+ """Verifica se existe instalacao do MetalLB."""
46
71
  result = run_cmd(
47
- [
48
- "kubectl",
49
- "-n",
50
- "metallb-system",
51
- "get",
52
- "deploy",
53
- "-l",
54
- "app.kubernetes.io/component=webhook",
55
- "-o",
56
- "jsonpath={.items[0].metadata.name}",
57
- ],
72
+ ["helm", "status", "metallb", "-n", "metallb-system"],
58
73
  ctx,
59
74
  check=False,
60
75
  )
61
- if result.returncode == 0:
62
- name = (result.stdout or "").strip()
63
- if name:
64
- _rollout_wait("deployment", name, ctx)
76
+ return result.returncode == 0
77
+
78
+
79
+ def _wait_for_pods_running(ctx: ExecutionContext, timeout: int = 180) -> bool:
80
+ """Aguarda todos os pods do MetalLB estarem Running."""
81
+ typer.echo("Aguardando pods do MetalLB ficarem Running...")
82
+ deadline = time.time() + timeout
83
+
84
+ while time.time() < deadline:
85
+ # Verifica se ha pods pending ou em erro
86
+ result = run_cmd(
87
+ [
88
+ "kubectl", "-n", "metallb-system", "get", "pods",
89
+ "-o", "jsonpath={range .items[*]}{.metadata.name}:{.status.phase}\\n{end}",
90
+ ],
91
+ ctx,
92
+ check=False,
93
+ )
94
+
95
+ if result.returncode != 0:
96
+ time.sleep(5)
97
+ continue
98
+
99
+ output = (result.stdout or "").strip()
100
+ if not output:
101
+ time.sleep(5)
102
+ continue
103
+
104
+ pods = []
105
+ for line in output.split("\n"):
106
+ if not line:
107
+ continue
108
+ # Split apenas na ultima ocorrencia de ":" para evitar problemas com nomes
109
+ parts = line.rsplit(":", 1)
110
+ if len(parts) == 2:
111
+ pods.append((parts[0], parts[1]))
112
+
113
+ all_running = all(phase == "Running" for _, phase in pods)
114
+ if all_running and pods:
115
+ typer.secho(f" Todos os {len(pods)} pods Running.", fg=typer.colors.GREEN)
116
+ return True
117
+
118
+ # Mostra status atual
119
+ pending = [name for name, phase in pods if phase != "Running"]
120
+ if pending:
121
+ typer.echo(f" Aguardando: {', '.join(pending[:3])}...")
122
+
123
+ time.sleep(10)
124
+
125
+ # Timeout - mostra diagnostico
126
+ typer.secho(" Timeout esperando pods. Diagnostico:", fg=typer.colors.YELLOW)
127
+ run_cmd(["kubectl", "-n", "metallb-system", "get", "pods", "-o", "wide"], ctx, check=False)
128
+ run_cmd(["kubectl", "-n", "metallb-system", "get", "events", "--sort-by=.lastTimestamp"], ctx, check=False)
129
+ return False
130
+
131
+
132
+ def _wait_for_webhook_ready(ctx: ExecutionContext, timeout: int = 120) -> bool:
133
+ """Aguarda webhook estar respondendo."""
134
+ typer.echo("Aguardando webhook do MetalLB...")
135
+ deadline = time.time() + timeout
136
+
137
+ while time.time() < deadline:
138
+ result = run_cmd(
139
+ [
140
+ "kubectl", "-n", "metallb-system", "get", "endpoints",
141
+ "metallb-webhook-service", "-o", "jsonpath={.subsets[0].addresses[0].ip}",
142
+ ],
143
+ ctx,
144
+ check=False,
145
+ )
146
+ if result.returncode == 0 and (result.stdout or "").strip():
147
+ typer.secho(" Webhook disponivel.", fg=typer.colors.GREEN)
148
+ return True
149
+ time.sleep(5)
150
+
151
+ typer.secho(" Webhook nao ficou disponivel.", fg=typer.colors.YELLOW)
152
+ return False
153
+
154
+
155
+ def _apply_pool_with_retry(manifest: str, ctx: ExecutionContext, max_attempts: int = 12) -> bool:
156
+ """Aplica IPAddressPool/L2Advertisement com retry."""
157
+ typer.echo("Aplicando IPAddressPool e L2Advertisement...")
158
+
159
+ for attempt in range(1, max_attempts + 1):
160
+ result = run_cmd(
161
+ f"echo '{manifest}' | kubectl apply -f -",
162
+ ctx,
163
+ use_shell=True,
164
+ check=False,
165
+ )
166
+ if result.returncode == 0:
167
+ typer.secho(" Pool e L2Advertisement aplicados.", fg=typer.colors.GREEN)
168
+ return True
169
+
170
+ stderr = (result.stderr or "").lower()
171
+ if "webhook" in stderr or "connection refused" in stderr:
172
+ typer.echo(f" Webhook nao pronto, tentativa {attempt}/{max_attempts}...")
173
+ time.sleep(10)
174
+ else:
175
+ typer.secho(f" Erro: {result.stderr}", fg=typer.colors.RED)
176
+ return False
177
+
178
+ return False
65
179
 
66
180
 
67
181
  def run(ctx: ExecutionContext) -> None:
68
182
  require_root(ctx)
69
183
  typer.echo("Instalando MetalLB via Helm...")
70
184
 
185
+ # Prompt opcional de limpeza
186
+ if _check_existing_metallb(ctx):
187
+ cleanup = typer.confirm(
188
+ "Instalacao anterior do MetalLB detectada. Limpar antes de reinstalar?",
189
+ default=False,
190
+ )
191
+ if cleanup:
192
+ _uninstall_metallb(ctx)
193
+
71
194
  pool = typer.prompt(
72
195
  "Pool de IPs (range ou CIDR) para services LoadBalancer",
73
196
  default="192.168.1.100-192.168.1.250",
@@ -89,12 +212,12 @@ def run(ctx: ExecutionContext) -> None:
89
212
  "speaker.tolerations[1].key=node-role.kubernetes.io/master",
90
213
  "speaker.tolerations[1].operator=Exists",
91
214
  "speaker.tolerations[1].effect=NoSchedule",
92
- # Escapa a chave com ponto; evita map literal que quebra o schema do chart
215
+ # nodeSelector com chave escapada
93
216
  f"controller.nodeSelector.kubernetes\\.io/hostname={node_name}",
94
217
  f"speaker.nodeSelector.kubernetes\\.io/hostname={node_name}",
95
218
  ]
96
219
 
97
- # Instala control-plane + speaker
220
+ # Instala do zero
98
221
  helm_upgrade_install(
99
222
  release="metallb",
100
223
  chart="metallb",
@@ -105,13 +228,16 @@ def run(ctx: ExecutionContext) -> None:
105
228
  values=values,
106
229
  )
107
230
 
108
- # Espera recursos principais ficarem prontos
109
- _rollout_wait("deployment", "controller", ctx)
110
- _rollout_wait("daemonset", "speaker", ctx)
111
- _wait_webhook(ctx)
112
- run_cmd(["sleep", "5"], ctx, check=False) # pequeno buffer para webhook responder
231
+ # Aguarda pods estarem Running
232
+ if not _wait_for_pods_running(ctx):
233
+ ctx.errors.append("Pods do MetalLB nao subiram - verifique taints/recursos do cluster")
234
+ return
235
+
236
+ # Aguarda webhook
237
+ if not _wait_for_webhook_ready(ctx):
238
+ typer.secho("Continuando mesmo sem confirmacao do webhook...", fg=typer.colors.YELLOW)
113
239
 
114
- # Aplica IPAddressPool + L2Advertisement
240
+ # Aplica pool
115
241
  manifest = f"""
116
242
  apiVersion: metallb.io/v1beta1
117
243
  kind: IPAddressPool
@@ -132,10 +258,8 @@ spec:
132
258
  - raijin-pool
133
259
  """
134
260
 
135
- run_cmd(
136
- f"echo '{manifest}' | kubectl apply -f -",
137
- ctx,
138
- use_shell=True,
139
- )
261
+ if not _apply_pool_with_retry(manifest, ctx):
262
+ ctx.errors.append("Falha ao aplicar IPAddressPool/L2Advertisement")
263
+ return
140
264
 
141
- typer.secho("\n✓ MetalLB aplicado. Services LoadBalancer usarao o pool informado.", fg=typer.colors.GREEN, bold=True)
265
+ typer.secho("\n✓ MetalLB instalado. Services LoadBalancer usarao o pool informado.", fg=typer.colors.GREEN, bold=True)
@@ -7,6 +7,36 @@ import typer
7
7
  from raijin_server.utils import ExecutionContext, helm_upgrade_install, require_root, run_cmd
8
8
 
9
9
 
10
+ def _check_existing_traefik(ctx: ExecutionContext) -> bool:
11
+ """Verifica se existe instalacao do Traefik."""
12
+ result = run_cmd(
13
+ ["helm", "status", "traefik", "-n", "traefik"],
14
+ ctx,
15
+ check=False,
16
+ )
17
+ return result.returncode == 0
18
+
19
+
20
+ def _uninstall_traefik(ctx: ExecutionContext) -> None:
21
+ """Remove instalacao anterior do Traefik."""
22
+ import time
23
+ typer.echo("Removendo instalacao anterior do Traefik...")
24
+
25
+ run_cmd(
26
+ ["helm", "uninstall", "traefik", "-n", "traefik"],
27
+ ctx,
28
+ check=False,
29
+ )
30
+
31
+ run_cmd(
32
+ ["kubectl", "delete", "namespace", "traefik", "--ignore-not-found"],
33
+ ctx,
34
+ check=False,
35
+ )
36
+
37
+ time.sleep(5)
38
+
39
+
10
40
  def _detect_node_name(ctx: ExecutionContext) -> str:
11
41
  """Tenta obter o nome do node via kubectl; fallback para hostname local.
12
42
 
@@ -35,6 +65,15 @@ def run(ctx: ExecutionContext) -> None:
35
65
  require_root(ctx)
36
66
  typer.echo("Instalando Traefik via Helm...")
37
67
 
68
+ # Prompt opcional de limpeza
69
+ if _check_existing_traefik(ctx):
70
+ cleanup = typer.confirm(
71
+ "Instalacao anterior do Traefik detectada. Limpar antes de reinstalar?",
72
+ default=False,
73
+ )
74
+ if cleanup:
75
+ _uninstall_traefik(ctx)
76
+
38
77
  acme_email = typer.prompt("Email para ACME/Let's Encrypt", default="admin@example.com")
39
78
  dashboard_host = typer.prompt("Host para dashboard (opcional)", default="traefik.local")
40
79
 
@@ -56,7 +95,8 @@ def run(ctx: ExecutionContext) -> None:
56
95
  "tolerations[1].key=node-role.kubernetes.io/master",
57
96
  "tolerations[1].operator=Exists",
58
97
  "tolerations[1].effect=NoSchedule",
59
- f"nodeSelector.kubernetes.io/hostname={node_name}",
98
+ # Escapa chave com ponto para evitar parsing incorreto
99
+ f"nodeSelector.kubernetes\\.io/hostname={node_name}",
60
100
  ]
61
101
 
62
102
  if dashboard_host:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: raijin-server
3
- Version: 0.2.17
3
+ Version: 0.2.19
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
@@ -1,4 +1,4 @@
1
- raijin_server/__init__.py,sha256=Q3OdR-7R6DPbItTfAjyDCsaCREmTNc9lTmwRE4YNpoQ,95
1
+ raijin_server/__init__.py,sha256=0tXuzf-HrPX7SKpdPAcVDC_u8HnxXWAls7-ufyxhsu0,95
2
2
  raijin_server/cli.py,sha256=71nn7QN0f3MJkXcHr0STXmxljr-CaPibzOoiItbOT88,28571
3
3
  raijin_server/config.py,sha256=QNiEVvrbW56XgvNn5-h3bkJm46Xc8mjNqPbvixXD8N0,4829
4
4
  raijin_server/healthchecks.py,sha256=lzXdFw6S0hOYbUKbqksh4phb04lXgXdTspP1Dsz4dx8,15401
@@ -20,7 +20,7 @@ raijin_server/modules/kafka.py,sha256=bp8k_IhuAIO6dL0IpK1UxxLZoGih6nJp0ZnzwmiZEj
20
20
  raijin_server/modules/kong.py,sha256=2EZKYBmBhm_7Nduw9PWrvrekp0VCxQbc2gElpAJqKfg,491
21
21
  raijin_server/modules/kubernetes.py,sha256=waSf2cCVnLicN5o3M47MzMzmHHtvKeFXm1__8ynQzA0,11871
22
22
  raijin_server/modules/loki.py,sha256=erwFfSiSFOv-Ul3nFdrI2RElPYuqqBPBBa_MJAwyLys,676
23
- raijin_server/modules/metallb.py,sha256=g1VWxl5Cmal963nyiiobPYuq-OBKB454LI-U_sLVly4,4090
23
+ raijin_server/modules/metallb.py,sha256=LBavNo5NJn8CM5x2kVuUHbbsdXYp0fkm5oUWfTTqeio,8471
24
24
  raijin_server/modules/minio.py,sha256=BVvsEaJlJUV92_ep7pKsBhSYPjWZrDOB3J6XAWYAHYg,486
25
25
  raijin_server/modules/network.py,sha256=QRlYdcryCCPAWG3QQ_W7ld9gJgETI7H8gwntOU7UqFE,4818
26
26
  raijin_server/modules/observability_dashboards.py,sha256=fVz0WEOQrUTF5rJ__Nu_onyBuwL_exFmysWMmg8AE9w,7319
@@ -29,7 +29,7 @@ raijin_server/modules/prometheus.py,sha256=Rs9BREmaoKlyteNdAQZnSIeJfsRO0RQKyyL2g
29
29
  raijin_server/modules/sanitize.py,sha256=_RnWn1DUuNrzx3NnKEbMvf5iicgjiN_ubwT59e0rYWY,6040
30
30
  raijin_server/modules/secrets.py,sha256=xpV3gIMnwQdAI2j69Ck5daIK4wlYJA_1rkWTtSfVNk0,3715
31
31
  raijin_server/modules/ssh_hardening.py,sha256=oQdk-EVnEHNMKIWvoFuZzI4jK0nNO8IAY4hkB4pj8zw,4025
32
- raijin_server/modules/traefik.py,sha256=hHjWl8UQodm_wwVOp0TzeYCVf3SpHt_tbgQtvkJi8FY,2467
32
+ raijin_server/modules/traefik.py,sha256=3DAcdW19jBaA1qFuqfwf054bKQCC4htQrY6dXf_cC0k,3539
33
33
  raijin_server/modules/velero.py,sha256=_CV0QQnWr5L-CWXDOiD9Ef4J7GaQT-s9yNBwqp_FLOY,1395
34
34
  raijin_server/modules/vpn.py,sha256=hF-0vA17VKTxhQLDBSEeqI5aPQpiaaj4IpUf9l6lr64,8297
35
35
  raijin_server/scripts/__init__.py,sha256=deduGfHf8BMVWred4ux5LfBDT2NJ5XYeJAt2sDEU4qs,53
@@ -37,9 +37,9 @@ raijin_server/scripts/checklist.sh,sha256=j6E0Kmk1EfjLvKK1VpCqzXJAXI_7Bm67LK4ndy
37
37
  raijin_server/scripts/install.sh,sha256=Y1ickbQ4siQ0NIPs6UgrqUr8WWy7U0LHmaTQbEgavoI,3949
38
38
  raijin_server/scripts/log_size_metric.sh,sha256=Iv4SsX8AuCYRou-klYn32mX41xB6j0xJGLBO6riw4rU,1208
39
39
  raijin_server/scripts/pre-deploy-check.sh,sha256=XqMo7IMIpwUHF17YEmU0-cVmTDMoCGMBFnmS39FidI4,4912
40
- raijin_server-0.2.17.dist-info/licenses/LICENSE,sha256=kJsMCjOiRZE0AQNtxWqBa32z9kMAaF4EUxyHj3hKaJo,1105
41
- raijin_server-0.2.17.dist-info/METADATA,sha256=sfCzPvuuejO46PlXz6bZydpHNW7Jr3nav-J7UihcEOw,22476
42
- raijin_server-0.2.17.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
43
- raijin_server-0.2.17.dist-info/entry_points.txt,sha256=3ZvxDX4pvcjkIRsXAJ69wIfVmKa78LKo-C3QhqN2KVM,56
44
- raijin_server-0.2.17.dist-info/top_level.txt,sha256=Yz1xneCRtsZOzbPIcTAcrSxd-1p80pohMXYAZ74dpok,14
45
- raijin_server-0.2.17.dist-info/RECORD,,
40
+ raijin_server-0.2.19.dist-info/licenses/LICENSE,sha256=kJsMCjOiRZE0AQNtxWqBa32z9kMAaF4EUxyHj3hKaJo,1105
41
+ raijin_server-0.2.19.dist-info/METADATA,sha256=PeFtZI1UEbt-j_VTPVGQ2T_guRQvkrOlXUddK25CPoI,22476
42
+ raijin_server-0.2.19.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
43
+ raijin_server-0.2.19.dist-info/entry_points.txt,sha256=3ZvxDX4pvcjkIRsXAJ69wIfVmKa78LKo-C3QhqN2KVM,56
44
+ raijin_server-0.2.19.dist-info/top_level.txt,sha256=Yz1xneCRtsZOzbPIcTAcrSxd-1p80pohMXYAZ74dpok,14
45
+ raijin_server-0.2.19.dist-info/RECORD,,