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.
- raijin_server/__init__.py +1 -1
- raijin_server/cli.py +2 -8
- raijin_server/healthchecks.py +1 -55
- raijin_server/minio_utils.py +562 -0
- raijin_server/modules/__init__.py +1 -2
- raijin_server/modules/harbor.py +10 -26
- raijin_server/modules/secrets.py +86 -72
- raijin_server/modules/velero.py +49 -2
- raijin_server/validators.py +1 -1
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.8.dist-info}/METADATA +1 -1
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.8.dist-info}/RECORD +15 -15
- raijin_server/modules/apokolips_demo.py +0 -414
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.8.dist-info}/WHEEL +0 -0
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.8.dist-info}/entry_points.txt +0 -0
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.8.dist-info}/licenses/LICENSE +0 -0
- {raijin_server-0.3.6.dist-info → raijin_server-0.3.8.dist-info}/top_level.txt +0 -0
|
@@ -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}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|