raijin-server 0.3.6__py3-none-any.whl → 0.3.8__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.
@@ -1,414 +0,0 @@
1
- """Provisiona uma landing page tema Apokolips para validar ingress."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- from pathlib import Path
7
- from textwrap import dedent, indent
8
-
9
- import typer
10
-
11
- from raijin_server.utils import ExecutionContext, ensure_tool, run_cmd, write_file
12
-
13
- NAMESPACE = "apokolips-demo"
14
- TMP_MANIFEST = Path("/tmp/raijin-apokolips.yaml")
15
- DEFAULT_HOST = "apokolips.raijin.local"
16
- HTML_TEMPLATE = dedent(
17
- """
18
- <!DOCTYPE html>
19
- <html lang="pt-BR">
20
- <head>
21
- <meta charset="UTF-8" />
22
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
23
- <title>Apokolips Signal Check</title>
24
- <link rel="preconnect" href="https://fonts.googleapis.com">
25
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
26
- <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600&family=Unica+One&display=swap" rel="stylesheet">
27
- <style>
28
- :root {
29
- --lava: #ff4d00;
30
- --ember: #ff9500;
31
- --ash: #2c2c34;
32
- --void: #050007;
33
- --smoke: #a0a3b1;
34
- }
35
- * {
36
- box-sizing: border-box;
37
- }
38
- body {
39
- margin: 0;
40
- padding: 0;
41
- min-height: 100vh;
42
- font-family: 'Space Grotesk', sans-serif;
43
- color: #f7f7ff;
44
- background: radial-gradient(circle at top, rgba(255,77,0,0.45), rgba(5,0,7,0.9)), #050007;
45
- display: flex;
46
- align-items: center;
47
- justify-content: center;
48
- overflow: hidden;
49
- }
50
- .atmosphere {
51
- position: absolute;
52
- inset: 0;
53
- background: url('https://www.transparenttextures.com/patterns/asfalt-dark.png');
54
- opacity: 0.35;
55
- mix-blend-mode: screen;
56
- pointer-events: none;
57
- }
58
- .container {
59
- width: min(960px, 90vw);
60
- background: linear-gradient(135deg, rgba(20,22,35,0.9), rgba(10,12,22,0.95));
61
- border: 1px solid rgba(255,255,255,0.08);
62
- border-radius: 28px;
63
- padding: 48px;
64
- position: relative;
65
- overflow: hidden;
66
- box-shadow: 0 40px 120px rgba(0,0,0,0.55);
67
- animation: rise 1.2s ease forwards;
68
- }
69
- .container::before, .container::after {
70
- content: '';
71
- position: absolute;
72
- width: 320px;
73
- height: 320px;
74
- border-radius: 50%;
75
- background: radial-gradient(circle, rgba(255,77,0,0.45), transparent 60%);
76
- filter: blur(20px);
77
- z-index: 0;
78
- }
79
- .container::before {
80
- top: -120px;
81
- right: -60px;
82
- }
83
- .container::after {
84
- bottom: -140px;
85
- left: -80px;
86
- background: radial-gradient(circle, rgba(255,149,0,0.4), transparent 60%);
87
- }
88
- @keyframes rise {
89
- from { transform: translateY(40px); opacity: 0; }
90
- to { transform: translateY(0); opacity: 1; }
91
- }
92
- h1 {
93
- font-family: 'Unica One', sans-serif;
94
- font-size: clamp(3rem, 5vw, 4.5rem);
95
- letter-spacing: 0.08em;
96
- text-transform: uppercase;
97
- color: var(--lava);
98
- margin: 0 0 12px;
99
- z-index: 1;
100
- }
101
- .subhead {
102
- font-size: 1.15rem;
103
- color: var(--smoke);
104
- letter-spacing: 0.05em;
105
- margin-bottom: 32px;
106
- text-transform: uppercase;
107
- }
108
- .panels {
109
- display: grid;
110
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
111
- gap: 24px;
112
- z-index: 1;
113
- }
114
- .panel {
115
- background: rgba(12,14,24,0.85);
116
- border-radius: 18px;
117
- padding: 20px;
118
- border: 1px solid rgba(255,255,255,0.05);
119
- }
120
- .panel h2 {
121
- font-size: 0.95rem;
122
- text-transform: uppercase;
123
- letter-spacing: 0.08em;
124
- color: var(--ember);
125
- margin: 0 0 12px;
126
- }
127
- .status {
128
- display: flex;
129
- flex-direction: column;
130
- gap: 6px;
131
- font-size: 1rem;
132
- color: var(--smoke);
133
- }
134
- .status span::before {
135
- content: '●';
136
- margin-right: 8px;
137
- color: var(--lava);
138
- }
139
- .cta {
140
- margin-top: 36px;
141
- display: flex;
142
- flex-wrap: wrap;
143
- gap: 18px;
144
- z-index: 1;
145
- }
146
- a.button {
147
- background: linear-gradient(135deg, var(--lava), var(--ember));
148
- color: #050007;
149
- text-decoration: none;
150
- padding: 14px 26px;
151
- border-radius: 999px;
152
- font-weight: 600;
153
- letter-spacing: 0.08em;
154
- text-transform: uppercase;
155
- transition: transform 0.2s ease, box-shadow 0.2s ease;
156
- }
157
- a.button:hover {
158
- transform: translateY(-2px);
159
- box-shadow: 0 20px 35px rgba(255,77,0,0.25);
160
- }
161
- pre {
162
- background: rgba(5,0,7,0.75);
163
- border-radius: 16px;
164
- padding: 16px;
165
- color: var(--smoke);
166
- font-size: 0.95rem;
167
- line-height: 1.5;
168
- overflow-x: auto;
169
- }
170
- footer {
171
- margin-top: 28px;
172
- font-size: 0.85rem;
173
- letter-spacing: 0.04em;
174
- color: rgba(247,247,255,0.7);
175
- }
176
- @media (max-width: 640px) {
177
- .container {
178
- padding: 32px 24px;
179
- }
180
- .cta {
181
- flex-direction: column;
182
- }
183
- }
184
- </style>
185
- </head>
186
- <body>
187
- <div class="atmosphere"></div>
188
- <main class="container">
189
- <h1>Apokolips Online</h1>
190
- <p class="subhead">Canal de prova para ingress / load balancer</p>
191
- <section class="panels">
192
- <article class="panel">
193
- <h2>Estado</h2>
194
- <div class="status">
195
- <span>Pods sincronizados</span>
196
- <span>ConfigMap montado</span>
197
- <span>Ingress publicado</span>
198
- </div>
199
- </article>
200
- <article class="panel">
201
- <h2>Checklist</h2>
202
- <div class="status">
203
- <span>DNS aponta para balanceador</span>
204
- <span>TLS (opcional) emitido</span>
205
- <span>Firewall libera HTTP/S</span>
206
- </div>
207
- </article>
208
- <article class="panel">
209
- <h2>Debug Rápido</h2>
210
- <pre>kubectl -n apokolips-demo get all
211
- kubectl -n apokolips-demo describe ingress apokolips-demo
212
- curl -H "Host: SEU_HOST" https://LB_IP/</pre>
213
- </article>
214
- </section>
215
- <div class="cta">
216
- <a class="button" href="https://github.com/darkseid/raijin-server" target="_blank" rel="noreferrer noopener">Docs Raijin</a>
217
- <a class="button" href="https://status.cloudflare.com/" target="_blank" rel="noreferrer noopener">Status Externo</a>
218
- </div>
219
- <footer>
220
- Se esta página carregou via ingress, seu cluster respondeu à chamada de Apokolips.
221
- </footer>
222
- </main>
223
- </body>
224
- </html>
225
- """
226
- )
227
-
228
-
229
- def _resolve_host() -> str:
230
- env_host = os.environ.get("APOKOLIPS_HOST")
231
- if env_host:
232
- return env_host.strip()
233
- return typer.prompt("Host (FQDN) para o ingress", default=DEFAULT_HOST).strip()
234
-
235
-
236
- def _resolve_tls_secret() -> str | None:
237
- env_secret = os.environ.get("APOKOLIPS_TLS_SECRET")
238
- if env_secret:
239
- return env_secret.strip()
240
- use_tls = typer.confirm("Deseja referenciar um Secret TLS existente?", default=False)
241
- if not use_tls:
242
- return None
243
- secret = typer.prompt("Nome do Secret TLS", default="apokolips-demo-tls")
244
- return secret.strip() or None
245
-
246
-
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:
256
- html_block = indent(HTML_TEMPLATE.strip("\n"), " " * 4)
257
- tls_block = ""
258
- if tls_secret:
259
- tls_block = (
260
- " tls:\n"
261
- " - hosts:\n"
262
- f" - {host}\n"
263
- f" secretName: {tls_secret}\n"
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"""
279
-
280
- template = """\
281
- apiVersion: v1
282
- kind: Namespace
283
- metadata:
284
- name: {namespace}
285
- ---
286
- apiVersion: v1
287
- kind: ConfigMap
288
- metadata:
289
- name: apokolips-html
290
- namespace: {namespace}
291
- data:
292
- index.html: |
293
- __HTML__
294
- ---
295
- apiVersion: apps/v1
296
- kind: Deployment
297
- metadata:
298
- name: apokolips-demo
299
- namespace: {namespace}
300
- labels:
301
- app: apokolips-demo
302
- spec:
303
- replicas: 1
304
- selector:
305
- matchLabels:
306
- app: apokolips-demo
307
- template:
308
- metadata:
309
- labels:
310
- app: apokolips-demo
311
- spec:
312
- containers:
313
- - name: apokolips-web
314
- image: nginx:1.25
315
- ports:
316
- - containerPort: 80
317
- resources:
318
- limits:
319
- cpu: 100m
320
- memory: 128Mi
321
- requests:
322
- cpu: 50m
323
- memory: 64Mi
324
- volumeMounts:
325
- - name: site
326
- mountPath: /usr/share/nginx/html
327
- readOnly: true
328
- volumes:
329
- - name: site
330
- configMap:
331
- name: apokolips-html
332
- ---
333
- apiVersion: v1
334
- kind: Service
335
- metadata:
336
- name: apokolips-demo
337
- namespace: {namespace}
338
- spec:
339
- type: ClusterIP
340
- selector:
341
- app: apokolips-demo
342
- ports:
343
- - port: 80
344
- targetPort: 80
345
- ---
346
- apiVersion: networking.k8s.io/v1
347
- kind: Ingress
348
- metadata:
349
- name: apokolips-demo
350
- namespace: {namespace}
351
- annotations:
352
- traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
353
- spec:
354
- ingressClassName: traefik
355
- rules:
356
- - host: {host}
357
- http:
358
- paths:
359
- - path: /
360
- pathType: Prefix
361
- backend:
362
- service:
363
- name: apokolips-demo
364
- port:
365
- number: 80__IP_RULE__
366
- __TLS__
367
- """
368
-
369
- manifest = template.format(namespace=NAMESPACE, host=host)
370
- manifest = manifest.replace("__HTML__", html_block)
371
- manifest = manifest.replace("__IP_RULE__", ip_rule)
372
- manifest = manifest.replace("__TLS__", tls_block.rstrip())
373
- return f"{manifest.strip()}\n"
374
-
375
-
376
- def run(ctx: ExecutionContext) -> None:
377
- ensure_tool("kubectl", ctx, install_hint="Instale kubectl para aplicar o manifesto do site.")
378
- host = _resolve_host()
379
- tls_secret = _resolve_tls_secret()
380
- ip_access = _resolve_ip_access()
381
- manifest = _build_manifest(host, tls_secret, ip_access)
382
-
383
- typer.echo("Gerando manifesto Apokolips...")
384
- write_file(TMP_MANIFEST, manifest, ctx)
385
-
386
- try:
387
- run_cmd(["kubectl", "apply", "-f", str(TMP_MANIFEST)], ctx)
388
- finally:
389
- TMP_MANIFEST.unlink(missing_ok=True)
390
-
391
- typer.secho("\nLanding page implantada!", fg=typer.colors.GREEN, bold=True)
392
- typer.echo(f" • Namespace: {NAMESPACE}")
393
- typer.echo(f" • Host: {host}")
394
- if tls_secret:
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
-
399
- typer.echo("\nTestes sugeridos:")
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>/")
406
- typer.echo(f" kubectl -n {NAMESPACE} get ingress {NAMESPACE}")
407
- typer.echo(f" kubectl -n {NAMESPACE} get pods")
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
-
413
- typer.echo("\nPara remover:")
414
- typer.echo(f" kubectl delete namespace {NAMESPACE}")