vibey-bootstrap 4.0.0__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.
- vibey_bootstrap/__init__.py +271 -0
- vibey_bootstrap/aks/__init__.py +84 -0
- vibey_bootstrap/aks/leader_election.py +82 -0
- vibey_bootstrap/alerts/__init__.py +36 -0
- vibey_bootstrap/alerts/dispatcher.py +358 -0
- vibey_bootstrap/alerts/escalation.py +25 -0
- vibey_bootstrap/alerts/render.py +97 -0
- vibey_bootstrap/audit/__init__.py +411 -0
- vibey_bootstrap/auth/__init__.py +19 -0
- vibey_bootstrap/auth/api_key.py +18 -0
- vibey_bootstrap/auth/hmac.py +40 -0
- vibey_bootstrap/auth/webhook.py +202 -0
- vibey_bootstrap/bootstrap/__init__.py +107 -0
- vibey_bootstrap/config_refresh/__init__.py +92 -0
- vibey_bootstrap/contrib/__init__.py +1 -0
- vibey_bootstrap/contrib/scaffold.py +107 -0
- vibey_bootstrap/contrib/templates/bicep/app/main.bicep.template +2 -0
- vibey_bootstrap/contrib/templates/cicd/github/test.yml.template +12 -0
- vibey_bootstrap/contrib/templates/gitops/base/kustomization.yaml.template +4 -0
- vibey_bootstrap/contrib/templates/helm/worker/Chart.yaml.template +4 -0
- vibey_bootstrap/contrib/templates/policy/conftest/kubernetes.rego.template +7 -0
- vibey_bootstrap/contrib/templates/terraform/aks/main.tf.template +4 -0
- vibey_bootstrap/counters/__init__.py +43 -0
- vibey_bootstrap/db/__init__.py +109 -0
- vibey_bootstrap/db/migrations/__init__.py +86 -0
- vibey_bootstrap/db/outbox.py +185 -0
- vibey_bootstrap/documentdb/__init__.py +38 -0
- vibey_bootstrap/email/__init__.py +69 -0
- vibey_bootstrap/exceptions/__init__.py +108 -0
- vibey_bootstrap/failclose/__init__.py +52 -0
- vibey_bootstrap/fastapi_middleware/__init__.py +144 -0
- vibey_bootstrap/governance/__init__.py +112 -0
- vibey_bootstrap/health/__init__.py +117 -0
- vibey_bootstrap/heartbeat/__init__.py +260 -0
- vibey_bootstrap/http/__init__.py +82 -0
- vibey_bootstrap/http/_common.py +37 -0
- vibey_bootstrap/http/async_client.py +40 -0
- vibey_bootstrap/identity/__init__.py +385 -0
- vibey_bootstrap/ingress/__init__.py +41 -0
- vibey_bootstrap/ingress/classifier.py +119 -0
- vibey_bootstrap/ingress/extensions.py +42 -0
- vibey_bootstrap/ingress/magic_bytes.py +63 -0
- vibey_bootstrap/ingress/mime.py +38 -0
- vibey_bootstrap/ingress/size.py +32 -0
- vibey_bootstrap/ingress/zip_safety.py +49 -0
- vibey_bootstrap/logging/__init__.py +69 -0
- vibey_bootstrap/logging/config.py +102 -0
- vibey_bootstrap/logging/correlation.py +97 -0
- vibey_bootstrap/logging/formatter.py +78 -0
- vibey_bootstrap/logging/jsonformatter.py +87 -0
- vibey_bootstrap/logging/masking.py +170 -0
- vibey_bootstrap/logging/noise.py +53 -0
- vibey_bootstrap/metrics/__init__.py +42 -0
- vibey_bootstrap/models/__init__.py +13 -0
- vibey_bootstrap/models/exceptions.py +38 -0
- vibey_bootstrap/notify/__init__.py +25 -0
- vibey_bootstrap/notify/templates.py +281 -0
- vibey_bootstrap/notify/throttle.py +68 -0
- vibey_bootstrap/openai/__init__.py +462 -0
- vibey_bootstrap/path_safety/__init__.py +79 -0
- vibey_bootstrap/pdf_safety/__init__.py +104 -0
- vibey_bootstrap/phases/__init__.py +112 -0
- vibey_bootstrap/py.typed +2 -0
- vibey_bootstrap/ratelimit/__init__.py +137 -0
- vibey_bootstrap/repositories/__init__.py +13 -0
- vibey_bootstrap/repositories/enhanced_config_repository.py +580 -0
- vibey_bootstrap/repositories/interfaces/__init__.py +17 -0
- vibey_bootstrap/repositories/interfaces/enhanced_config_repository_interface.py +140 -0
- vibey_bootstrap/repositories/interfaces/secrets_repository_interface.py +96 -0
- vibey_bootstrap/repositories/secrets_repository.py +243 -0
- vibey_bootstrap/retry/__init__.py +170 -0
- vibey_bootstrap/sb_lock/__init__.py +128 -0
- vibey_bootstrap/scheduler/__init__.py +76 -0
- vibey_bootstrap/security/__init__.py +65 -0
- vibey_bootstrap/servicebus/__init__.py +51 -0
- vibey_bootstrap/servicebus/async_ext.py +99 -0
- vibey_bootstrap/servicebus/consumer.py +20 -0
- vibey_bootstrap/servicebus/consumer_wrapper.py +235 -0
- vibey_bootstrap/servicebus/dlq_alarm.py +85 -0
- vibey_bootstrap/servicebus/dlq_digest.py +162 -0
- vibey_bootstrap/services/__init__.py +29 -0
- vibey_bootstrap/services/application_bootstrap.py +592 -0
- vibey_bootstrap/services/bootstrap_logging.py +199 -0
- vibey_bootstrap/services/interfaces/__init__.py +21 -0
- vibey_bootstrap/services/interfaces/application_bootstrap_interface.py +58 -0
- vibey_bootstrap/services/interfaces/bootstrap_logger_interface.py +57 -0
- vibey_bootstrap/services/interfaces/telemetry_manager_interface.py +149 -0
- vibey_bootstrap/services/telemetry.py +277 -0
- vibey_bootstrap/softfail/__init__.py +146 -0
- vibey_bootstrap/subscription/__init__.py +154 -0
- vibey_bootstrap/tokens/__init__.py +95 -0
- vibey_bootstrap/tracing/__init__.py +28 -0
- vibey_bootstrap/tracing/decorators.py +235 -0
- vibey_bootstrap/tracing/latency.py +95 -0
- vibey_bootstrap/tracing/log_exception_context.py +24 -0
- vibey_bootstrap/tracing/slow_thresholds.py +47 -0
- vibey_bootstrap/tracing/timed_operation.py +29 -0
- vibey_bootstrap/transports/__init__.py +251 -0
- vibey_bootstrap/transports/_base.py +309 -0
- vibey_bootstrap/transports/adx.py +125 -0
- vibey_bootstrap/transports/blob.py +208 -0
- vibey_bootstrap/transports/builtins.py +93 -0
- vibey_bootstrap/transports/event_hubs.py +122 -0
- vibey_bootstrap/transports/file.py +251 -0
- vibey_bootstrap/transports/nosql.py +235 -0
- vibey_bootstrap/transports/panther.py +261 -0
- vibey_bootstrap/transports/sql.py +293 -0
- vibey_bootstrap/transports/sumologic.py +180 -0
- vibey_bootstrap/validation/__init__.py +151 -0
- vibey_bootstrap-4.0.0.dist-info/METADATA +354 -0
- vibey_bootstrap-4.0.0.dist-info/RECORD +116 -0
- vibey_bootstrap-4.0.0.dist-info/WHEEL +5 -0
- vibey_bootstrap-4.0.0.dist-info/entry_points.txt +3 -0
- vibey_bootstrap-4.0.0.dist-info/licenses/LICENSE +22 -0
- vibey_bootstrap-4.0.0.dist-info/licenses/NOTICE.md +8 -0
- vibey_bootstrap-4.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
vibey-bootstrap (formerly azure-bootstrap)
|
|
3
|
+
|
|
4
|
+
A production-ready Azure bootstrap library that handles application initialization
|
|
5
|
+
for Azure Functions, including App Configuration, Key Vault, and App Insights integration.
|
|
6
|
+
|
|
7
|
+
This library solves the circular dependency between logging and configuration by:
|
|
8
|
+
1. Starting with basic console logging
|
|
9
|
+
2. Loading configuration from Azure App Configuration + Key Vault
|
|
10
|
+
3. Upgrading to App Insights telemetry when available
|
|
11
|
+
4. Loading all configs to os.environ for transparent access
|
|
12
|
+
|
|
13
|
+
Quick Start:
|
|
14
|
+
from vibey_bootstrap import initialize_application, get_bootstrap_logger
|
|
15
|
+
|
|
16
|
+
# Get logger that works immediately
|
|
17
|
+
logger = get_bootstrap_logger(__name__)
|
|
18
|
+
|
|
19
|
+
# Bootstrap the application (App Config + Key Vault + App Insights)
|
|
20
|
+
config_repo = initialize_application()
|
|
21
|
+
|
|
22
|
+
# Now all configs are in os.environ
|
|
23
|
+
db_host = os.getenv("DATABASE_HOST")
|
|
24
|
+
|
|
25
|
+
For detailed usage, see: https://github.com/adammatthewsteinberger/vibey-bootstrap
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__version__ = "4.0.0"
|
|
29
|
+
__author__ = "Adam Matthew Steinberger"
|
|
30
|
+
__license__ = "MIT"
|
|
31
|
+
|
|
32
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
33
|
+
# v2 additions (additive only — never alters the v1 surface above)
|
|
34
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
35
|
+
import logging as _stdlib_logging
|
|
36
|
+
import os as _os
|
|
37
|
+
|
|
38
|
+
# v3.0.0 additions (opt-in subpackages re-exported for convenience)
|
|
39
|
+
from vibey_bootstrap.aks import build_info as build_info
|
|
40
|
+
from vibey_bootstrap.audit import verify_chain as verify_chain
|
|
41
|
+
from vibey_bootstrap.auth import verify_hmac_signature as verify_hmac_signature
|
|
42
|
+
from vibey_bootstrap.bootstrap import bootstrap_initialized as bootstrap_initialized
|
|
43
|
+
from vibey_bootstrap.bootstrap import ensure_bootstrap as ensure_bootstrap
|
|
44
|
+
from vibey_bootstrap.bootstrap import load_local_settings as load_local_settings
|
|
45
|
+
from vibey_bootstrap.counters import bump_counter as bump_counter
|
|
46
|
+
from vibey_bootstrap.counters import counter_snapshot as counter_snapshot
|
|
47
|
+
from vibey_bootstrap.db.outbox import drain_outbox as drain_outbox
|
|
48
|
+
from vibey_bootstrap.email import AcsEmailSender as AcsEmailSender
|
|
49
|
+
from vibey_bootstrap.exceptions import InvalidMessageError as InvalidMessageError
|
|
50
|
+
from vibey_bootstrap.exceptions import NetworkError as NetworkError
|
|
51
|
+
from vibey_bootstrap.exceptions import PipelineError as PipelineError
|
|
52
|
+
from vibey_bootstrap.exceptions import RateLimitError as RateLimitError
|
|
53
|
+
from vibey_bootstrap.exceptions import TransientError as TransientError
|
|
54
|
+
from vibey_bootstrap.exceptions import UnrecoverableError as UnrecoverableError
|
|
55
|
+
from vibey_bootstrap.exceptions import is_unrecoverable as is_unrecoverable
|
|
56
|
+
from vibey_bootstrap.governance import budget_guard as budget_guard
|
|
57
|
+
from vibey_bootstrap.governance import track_usage as track_usage
|
|
58
|
+
from vibey_bootstrap.http import build_session as build_session
|
|
59
|
+
from vibey_bootstrap.http import request_with_retry as request_with_retry
|
|
60
|
+
from vibey_bootstrap.identity import build_tenant_credential as build_tenant_credential
|
|
61
|
+
from vibey_bootstrap.identity import (
|
|
62
|
+
build_tenant_credential_cached as build_tenant_credential_cached,
|
|
63
|
+
)
|
|
64
|
+
from vibey_bootstrap.logging import JsonLogFormatter as JsonLogFormatter
|
|
65
|
+
from vibey_bootstrap.logging import configure_logging as configure_logging
|
|
66
|
+
from vibey_bootstrap.logging import correlation_scope as correlation_scope
|
|
67
|
+
from vibey_bootstrap.logging import get_correlation_id as get_correlation_id
|
|
68
|
+
from vibey_bootstrap.logging import mask_api_key as mask_api_key
|
|
69
|
+
from vibey_bootstrap.logging import mask_bearer_token as mask_bearer_token
|
|
70
|
+
from vibey_bootstrap.logging import mask_email_address as mask_email_address
|
|
71
|
+
from vibey_bootstrap.logging import mask_secrets_in_dict as mask_secrets_in_dict
|
|
72
|
+
from vibey_bootstrap.logging import safe_json_dumps as safe_json_dumps
|
|
73
|
+
from vibey_bootstrap.logging import sanitize_for_log as sanitize_for_log
|
|
74
|
+
from vibey_bootstrap.logging import set_correlation_id as set_correlation_id
|
|
75
|
+
|
|
76
|
+
# Exceptions
|
|
77
|
+
from vibey_bootstrap.models.exceptions import ConfigurationError as ConfigurationError
|
|
78
|
+
from vibey_bootstrap.models.exceptions import KeyVaultError as KeyVaultError
|
|
79
|
+
from vibey_bootstrap.models.exceptions import RepositoryError as RepositoryError
|
|
80
|
+
from vibey_bootstrap.path_safety import confine_to_root as confine_to_root
|
|
81
|
+
from vibey_bootstrap.path_safety import sanitize_path_segment as sanitize_path_segment
|
|
82
|
+
from vibey_bootstrap.phases import PhaseResult as PhaseResult
|
|
83
|
+
from vibey_bootstrap.phases import run_phase as run_phase
|
|
84
|
+
from vibey_bootstrap.phases import run_phases as run_phases
|
|
85
|
+
|
|
86
|
+
# Repository implementations
|
|
87
|
+
from vibey_bootstrap.repositories.enhanced_config_repository import (
|
|
88
|
+
EnhancedConfigRepository as EnhancedConfigRepository,
|
|
89
|
+
)
|
|
90
|
+
from vibey_bootstrap.repositories.enhanced_config_repository import (
|
|
91
|
+
create_enhanced_config_repository as create_enhanced_config_repository,
|
|
92
|
+
)
|
|
93
|
+
from vibey_bootstrap.repositories.interfaces.enhanced_config_repository_interface import (
|
|
94
|
+
EnhancedConfigRepositoryInterface as EnhancedConfigRepositoryInterface,
|
|
95
|
+
)
|
|
96
|
+
from vibey_bootstrap.repositories.interfaces.secrets_repository_interface import (
|
|
97
|
+
SecretsRepositoryInterface as SecretsRepositoryInterface,
|
|
98
|
+
)
|
|
99
|
+
from vibey_bootstrap.repositories.secrets_repository import SecretsRepository as SecretsRepository
|
|
100
|
+
from vibey_bootstrap.security import compare_secrets as compare_secrets
|
|
101
|
+
|
|
102
|
+
# Core classes for advanced usage
|
|
103
|
+
# Main bootstrap function - most users only need this
|
|
104
|
+
from vibey_bootstrap.services.application_bootstrap import (
|
|
105
|
+
ApplicationBootstrap as ApplicationBootstrap,
|
|
106
|
+
)
|
|
107
|
+
from vibey_bootstrap.services.application_bootstrap import (
|
|
108
|
+
initialize_application as initialize_application,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Bootstrap logging - works before configuration loaded
|
|
112
|
+
from vibey_bootstrap.services.bootstrap_logging import BootstrapLogger as BootstrapLogger
|
|
113
|
+
from vibey_bootstrap.services.bootstrap_logging import ExtraFieldsFormatter as ExtraFieldsFormatter
|
|
114
|
+
from vibey_bootstrap.services.bootstrap_logging import (
|
|
115
|
+
ensure_bootstrap_logging as ensure_bootstrap_logging,
|
|
116
|
+
)
|
|
117
|
+
from vibey_bootstrap.services.bootstrap_logging import get_bootstrap_logger as get_bootstrap_logger
|
|
118
|
+
|
|
119
|
+
# Interfaces for type hinting and custom implementations
|
|
120
|
+
from vibey_bootstrap.services.interfaces.application_bootstrap_interface import (
|
|
121
|
+
ApplicationBootstrapInterface as ApplicationBootstrapInterface,
|
|
122
|
+
)
|
|
123
|
+
from vibey_bootstrap.services.interfaces.bootstrap_logger_interface import (
|
|
124
|
+
BootstrapLoggerInterface as BootstrapLoggerInterface,
|
|
125
|
+
)
|
|
126
|
+
from vibey_bootstrap.services.interfaces.telemetry_manager_interface import (
|
|
127
|
+
TelemetryManagerInterface as TelemetryManagerInterface,
|
|
128
|
+
)
|
|
129
|
+
from vibey_bootstrap.services.telemetry import TelemetryManager as TelemetryManager
|
|
130
|
+
from vibey_bootstrap.services.telemetry import telemetry_manager as telemetry_manager
|
|
131
|
+
from vibey_bootstrap.softfail import SoftFailResult as SoftFailResult
|
|
132
|
+
from vibey_bootstrap.softfail import soft_fail as soft_fail
|
|
133
|
+
from vibey_bootstrap.softfail import soft_fail_with as soft_fail_with
|
|
134
|
+
from vibey_bootstrap.tracing import latency_snapshot as latency_snapshot
|
|
135
|
+
from vibey_bootstrap.tracing import traced as traced
|
|
136
|
+
from vibey_bootstrap.transports import configure_transports as configure_transports
|
|
137
|
+
from vibey_bootstrap.transports import disable_transport as disable_transport
|
|
138
|
+
from vibey_bootstrap.transports import enable_transport as enable_transport
|
|
139
|
+
from vibey_bootstrap.transports import list_transports as list_transports
|
|
140
|
+
from vibey_bootstrap.transports import register_transport as register_transport
|
|
141
|
+
from vibey_bootstrap.validation import MessageSchema as MessageSchema
|
|
142
|
+
from vibey_bootstrap.validation import queue_message_schema as queue_message_schema
|
|
143
|
+
from vibey_bootstrap.validation import validate_message as validate_message
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def refresh_setting(*names: str) -> None:
|
|
147
|
+
"""Re-read named settings from the cached App Configuration repo and
|
|
148
|
+
write their values into ``os.environ``.
|
|
149
|
+
|
|
150
|
+
Net-new in v2. Designed to be called from a recurring job (see
|
|
151
|
+
``vibey_bootstrap.config_refresh.refresh_log_flags``) so ops can flip a
|
|
152
|
+
setting in App Configuration and see it take effect within seconds
|
|
153
|
+
without redeploying.
|
|
154
|
+
|
|
155
|
+
No-ops with a DEBUG log when ``initialize_application()`` has not yet
|
|
156
|
+
run. Best-effort — never raises.
|
|
157
|
+
"""
|
|
158
|
+
if not names:
|
|
159
|
+
return
|
|
160
|
+
logger = _stdlib_logging.getLogger(__name__)
|
|
161
|
+
try:
|
|
162
|
+
from vibey_bootstrap.services.application_bootstrap import (
|
|
163
|
+
get_last_initialized_repo,
|
|
164
|
+
)
|
|
165
|
+
except Exception:
|
|
166
|
+
logger.debug("refresh_setting: bootstrap module unavailable")
|
|
167
|
+
return
|
|
168
|
+
repo = get_last_initialized_repo()
|
|
169
|
+
if repo is None:
|
|
170
|
+
logger.debug("refresh_setting: no cached repo (initialize_application not called)")
|
|
171
|
+
return
|
|
172
|
+
for name in names:
|
|
173
|
+
if not isinstance(name, str) or not name:
|
|
174
|
+
continue
|
|
175
|
+
try:
|
|
176
|
+
value = repo.get_value(name)
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
logger.warning("refresh_setting: failed to read %s: %s", name, exc)
|
|
179
|
+
continue
|
|
180
|
+
if value is None:
|
|
181
|
+
continue
|
|
182
|
+
_os.environ[name] = str(value)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Public API
|
|
186
|
+
__all__ = [
|
|
187
|
+
# Version
|
|
188
|
+
"__version__",
|
|
189
|
+
# Main bootstrap functions (most common usage)
|
|
190
|
+
"initialize_application",
|
|
191
|
+
"get_bootstrap_logger",
|
|
192
|
+
"create_enhanced_config_repository",
|
|
193
|
+
"ensure_bootstrap_logging",
|
|
194
|
+
# Singleton instance
|
|
195
|
+
"telemetry_manager",
|
|
196
|
+
# Core classes
|
|
197
|
+
"ApplicationBootstrap",
|
|
198
|
+
"BootstrapLogger",
|
|
199
|
+
"ExtraFieldsFormatter",
|
|
200
|
+
"TelemetryManager",
|
|
201
|
+
"EnhancedConfigRepository",
|
|
202
|
+
"SecretsRepository",
|
|
203
|
+
# Interfaces
|
|
204
|
+
"ApplicationBootstrapInterface",
|
|
205
|
+
"BootstrapLoggerInterface",
|
|
206
|
+
"TelemetryManagerInterface",
|
|
207
|
+
"EnhancedConfigRepositoryInterface",
|
|
208
|
+
"SecretsRepositoryInterface",
|
|
209
|
+
# Exceptions
|
|
210
|
+
"RepositoryError",
|
|
211
|
+
"ConfigurationError",
|
|
212
|
+
"KeyVaultError",
|
|
213
|
+
# v2 additions — Tier 1 always-on primitives
|
|
214
|
+
"bootstrap_initialized",
|
|
215
|
+
"bump_counter",
|
|
216
|
+
"configure_logging",
|
|
217
|
+
"correlation_scope",
|
|
218
|
+
"counter_snapshot",
|
|
219
|
+
"ensure_bootstrap",
|
|
220
|
+
"get_correlation_id",
|
|
221
|
+
"latency_snapshot",
|
|
222
|
+
"load_local_settings",
|
|
223
|
+
"mask_api_key",
|
|
224
|
+
"mask_bearer_token",
|
|
225
|
+
"mask_email_address",
|
|
226
|
+
"mask_secrets_in_dict",
|
|
227
|
+
"refresh_setting",
|
|
228
|
+
"safe_json_dumps",
|
|
229
|
+
"sanitize_for_log",
|
|
230
|
+
"set_correlation_id",
|
|
231
|
+
"traced",
|
|
232
|
+
# v2 Parts 2+3 — exceptions, soft-fail, phases, validation, path safety, security
|
|
233
|
+
"InvalidMessageError",
|
|
234
|
+
"MessageSchema",
|
|
235
|
+
"NetworkError",
|
|
236
|
+
"PhaseResult",
|
|
237
|
+
"PipelineError",
|
|
238
|
+
"RateLimitError",
|
|
239
|
+
"SoftFailResult",
|
|
240
|
+
"TransientError",
|
|
241
|
+
"UnrecoverableError",
|
|
242
|
+
"compare_secrets",
|
|
243
|
+
"confine_to_root",
|
|
244
|
+
"is_unrecoverable",
|
|
245
|
+
"queue_message_schema",
|
|
246
|
+
"run_phase",
|
|
247
|
+
"run_phases",
|
|
248
|
+
"sanitize_path_segment",
|
|
249
|
+
"soft_fail",
|
|
250
|
+
"soft_fail_with",
|
|
251
|
+
"validate_message",
|
|
252
|
+
# v2.1 additions — logging transport layer
|
|
253
|
+
"JsonLogFormatter",
|
|
254
|
+
"configure_transports",
|
|
255
|
+
"disable_transport",
|
|
256
|
+
"enable_transport",
|
|
257
|
+
"list_transports",
|
|
258
|
+
"register_transport",
|
|
259
|
+
# v3.0.0 — db, outbox, email, http, documentdb, aks, governance, identity, audit
|
|
260
|
+
"build_tenant_credential",
|
|
261
|
+
"build_tenant_credential_cached",
|
|
262
|
+
"verify_chain",
|
|
263
|
+
"budget_guard",
|
|
264
|
+
"track_usage",
|
|
265
|
+
"build_session",
|
|
266
|
+
"request_with_retry",
|
|
267
|
+
"build_info",
|
|
268
|
+
"drain_outbox",
|
|
269
|
+
"AcsEmailSender",
|
|
270
|
+
"verify_hmac_signature",
|
|
271
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""AKS runtime helpers — graceful shutdown, build info, probes, pod context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import signal
|
|
9
|
+
import threading
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from vibey_bootstrap.counters import counter_snapshot
|
|
14
|
+
|
|
15
|
+
_logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def install_sigterm_handler(stop_event: threading.Event) -> None:
|
|
19
|
+
"""Register SIGTERM handler that sets *stop_event* for graceful drain."""
|
|
20
|
+
|
|
21
|
+
def _handler(signum: int, frame: Any) -> None:
|
|
22
|
+
_logger.info("SIGTERM received — setting stop event")
|
|
23
|
+
stop_event.set()
|
|
24
|
+
|
|
25
|
+
signal.signal(signal.SIGTERM, _handler)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def setup_async_sigterm_handler() -> asyncio.Event:
|
|
29
|
+
"""Return an asyncio.Event set on SIGTERM."""
|
|
30
|
+
stop = asyncio.Event()
|
|
31
|
+
loop = asyncio.get_running_loop()
|
|
32
|
+
|
|
33
|
+
def _handler() -> None:
|
|
34
|
+
_logger.info("SIGTERM received — setting async stop event")
|
|
35
|
+
stop.set()
|
|
36
|
+
|
|
37
|
+
loop.add_signal_handler(signal.SIGTERM, _handler)
|
|
38
|
+
return stop
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_info() -> dict[str, str | None]:
|
|
42
|
+
"""Return version/build metadata from downward-API or CI env vars."""
|
|
43
|
+
return {
|
|
44
|
+
"version": os.environ.get("BUILD_VERSION") or os.environ.get("APP_VERSION"),
|
|
45
|
+
"git_sha": os.environ.get("GIT_SHA"),
|
|
46
|
+
"build_time": os.environ.get("BUILD_TIME"),
|
|
47
|
+
"image_tag": os.environ.get("IMAGE_TAG"),
|
|
48
|
+
"pod_name": os.environ.get("POD_NAME"),
|
|
49
|
+
"pod_namespace": os.environ.get("POD_NAMESPACE"),
|
|
50
|
+
"node_name": os.environ.get("NODE_NAME"),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def keda_metric_value(name: str = "queue_depth") -> float:
|
|
55
|
+
"""Scalar metric for KEDA metrics-api scaler (from counters snapshot)."""
|
|
56
|
+
snap = counter_snapshot()
|
|
57
|
+
return float(snap.get(name, 0))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def mount_build_info_route(app: Any, path: str = "/api/version") -> None:
|
|
61
|
+
"""Mount a FastAPI route returning :func:`build_info`."""
|
|
62
|
+
|
|
63
|
+
@app.get(path)
|
|
64
|
+
def _version() -> dict[str, str | None]:
|
|
65
|
+
return build_info()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def pod_context_extra() -> dict[str, str | None]:
|
|
69
|
+
"""Correlation/log extras from Kubernetes downward API env vars."""
|
|
70
|
+
return {
|
|
71
|
+
"pod_name": os.environ.get("POD_NAME"),
|
|
72
|
+
"pod_namespace": os.environ.get("POD_NAMESPACE"),
|
|
73
|
+
"node_name": os.environ.get("NODE_NAME"),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = [
|
|
78
|
+
"build_info",
|
|
79
|
+
"install_sigterm_handler",
|
|
80
|
+
"keda_metric_value",
|
|
81
|
+
"mount_build_info_route",
|
|
82
|
+
"pod_context_extra",
|
|
83
|
+
"setup_async_sigterm_handler",
|
|
84
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Generic leader election — K8s ConfigMap lock or soft no-op."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
_logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LeaderElection:
|
|
14
|
+
"""Simple in-process leader election using a ConfigMap name env var.
|
|
15
|
+
|
|
16
|
+
When ``LEADER_ELECTION_CONFIGMAP`` is unset, :meth:`is_leader` always
|
|
17
|
+
returns ``True`` (soft no-op for single-replica dev).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
configmap_name: str | None = None,
|
|
24
|
+
namespace: str | None = None,
|
|
25
|
+
identity: str | None = None,
|
|
26
|
+
lease_seconds: int = 30,
|
|
27
|
+
) -> None:
|
|
28
|
+
self._configmap = configmap_name or os.environ.get("LEADER_ELECTION_CONFIGMAP")
|
|
29
|
+
self._namespace = namespace or os.environ.get("POD_NAMESPACE", "default")
|
|
30
|
+
self._identity = identity or os.environ.get("POD_NAME", "local")
|
|
31
|
+
self._lease_seconds = lease_seconds
|
|
32
|
+
self._is_leader = False
|
|
33
|
+
self._lock = threading.Lock()
|
|
34
|
+
self._stop = threading.Event()
|
|
35
|
+
self._thread: threading.Thread | None = None
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def is_leader(self) -> bool:
|
|
39
|
+
if not self._configmap:
|
|
40
|
+
return True
|
|
41
|
+
with self._lock:
|
|
42
|
+
return self._is_leader
|
|
43
|
+
|
|
44
|
+
def start(self) -> None:
|
|
45
|
+
if not self._configmap:
|
|
46
|
+
self._is_leader = True
|
|
47
|
+
return
|
|
48
|
+
self._thread = threading.Thread(target=self._run, name="leader-election", daemon=True)
|
|
49
|
+
self._thread.start()
|
|
50
|
+
|
|
51
|
+
def stop(self) -> None:
|
|
52
|
+
self._stop.set()
|
|
53
|
+
if self._thread and self._thread.is_alive():
|
|
54
|
+
self._thread.join(timeout=5.0)
|
|
55
|
+
|
|
56
|
+
def _run(self) -> None:
|
|
57
|
+
while not self._stop.is_set():
|
|
58
|
+
try:
|
|
59
|
+
acquired = self._try_acquire()
|
|
60
|
+
with self._lock:
|
|
61
|
+
self._is_leader = acquired
|
|
62
|
+
except Exception:
|
|
63
|
+
_logger.debug("leader election cycle failed", exc_info=True)
|
|
64
|
+
self._stop.wait(self._lease_seconds / 2)
|
|
65
|
+
|
|
66
|
+
def _try_acquire(self) -> bool:
|
|
67
|
+
# Production apps use the K8s API; this stub uses env-only soft lock.
|
|
68
|
+
holder = os.environ.get("LEADER_HOLDER")
|
|
69
|
+
if holder is None or holder == self._identity:
|
|
70
|
+
os.environ["LEADER_HOLDER"] = self._identity
|
|
71
|
+
return True
|
|
72
|
+
return holder == self._identity
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def leader_election(**kwargs: Any) -> LeaderElection:
|
|
76
|
+
"""Factory for :class:`LeaderElection`."""
|
|
77
|
+
le = LeaderElection(**kwargs)
|
|
78
|
+
le.start()
|
|
79
|
+
return le
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = ["LeaderElection", "leader_election"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Tier 2 tiered-alert dispatcher.
|
|
2
|
+
|
|
3
|
+
Apps register a sender (any callable matching ``AlertSender``) at startup,
|
|
4
|
+
then code anywhere in the app can fire ``alert_dev_team(...)`` — the
|
|
5
|
+
dispatcher handles dedup, rate-limit, escalation, and HTML rendering.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from vibey_bootstrap.alerts.dispatcher import (
|
|
9
|
+
AlertRecord,
|
|
10
|
+
AlertSender,
|
|
11
|
+
AlertSeverity,
|
|
12
|
+
alert_dev_team,
|
|
13
|
+
drain_pending_alerts,
|
|
14
|
+
install_global_exception_hooks,
|
|
15
|
+
register_dispatcher,
|
|
16
|
+
reset_state,
|
|
17
|
+
)
|
|
18
|
+
from vibey_bootstrap.alerts.render import (
|
|
19
|
+
_render_alert_html,
|
|
20
|
+
render_pending_alerts_html,
|
|
21
|
+
)
|
|
22
|
+
from vibey_bootstrap.counters import bump_counter, counter_snapshot
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"AlertRecord",
|
|
26
|
+
"AlertSender",
|
|
27
|
+
"AlertSeverity",
|
|
28
|
+
"alert_dev_team",
|
|
29
|
+
"bump_counter",
|
|
30
|
+
"counter_snapshot",
|
|
31
|
+
"drain_pending_alerts",
|
|
32
|
+
"install_global_exception_hooks",
|
|
33
|
+
"register_dispatcher",
|
|
34
|
+
"render_pending_alerts_html",
|
|
35
|
+
"reset_state",
|
|
36
|
+
]
|