granny-devops 0.17.4__tar.gz → 0.18.0__tar.gz
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.
- {granny_devops-0.17.4 → granny_devops-0.18.0}/PKG-INFO +14 -2
- {granny_devops-0.17.4 → granny_devops-0.18.0}/README.md +13 -1
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/__init__.py +1 -1
- granny_devops-0.18.0/granny/cli/knative.py +378 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/main.py +26 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/serverless.py +46 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/credentials/secrets.py +8 -1
- granny_devops-0.18.0/granny/knative/__init__.py +5 -0
- granny_devops-0.18.0/granny/knative/client.py +339 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/pyproject.toml +1 -1
- {granny_devops-0.17.4 → granny_devops-0.18.0}/.gitignore +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/LICENSE +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/airtable/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/airtable/client.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/analyze/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/analyze/costs.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/analyze/credits.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/analyze/gpu_pricing.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/analyze/gpus.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/analyze/kubernetes.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/analyze/lambdas.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/analyze/vpcs.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/authentik/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/authentik/client.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/authentik/provision.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/aws/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/aws/bedrock.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/aws/quota.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/_client.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/account.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/deployment.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/openai.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/quota.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/resourcegroup.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/role.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/sku.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/vm.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/azure/webapp.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cdn/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cdn/bunny.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/airtable.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/analyze.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/authentik.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/aws.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/azure.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/cdn.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/cloudflare.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/create.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/credentials.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/dns.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/docker.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/edge.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/elk.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/email.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/indexing.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/oauth.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/storage.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cli/zitadel.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cloudflare/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cloudflare/d1.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cloudflare/r2.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/cloudflare/workers.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/auto_certificate.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/cloudfront-security-headers.js +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/letsencrypt_cert.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/manage-dns.sh +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/manage_mailjet_contacts.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/registrars.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_aws_cloudfront.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_bunny_edge_script.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_bunny_storage.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_cognito_identity_pool.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_hetzner_bunny.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_mailjet_dns.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_private_cdn.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_s3_website.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_scaleway_container.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_scaleway_faas.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/setup_workmail.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/create/www-redirect-function.js +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/credentials/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/base.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/bunny.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/cloudflare.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/cloudns.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/desec.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/factory.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/hetzner.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/inwx.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/manual.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/dns/records.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/docker/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/docker/build_base.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/edge/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/edge/bunny.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/elk/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/elk/client.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/email/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/email/mailjet.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/email/mailjet_contacts.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/email/ses_forwarding.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/email/stalwart.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/email/workmail.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/indexing/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/indexing/google.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/indexing/indexnow.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/report.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/serverless/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/serverless/scaleway.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/storage/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/storage/aws.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/storage/bunny.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/storage/hetzner.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/zitadel/__init__.py +0 -0
- {granny_devops-0.17.4 → granny_devops-0.18.0}/granny/zitadel/client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: granny-devops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Cloud tools collection -- AWS infrastructure, CDN, and DevOps automation
|
|
5
5
|
Author-email: Martin Wieser <martin.wieser@pseekoo.com>
|
|
6
6
|
License: MIT License
|
|
@@ -219,6 +219,7 @@ Common keys:
|
|
|
219
219
|
| Cloudflare | `CLOUDFLARE_API_TOKEN` |
|
|
220
220
|
| Hetzner | `HETZNER_S3_ACCESS_KEY`, `HETZNER_S3_SECRET_KEY`, `HETZNER_DNS_API_TOKEN` |
|
|
221
221
|
| Scaleway | `SCW_ACCESS_KEY`, `SCW_SECRET_KEY`, `SCW_DEFAULT_PROJECT_ID` |
|
|
222
|
+
| Knative FaaS API | `OBACHAN_API_KEY`, `OBACHAN_API_BASE_URL` |
|
|
222
223
|
| Mailjet | `MAILJET_API_KEY`, `MAILJET_SECRET_KEY` |
|
|
223
224
|
| deSEC | `DESEC_API_TOKEN` |
|
|
224
225
|
| ClouDNS | `CLOUDNS_AUTH_ID`/`_PASSWORD` (or `_SUB_AUTH_ID`/`_SUB_AUTH_USER`) |
|
|
@@ -301,6 +302,16 @@ granny docker build-base --image myapp-base --hash-file requirements.txt
|
|
|
301
302
|
|
|
302
303
|
# Scaleway FaaS
|
|
303
304
|
granny serverless deploy my-fn --source-dir ./dist --namespace my-app
|
|
305
|
+
granny serverless delete-function my-fn --namespace my-app --yes
|
|
306
|
+
|
|
307
|
+
# Knative FaaS control API (`granny faas` is an alias)
|
|
308
|
+
granny knative list
|
|
309
|
+
granny knative deploy my-fn --image registry.example.com/my-fn:latest \
|
|
310
|
+
--env LOG_LEVEL=info --min-scale 0 --max-scale 5
|
|
311
|
+
granny knative scale my-fn --max-scale 10
|
|
312
|
+
granny knative logs my-fn --tail 100
|
|
313
|
+
granny knative invoke my-fn --method POST --path /process --body '{"job": 42}'
|
|
314
|
+
granny knative remove my-fn --yes
|
|
304
315
|
|
|
305
316
|
# Mailjet + WorkMail
|
|
306
317
|
granny email mailjet setup-dns example.com
|
|
@@ -391,7 +402,7 @@ granny indexing google https://example.com/job-posting --action URL_UPDATED
|
|
|
391
402
|
| CDN | Bunny |
|
|
392
403
|
| Edge scripting | Bunny |
|
|
393
404
|
| Object storage | AWS S3, Bunny Storage, Hetzner S3 |
|
|
394
|
-
| Serverless functions | Scaleway FaaS, Scaleway Containers |
|
|
405
|
+
| Serverless functions | Scaleway FaaS, Scaleway Containers, Knative FaaS control API |
|
|
395
406
|
| Workers / KV / D1 / R2 | Cloudflare |
|
|
396
407
|
| Record search and endpoint maintenance | Airtable |
|
|
397
408
|
| Email send infra | Mailjet, AWS SES, AWS WorkMail |
|
|
@@ -444,6 +455,7 @@ granny/
|
|
|
444
455
|
elk/ Elasticsearch / Kibana security user management
|
|
445
456
|
email/ Mailjet, WorkMail, SES forwarding
|
|
446
457
|
indexing/ IndexNow + Google Indexing API
|
|
458
|
+
knative/ Knative FaaS control API client
|
|
447
459
|
serverless/ Scaleway FaaS
|
|
448
460
|
storage/ Object storage (AWS / Bunny / Hetzner)
|
|
449
461
|
```
|
|
@@ -85,6 +85,7 @@ Common keys:
|
|
|
85
85
|
| Cloudflare | `CLOUDFLARE_API_TOKEN` |
|
|
86
86
|
| Hetzner | `HETZNER_S3_ACCESS_KEY`, `HETZNER_S3_SECRET_KEY`, `HETZNER_DNS_API_TOKEN` |
|
|
87
87
|
| Scaleway | `SCW_ACCESS_KEY`, `SCW_SECRET_KEY`, `SCW_DEFAULT_PROJECT_ID` |
|
|
88
|
+
| Knative FaaS API | `OBACHAN_API_KEY`, `OBACHAN_API_BASE_URL` |
|
|
88
89
|
| Mailjet | `MAILJET_API_KEY`, `MAILJET_SECRET_KEY` |
|
|
89
90
|
| deSEC | `DESEC_API_TOKEN` |
|
|
90
91
|
| ClouDNS | `CLOUDNS_AUTH_ID`/`_PASSWORD` (or `_SUB_AUTH_ID`/`_SUB_AUTH_USER`) |
|
|
@@ -167,6 +168,16 @@ granny docker build-base --image myapp-base --hash-file requirements.txt
|
|
|
167
168
|
|
|
168
169
|
# Scaleway FaaS
|
|
169
170
|
granny serverless deploy my-fn --source-dir ./dist --namespace my-app
|
|
171
|
+
granny serverless delete-function my-fn --namespace my-app --yes
|
|
172
|
+
|
|
173
|
+
# Knative FaaS control API (`granny faas` is an alias)
|
|
174
|
+
granny knative list
|
|
175
|
+
granny knative deploy my-fn --image registry.example.com/my-fn:latest \
|
|
176
|
+
--env LOG_LEVEL=info --min-scale 0 --max-scale 5
|
|
177
|
+
granny knative scale my-fn --max-scale 10
|
|
178
|
+
granny knative logs my-fn --tail 100
|
|
179
|
+
granny knative invoke my-fn --method POST --path /process --body '{"job": 42}'
|
|
180
|
+
granny knative remove my-fn --yes
|
|
170
181
|
|
|
171
182
|
# Mailjet + WorkMail
|
|
172
183
|
granny email mailjet setup-dns example.com
|
|
@@ -257,7 +268,7 @@ granny indexing google https://example.com/job-posting --action URL_UPDATED
|
|
|
257
268
|
| CDN | Bunny |
|
|
258
269
|
| Edge scripting | Bunny |
|
|
259
270
|
| Object storage | AWS S3, Bunny Storage, Hetzner S3 |
|
|
260
|
-
| Serverless functions | Scaleway FaaS, Scaleway Containers |
|
|
271
|
+
| Serverless functions | Scaleway FaaS, Scaleway Containers, Knative FaaS control API |
|
|
261
272
|
| Workers / KV / D1 / R2 | Cloudflare |
|
|
262
273
|
| Record search and endpoint maintenance | Airtable |
|
|
263
274
|
| Email send infra | Mailjet, AWS SES, AWS WorkMail |
|
|
@@ -310,6 +321,7 @@ granny/
|
|
|
310
321
|
elk/ Elasticsearch / Kibana security user management
|
|
311
322
|
email/ Mailjet, WorkMail, SES forwarding
|
|
312
323
|
indexing/ IndexNow + Google Indexing API
|
|
324
|
+
knative/ Knative FaaS control API client
|
|
313
325
|
serverless/ Scaleway FaaS
|
|
314
326
|
storage/ Object storage (AWS / Bunny / Hetzner)
|
|
315
327
|
```
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Knative function management commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
def knative() -> None:
|
|
13
|
+
"""Manage functions through a Knative FaaS control API."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Private helpers
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _guess_content_type(path: str) -> str:
|
|
22
|
+
"""Guess Content-Type from file extension."""
|
|
23
|
+
ext = path.rsplit(".", 1)[-1].lower() if "." in path else ""
|
|
24
|
+
return {
|
|
25
|
+
"html": "text/html; charset=utf-8",
|
|
26
|
+
"css": "text/css; charset=utf-8",
|
|
27
|
+
"js": "application/javascript; charset=utf-8",
|
|
28
|
+
"json": "application/json; charset=utf-8",
|
|
29
|
+
"png": "image/png",
|
|
30
|
+
"jpg": "image/jpeg",
|
|
31
|
+
"jpeg": "image/jpeg",
|
|
32
|
+
"gif": "image/gif",
|
|
33
|
+
"svg": "image/svg+xml",
|
|
34
|
+
"webp": "image/webp",
|
|
35
|
+
"ico": "image/x-icon",
|
|
36
|
+
"woff": "font/woff",
|
|
37
|
+
"woff2": "font/woff2",
|
|
38
|
+
"ttf": "font/ttf",
|
|
39
|
+
"xml": "application/xml",
|
|
40
|
+
"txt": "text/plain",
|
|
41
|
+
}.get(ext, "application/octet-stream")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Commands
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@knative.command("deploy")
|
|
50
|
+
@click.argument("name")
|
|
51
|
+
@click.option(
|
|
52
|
+
"--image", required=True, help="Container image ref (e.g. registry/fn:latest)"
|
|
53
|
+
)
|
|
54
|
+
@click.option(
|
|
55
|
+
"--env",
|
|
56
|
+
"-e",
|
|
57
|
+
"env_vars",
|
|
58
|
+
multiple=True,
|
|
59
|
+
help="Environment variable in KEY=VAL format. Repeatable.",
|
|
60
|
+
)
|
|
61
|
+
@click.option("--min-scale", default=0, show_default=True, type=int)
|
|
62
|
+
@click.option("--max-scale", default=5, show_default=True, type=int)
|
|
63
|
+
@click.option(
|
|
64
|
+
"--api-url",
|
|
65
|
+
envvar="OBACHAN_API_BASE_URL",
|
|
66
|
+
default=None,
|
|
67
|
+
help="FaaS control API base URL. Defaults to OBACHAN_API_BASE_URL env or vault.",
|
|
68
|
+
)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--api-key",
|
|
71
|
+
envvar="OBACHAN_API_KEY",
|
|
72
|
+
default=None,
|
|
73
|
+
help="API key. Defaults to OBACHAN_API_KEY env or vault.",
|
|
74
|
+
)
|
|
75
|
+
def knative_deploy(
|
|
76
|
+
name: str,
|
|
77
|
+
image: str,
|
|
78
|
+
env_vars: tuple,
|
|
79
|
+
min_scale: int,
|
|
80
|
+
max_scale: int,
|
|
81
|
+
api_url: str | None,
|
|
82
|
+
api_key: str | None,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Deploy (create or update) a Knative function."""
|
|
85
|
+
from granny.knative import KnativeClient
|
|
86
|
+
|
|
87
|
+
parsed: dict[str, str] = {}
|
|
88
|
+
for entry in env_vars:
|
|
89
|
+
k, sep, v = entry.partition("=")
|
|
90
|
+
if sep:
|
|
91
|
+
parsed[k.strip()] = v
|
|
92
|
+
else:
|
|
93
|
+
click.echo(f"Warning: skipping malformed env entry: {entry!r}", err=True)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
c = KnativeClient(base_url=api_url, api_key=api_key)
|
|
97
|
+
result = c.deploy(
|
|
98
|
+
name, image, env_vars=parsed, min_scale=min_scale, max_scale=max_scale
|
|
99
|
+
)
|
|
100
|
+
except (ValueError, RuntimeError) as exc:
|
|
101
|
+
click.echo(f"Error: {exc}", err=True)
|
|
102
|
+
raise SystemExit(1)
|
|
103
|
+
|
|
104
|
+
url = (result.get("status") or {}).get("url", "(not yet ready)")
|
|
105
|
+
click.echo(f"Deployed: {name}")
|
|
106
|
+
click.echo(f"URL: {url}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@knative.command("list")
|
|
110
|
+
@click.option("--api-url", envvar="OBACHAN_API_BASE_URL", default=None)
|
|
111
|
+
@click.option("--api-key", envvar="OBACHAN_API_KEY", default=None)
|
|
112
|
+
def knative_list(api_url: str | None, api_key: str | None) -> None:
|
|
113
|
+
"""List deployed Knative functions."""
|
|
114
|
+
from granny.knative import KnativeClient
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
c = KnativeClient(base_url=api_url, api_key=api_key)
|
|
118
|
+
items = c.list()
|
|
119
|
+
except (ValueError, RuntimeError) as exc:
|
|
120
|
+
click.echo(f"Error: {exc}", err=True)
|
|
121
|
+
raise SystemExit(1)
|
|
122
|
+
|
|
123
|
+
if not items:
|
|
124
|
+
click.echo("No functions deployed.")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
click.echo(f"{'NAME':<30} {'URL'}")
|
|
128
|
+
for item in items:
|
|
129
|
+
item_name = (item.get("metadata") or {}).get("name") or item.get("name") or "?"
|
|
130
|
+
url = (item.get("status") or {}).get("url", "-")
|
|
131
|
+
click.echo(f"{item_name:<30} {url}")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@knative.command("get")
|
|
135
|
+
@click.argument("name")
|
|
136
|
+
@click.option("--api-url", envvar="OBACHAN_API_BASE_URL", default=None)
|
|
137
|
+
@click.option("--api-key", envvar="OBACHAN_API_KEY", default=None)
|
|
138
|
+
def knative_get(name: str, api_url: str | None, api_key: str | None) -> None:
|
|
139
|
+
"""Get details of a Knative function."""
|
|
140
|
+
from granny.knative import KnativeClient
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
c = KnativeClient(base_url=api_url, api_key=api_key)
|
|
144
|
+
result = c.get(name)
|
|
145
|
+
except (ValueError, RuntimeError) as exc:
|
|
146
|
+
click.echo(f"Error: {exc}", err=True)
|
|
147
|
+
raise SystemExit(1)
|
|
148
|
+
|
|
149
|
+
if result is None:
|
|
150
|
+
click.echo(f"Function '{name}' not found.", err=True)
|
|
151
|
+
raise SystemExit(1)
|
|
152
|
+
|
|
153
|
+
click.echo(json.dumps(result, indent=2))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@knative.command("scale")
|
|
157
|
+
@click.argument("name")
|
|
158
|
+
@click.option("--min-scale", default=None, type=int)
|
|
159
|
+
@click.option("--max-scale", default=None, type=int)
|
|
160
|
+
@click.option("--api-url", envvar="OBACHAN_API_BASE_URL", default=None)
|
|
161
|
+
@click.option("--api-key", envvar="OBACHAN_API_KEY", default=None)
|
|
162
|
+
def knative_scale(
|
|
163
|
+
name: str,
|
|
164
|
+
min_scale: int | None,
|
|
165
|
+
max_scale: int | None,
|
|
166
|
+
api_url: str | None,
|
|
167
|
+
api_key: str | None,
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Update the scale bounds of a Knative function."""
|
|
170
|
+
if min_scale is None and max_scale is None:
|
|
171
|
+
raise click.UsageError(
|
|
172
|
+
"At least one of --min-scale or --max-scale must be provided."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
from granny.knative import KnativeClient
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
c = KnativeClient(base_url=api_url, api_key=api_key)
|
|
179
|
+
c.scale(name, min_scale=min_scale, max_scale=max_scale)
|
|
180
|
+
except (ValueError, RuntimeError) as exc:
|
|
181
|
+
click.echo(f"Error: {exc}", err=True)
|
|
182
|
+
raise SystemExit(1)
|
|
183
|
+
|
|
184
|
+
click.echo(f"Scaled {name}: min={min_scale} max={max_scale}")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@knative.command("remove")
|
|
188
|
+
@click.argument("name")
|
|
189
|
+
@click.option(
|
|
190
|
+
"--yes",
|
|
191
|
+
is_flag=True,
|
|
192
|
+
help="Confirm deletion. Required to prevent accidental removal.",
|
|
193
|
+
)
|
|
194
|
+
@click.option("--api-url", envvar="OBACHAN_API_BASE_URL", default=None)
|
|
195
|
+
@click.option("--api-key", envvar="OBACHAN_API_KEY", default=None)
|
|
196
|
+
def knative_remove(
|
|
197
|
+
name: str,
|
|
198
|
+
yes: bool,
|
|
199
|
+
api_url: str | None,
|
|
200
|
+
api_key: str | None,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Remove a deployed Knative function."""
|
|
203
|
+
if not yes:
|
|
204
|
+
raise click.UsageError(
|
|
205
|
+
"Deletion requires --yes flag to confirm. "
|
|
206
|
+
"Pass --yes to confirm removal of the function."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
from granny.knative import KnativeClient
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
c = KnativeClient(base_url=api_url, api_key=api_key)
|
|
213
|
+
c.delete(name)
|
|
214
|
+
except (ValueError, RuntimeError) as exc:
|
|
215
|
+
click.echo(f"Error: {exc}", err=True)
|
|
216
|
+
raise SystemExit(1)
|
|
217
|
+
|
|
218
|
+
click.echo(f"Removed: {name}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@knative.command("logs")
|
|
222
|
+
@click.argument("name")
|
|
223
|
+
@click.option("--tail", default=50, show_default=True, type=int)
|
|
224
|
+
@click.option("--api-url", envvar="OBACHAN_API_BASE_URL", default=None)
|
|
225
|
+
@click.option("--api-key", envvar="OBACHAN_API_KEY", default=None)
|
|
226
|
+
def knative_logs(
|
|
227
|
+
name: str,
|
|
228
|
+
tail: int,
|
|
229
|
+
api_url: str | None,
|
|
230
|
+
api_key: str | None,
|
|
231
|
+
) -> None:
|
|
232
|
+
"""Retrieve recent log lines for a Knative function."""
|
|
233
|
+
from granny.knative import KnativeClient
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
c = KnativeClient(base_url=api_url, api_key=api_key)
|
|
237
|
+
log_text = c.logs(name, tail=tail)
|
|
238
|
+
except (ValueError, RuntimeError) as exc:
|
|
239
|
+
click.echo(f"Error: {exc}", err=True)
|
|
240
|
+
raise SystemExit(1)
|
|
241
|
+
|
|
242
|
+
click.echo(log_text)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@knative.command("invoke")
|
|
246
|
+
@click.argument("name")
|
|
247
|
+
@click.option("--method", default="GET", show_default=True)
|
|
248
|
+
@click.option("--path", "invoke_path", default="/", show_default=True)
|
|
249
|
+
@click.option("--body", default=None, help="Request body string")
|
|
250
|
+
@click.option("--api-url", envvar="OBACHAN_API_BASE_URL", default=None)
|
|
251
|
+
@click.option("--api-key", envvar="OBACHAN_API_KEY", default=None)
|
|
252
|
+
def knative_invoke(
|
|
253
|
+
name: str,
|
|
254
|
+
method: str,
|
|
255
|
+
invoke_path: str,
|
|
256
|
+
body: str | None,
|
|
257
|
+
api_url: str | None,
|
|
258
|
+
api_key: str | None,
|
|
259
|
+
) -> None:
|
|
260
|
+
"""Invoke a Knative function via the control-plane proxy."""
|
|
261
|
+
from granny.knative import KnativeClient
|
|
262
|
+
|
|
263
|
+
body_bytes = body.encode() if body else None
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
c = KnativeClient(base_url=api_url, api_key=api_key)
|
|
267
|
+
result = c.invoke(name, method=method, path=invoke_path, body=body_bytes)
|
|
268
|
+
except (ValueError, RuntimeError) as exc:
|
|
269
|
+
click.echo(f"Error: {exc}", err=True)
|
|
270
|
+
raise SystemExit(1)
|
|
271
|
+
|
|
272
|
+
click.echo(result.decode("utf-8", errors="replace"))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@knative.command("deploy-ui")
|
|
276
|
+
@click.argument("name")
|
|
277
|
+
@click.argument(
|
|
278
|
+
"dist_dir",
|
|
279
|
+
type=click.Path(exists=True, file_okay=False, resolve_path=True),
|
|
280
|
+
)
|
|
281
|
+
@click.option("--image", required=True, help="Backend container image ref")
|
|
282
|
+
@click.option("--zone", required=True, help="Bunny Storage zone name")
|
|
283
|
+
@click.option(
|
|
284
|
+
"--storage-password",
|
|
285
|
+
envvar="BUNNY_STORAGE_PASSWORD",
|
|
286
|
+
required=True,
|
|
287
|
+
help="Bunny Storage zone password.",
|
|
288
|
+
)
|
|
289
|
+
@click.option("--hostname", default="storage.bunnycdn.com", show_default=True)
|
|
290
|
+
@click.option(
|
|
291
|
+
"--pull-zone-id",
|
|
292
|
+
default=None,
|
|
293
|
+
type=int,
|
|
294
|
+
help="Bunny CDN pull zone ID for cache purge after upload.",
|
|
295
|
+
)
|
|
296
|
+
@click.option(
|
|
297
|
+
"--env", "-e", "env_vars", multiple=True, help="Backend env vars KEY=VAL."
|
|
298
|
+
)
|
|
299
|
+
@click.option("--api-url", envvar="OBACHAN_API_BASE_URL", default=None)
|
|
300
|
+
@click.option("--api-key", envvar="OBACHAN_API_KEY", default=None)
|
|
301
|
+
def knative_deploy_ui(
|
|
302
|
+
name: str,
|
|
303
|
+
dist_dir: str,
|
|
304
|
+
image: str,
|
|
305
|
+
zone: str,
|
|
306
|
+
storage_password: str,
|
|
307
|
+
hostname: str,
|
|
308
|
+
pull_zone_id: int | None,
|
|
309
|
+
env_vars: tuple,
|
|
310
|
+
api_url: str | None,
|
|
311
|
+
api_key: str | None,
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Deploy a UI to Bunny Storage and a backend function to Knative.
|
|
314
|
+
|
|
315
|
+
Uploads all files in DIST_DIR to Bunny Storage (with Cache-Control headers
|
|
316
|
+
appropriate for SPA assets), optionally purges the CDN cache, then deploys
|
|
317
|
+
the backend image as a Knative service.
|
|
318
|
+
"""
|
|
319
|
+
from granny.storage.bunny import BunnyStorageClient
|
|
320
|
+
|
|
321
|
+
storage_client = BunnyStorageClient()
|
|
322
|
+
uploaded = 0
|
|
323
|
+
errors = 0
|
|
324
|
+
|
|
325
|
+
for root, _dirs, files in os.walk(dist_dir):
|
|
326
|
+
for fname in files:
|
|
327
|
+
local = os.path.join(root, fname)
|
|
328
|
+
remote = os.path.relpath(local, dist_dir).replace("\\", "/")
|
|
329
|
+
ct = _guess_content_type(local)
|
|
330
|
+
cc = (
|
|
331
|
+
"no-cache, no-store, must-revalidate"
|
|
332
|
+
if fname.lower().endswith(".html")
|
|
333
|
+
else "public, max-age=31536000, immutable"
|
|
334
|
+
)
|
|
335
|
+
try:
|
|
336
|
+
with open(local, "rb") as fh:
|
|
337
|
+
storage_client.upload_file(
|
|
338
|
+
hostname,
|
|
339
|
+
storage_password,
|
|
340
|
+
zone,
|
|
341
|
+
remote,
|
|
342
|
+
fh.read(),
|
|
343
|
+
ct,
|
|
344
|
+
cache_control=cc,
|
|
345
|
+
)
|
|
346
|
+
uploaded += 1
|
|
347
|
+
except Exception as exc:
|
|
348
|
+
click.echo(f" ERROR uploading {remote}: {exc}", err=True)
|
|
349
|
+
errors += 1
|
|
350
|
+
|
|
351
|
+
click.echo(f"Uploaded {uploaded} files to Bunny zone '{zone}' ({errors} errors).")
|
|
352
|
+
if errors:
|
|
353
|
+
raise SystemExit(1)
|
|
354
|
+
|
|
355
|
+
if pull_zone_id is not None:
|
|
356
|
+
from granny.cdn.bunny import BunnyCDNClient
|
|
357
|
+
|
|
358
|
+
BunnyCDNClient().purge_cache(pull_zone_id)
|
|
359
|
+
click.echo(f"CDN cache purged for pull zone {pull_zone_id}.")
|
|
360
|
+
|
|
361
|
+
parsed_env: dict[str, str] = {}
|
|
362
|
+
for entry in env_vars:
|
|
363
|
+
k, sep, v = entry.partition("=")
|
|
364
|
+
if sep:
|
|
365
|
+
parsed_env[k.strip()] = v
|
|
366
|
+
|
|
367
|
+
from granny.knative import KnativeClient
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
c = KnativeClient(base_url=api_url, api_key=api_key)
|
|
371
|
+
result = c.deploy(name, image, env_vars=parsed_env)
|
|
372
|
+
except (ValueError, RuntimeError) as exc:
|
|
373
|
+
click.echo(f"Error deploying {name}: {exc}", err=True)
|
|
374
|
+
raise SystemExit(1)
|
|
375
|
+
|
|
376
|
+
url = (result.get("status") or {}).get("url", "(not yet ready)")
|
|
377
|
+
click.echo(f"Deployed: {name}")
|
|
378
|
+
click.echo(f"URL: {url}")
|
|
@@ -38,65 +38,78 @@ def cli(verbose: bool, quiet: bool) -> None:
|
|
|
38
38
|
|
|
39
39
|
# Register command groups -------------------------------------------------
|
|
40
40
|
|
|
41
|
+
|
|
41
42
|
def _register_commands() -> None:
|
|
42
43
|
"""Register available command groups."""
|
|
43
44
|
try:
|
|
44
45
|
from granny.cli.credentials import credentials
|
|
46
|
+
|
|
45
47
|
cli.add_command(credentials)
|
|
46
48
|
except ImportError:
|
|
47
49
|
pass
|
|
48
50
|
try:
|
|
49
51
|
from granny.cli.analyze import analyze
|
|
52
|
+
|
|
50
53
|
cli.add_command(analyze)
|
|
51
54
|
except ImportError:
|
|
52
55
|
pass
|
|
53
56
|
try:
|
|
54
57
|
from granny.cli.cdn import cdn
|
|
58
|
+
|
|
55
59
|
cli.add_command(cdn)
|
|
56
60
|
except ImportError:
|
|
57
61
|
pass
|
|
58
62
|
try:
|
|
59
63
|
from granny.cli.create import create
|
|
64
|
+
|
|
60
65
|
cli.add_command(create)
|
|
61
66
|
except ImportError:
|
|
62
67
|
pass
|
|
63
68
|
try:
|
|
64
69
|
from granny.cli.docker import docker
|
|
70
|
+
|
|
65
71
|
cli.add_command(docker)
|
|
66
72
|
except ImportError:
|
|
67
73
|
pass
|
|
68
74
|
try:
|
|
69
75
|
from granny.cli.dns import dns
|
|
76
|
+
|
|
70
77
|
cli.add_command(dns)
|
|
71
78
|
except ImportError:
|
|
72
79
|
pass
|
|
73
80
|
try:
|
|
74
81
|
from granny.cli.cloudflare import cloudflare
|
|
82
|
+
|
|
75
83
|
cli.add_command(cloudflare)
|
|
76
84
|
except ImportError:
|
|
77
85
|
pass
|
|
78
86
|
try:
|
|
79
87
|
from granny.cli.storage import storage
|
|
88
|
+
|
|
80
89
|
cli.add_command(storage)
|
|
81
90
|
except ImportError:
|
|
82
91
|
pass
|
|
83
92
|
try:
|
|
84
93
|
from granny.cli.edge import edge
|
|
94
|
+
|
|
85
95
|
cli.add_command(edge)
|
|
86
96
|
except ImportError:
|
|
87
97
|
pass
|
|
88
98
|
try:
|
|
89
99
|
from granny.cli.serverless import serverless
|
|
100
|
+
|
|
90
101
|
cli.add_command(serverless)
|
|
91
102
|
except ImportError:
|
|
92
103
|
pass
|
|
93
104
|
try:
|
|
94
105
|
from granny.cli.email import email
|
|
106
|
+
|
|
95
107
|
cli.add_command(email)
|
|
96
108
|
except ImportError:
|
|
97
109
|
pass
|
|
98
110
|
try:
|
|
99
111
|
from granny.cli.authentik import authentik
|
|
112
|
+
|
|
100
113
|
cli.add_command(authentik)
|
|
101
114
|
except ImportError:
|
|
102
115
|
pass
|
|
@@ -107,34 +120,47 @@ def _register_commands() -> None:
|
|
|
107
120
|
pass
|
|
108
121
|
try:
|
|
109
122
|
from granny.cli.elk import elk
|
|
123
|
+
|
|
110
124
|
cli.add_command(elk)
|
|
111
125
|
except ImportError:
|
|
112
126
|
pass
|
|
113
127
|
try:
|
|
114
128
|
from granny.cli.azure import azure
|
|
129
|
+
|
|
115
130
|
cli.add_command(azure)
|
|
116
131
|
except ImportError:
|
|
117
132
|
pass
|
|
118
133
|
try:
|
|
119
134
|
from granny.cli.aws import aws
|
|
135
|
+
|
|
120
136
|
cli.add_command(aws)
|
|
121
137
|
except ImportError:
|
|
122
138
|
pass
|
|
123
139
|
try:
|
|
124
140
|
from granny.cli.indexing import indexing
|
|
141
|
+
|
|
125
142
|
cli.add_command(indexing)
|
|
126
143
|
except ImportError:
|
|
127
144
|
pass
|
|
128
145
|
try:
|
|
129
146
|
from granny.cli.zitadel import zitadel
|
|
147
|
+
|
|
130
148
|
cli.add_command(zitadel)
|
|
131
149
|
except ImportError:
|
|
132
150
|
pass
|
|
133
151
|
try:
|
|
134
152
|
from granny.cli.oauth import oauth
|
|
153
|
+
|
|
135
154
|
cli.add_command(oauth)
|
|
136
155
|
except ImportError:
|
|
137
156
|
pass
|
|
157
|
+
try:
|
|
158
|
+
from granny.cli.knative import knative
|
|
159
|
+
|
|
160
|
+
cli.add_command(knative)
|
|
161
|
+
cli.add_command(knative, name="faas")
|
|
162
|
+
except ImportError:
|
|
163
|
+
pass
|
|
138
164
|
|
|
139
165
|
|
|
140
166
|
_register_commands()
|
|
@@ -110,6 +110,52 @@ def create_function(
|
|
|
110
110
|
click.echo(f"Endpoint: https://{result['domain_name']}")
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
@serverless.command("delete-function")
|
|
114
|
+
@click.argument("function_name")
|
|
115
|
+
@click.option(
|
|
116
|
+
"--namespace", "namespace_name",
|
|
117
|
+
help="Namespace name. If omitted, the function must be uniquely named across all namespaces.",
|
|
118
|
+
)
|
|
119
|
+
@click.confirmation_option(prompt="Really delete this Scaleway function?")
|
|
120
|
+
@region_option
|
|
121
|
+
def delete_function(
|
|
122
|
+
function_name: str,
|
|
123
|
+
namespace_name: str | None,
|
|
124
|
+
region: str,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Delete a Scaleway function by name.
|
|
127
|
+
|
|
128
|
+
Resolves ``function_name`` to a function id within ``--namespace`` (or
|
|
129
|
+
across all namespaces if unique) and issues a DELETE against the
|
|
130
|
+
Functions API. Custom domains attached to the function are removed by
|
|
131
|
+
Scaleway as part of the cascade.
|
|
132
|
+
"""
|
|
133
|
+
c = _client(region)
|
|
134
|
+
|
|
135
|
+
function = None
|
|
136
|
+
if namespace_name:
|
|
137
|
+
ns = c.find_namespace(namespace_name)
|
|
138
|
+
if not ns:
|
|
139
|
+
raise click.ClickException(f"Namespace '{namespace_name}' not found")
|
|
140
|
+
function = c.find_function(ns["id"], function_name)
|
|
141
|
+
else:
|
|
142
|
+
for ns in c.list_namespaces():
|
|
143
|
+
fn = c.find_function(ns["id"], function_name)
|
|
144
|
+
if fn:
|
|
145
|
+
if function:
|
|
146
|
+
raise click.ClickException(
|
|
147
|
+
f"Function '{function_name}' exists in multiple namespaces; use --namespace"
|
|
148
|
+
)
|
|
149
|
+
function = fn
|
|
150
|
+
|
|
151
|
+
if not function:
|
|
152
|
+
raise click.ClickException(f"Function '{function_name}' not found")
|
|
153
|
+
|
|
154
|
+
click.echo(f"Deleting {function['name']} (id={function['id']}, status={function.get('status', '?')})")
|
|
155
|
+
c.delete_function(function["id"])
|
|
156
|
+
click.echo("Deleted.")
|
|
157
|
+
|
|
158
|
+
|
|
113
159
|
# Patterns that bloat a function package without contributing to runtime.
|
|
114
160
|
# Matches both whole path segments and trailing-name globs.
|
|
115
161
|
_DEFAULT_EXCLUDES = (
|
|
@@ -137,6 +137,7 @@ def _invalidate_vault_session() -> None:
|
|
|
137
137
|
except Exception:
|
|
138
138
|
pass
|
|
139
139
|
|
|
140
|
+
|
|
140
141
|
# Map env var name -> vault secret name (kebab-case).
|
|
141
142
|
# The full vault path is: {VAULT_FOLDER}/{secret_name}
|
|
142
143
|
VAULT_FOLDER = "granny/infra"
|
|
@@ -189,6 +190,9 @@ SECRET_MAP: dict[str, str] = {
|
|
|
189
190
|
# StalwartClient.from_environment() and is overridable via STALWART_URL.
|
|
190
191
|
"STALWART_ADMIN_USER": "stalwart-admin-user",
|
|
191
192
|
"STALWART_ADMIN_PASSWORD": "stalwart-admin-password",
|
|
193
|
+
# Knative FaaS control API. The base URL is vault-distributed configuration.
|
|
194
|
+
"OBACHAN_API_KEY": "obachan-api-key",
|
|
195
|
+
"OBACHAN_API_BASE_URL": "obachan-api-base-url",
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
# Module-level cache so vault is only contacted once per process
|
|
@@ -232,7 +236,10 @@ def _get_vault_client():
|
|
|
232
236
|
logger.info("Reusing cached vault session")
|
|
233
237
|
return _vault_client
|
|
234
238
|
else:
|
|
235
|
-
logger.debug(
|
|
239
|
+
logger.debug(
|
|
240
|
+
"Cached vault token expired (HTTP %s), re-authenticating",
|
|
241
|
+
resp.status_code,
|
|
242
|
+
)
|
|
236
243
|
_invalidate_vault_session()
|
|
237
244
|
_vault_client = None
|
|
238
245
|
except Exception as exc:
|