truthound 1.0.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.
- truthound/__init__.py +162 -0
- truthound/adapters.py +100 -0
- truthound/api.py +365 -0
- truthound/audit/__init__.py +248 -0
- truthound/audit/core.py +967 -0
- truthound/audit/filters.py +620 -0
- truthound/audit/formatters.py +707 -0
- truthound/audit/logger.py +902 -0
- truthound/audit/middleware.py +571 -0
- truthound/audit/storage.py +1083 -0
- truthound/benchmark/__init__.py +123 -0
- truthound/benchmark/base.py +757 -0
- truthound/benchmark/comparison.py +635 -0
- truthound/benchmark/generators.py +706 -0
- truthound/benchmark/reporters.py +718 -0
- truthound/benchmark/runner.py +635 -0
- truthound/benchmark/scenarios.py +712 -0
- truthound/cache.py +252 -0
- truthound/checkpoint/__init__.py +136 -0
- truthound/checkpoint/actions/__init__.py +164 -0
- truthound/checkpoint/actions/base.py +324 -0
- truthound/checkpoint/actions/custom.py +234 -0
- truthound/checkpoint/actions/discord_notify.py +290 -0
- truthound/checkpoint/actions/email_notify.py +405 -0
- truthound/checkpoint/actions/github_action.py +406 -0
- truthound/checkpoint/actions/opsgenie.py +1499 -0
- truthound/checkpoint/actions/pagerduty.py +226 -0
- truthound/checkpoint/actions/slack_notify.py +233 -0
- truthound/checkpoint/actions/store_result.py +249 -0
- truthound/checkpoint/actions/teams_notify.py +1570 -0
- truthound/checkpoint/actions/telegram_notify.py +419 -0
- truthound/checkpoint/actions/update_docs.py +552 -0
- truthound/checkpoint/actions/webhook.py +293 -0
- truthound/checkpoint/analytics/__init__.py +147 -0
- truthound/checkpoint/analytics/aggregations/__init__.py +23 -0
- truthound/checkpoint/analytics/aggregations/rollup.py +481 -0
- truthound/checkpoint/analytics/aggregations/time_bucket.py +306 -0
- truthound/checkpoint/analytics/analyzers/__init__.py +17 -0
- truthound/checkpoint/analytics/analyzers/anomaly.py +386 -0
- truthound/checkpoint/analytics/analyzers/base.py +270 -0
- truthound/checkpoint/analytics/analyzers/forecast.py +421 -0
- truthound/checkpoint/analytics/analyzers/trend.py +314 -0
- truthound/checkpoint/analytics/models.py +292 -0
- truthound/checkpoint/analytics/protocols.py +549 -0
- truthound/checkpoint/analytics/service.py +718 -0
- truthound/checkpoint/analytics/stores/__init__.py +16 -0
- truthound/checkpoint/analytics/stores/base.py +306 -0
- truthound/checkpoint/analytics/stores/memory_store.py +353 -0
- truthound/checkpoint/analytics/stores/sqlite_store.py +557 -0
- truthound/checkpoint/analytics/stores/timescale_store.py +501 -0
- truthound/checkpoint/async_actions.py +794 -0
- truthound/checkpoint/async_base.py +708 -0
- truthound/checkpoint/async_checkpoint.py +617 -0
- truthound/checkpoint/async_runner.py +639 -0
- truthound/checkpoint/checkpoint.py +527 -0
- truthound/checkpoint/ci/__init__.py +61 -0
- truthound/checkpoint/ci/detector.py +355 -0
- truthound/checkpoint/ci/reporter.py +436 -0
- truthound/checkpoint/ci/templates.py +454 -0
- truthound/checkpoint/circuitbreaker/__init__.py +133 -0
- truthound/checkpoint/circuitbreaker/breaker.py +542 -0
- truthound/checkpoint/circuitbreaker/core.py +252 -0
- truthound/checkpoint/circuitbreaker/detection.py +459 -0
- truthound/checkpoint/circuitbreaker/middleware.py +389 -0
- truthound/checkpoint/circuitbreaker/registry.py +357 -0
- truthound/checkpoint/distributed/__init__.py +139 -0
- truthound/checkpoint/distributed/backends/__init__.py +35 -0
- truthound/checkpoint/distributed/backends/celery_backend.py +503 -0
- truthound/checkpoint/distributed/backends/kubernetes_backend.py +696 -0
- truthound/checkpoint/distributed/backends/local_backend.py +397 -0
- truthound/checkpoint/distributed/backends/ray_backend.py +625 -0
- truthound/checkpoint/distributed/base.py +774 -0
- truthound/checkpoint/distributed/orchestrator.py +765 -0
- truthound/checkpoint/distributed/protocols.py +842 -0
- truthound/checkpoint/distributed/registry.py +449 -0
- truthound/checkpoint/idempotency/__init__.py +120 -0
- truthound/checkpoint/idempotency/core.py +295 -0
- truthound/checkpoint/idempotency/fingerprint.py +454 -0
- truthound/checkpoint/idempotency/locking.py +604 -0
- truthound/checkpoint/idempotency/service.py +592 -0
- truthound/checkpoint/idempotency/stores.py +653 -0
- truthound/checkpoint/monitoring/__init__.py +134 -0
- truthound/checkpoint/monitoring/aggregators/__init__.py +15 -0
- truthound/checkpoint/monitoring/aggregators/base.py +372 -0
- truthound/checkpoint/monitoring/aggregators/realtime.py +300 -0
- truthound/checkpoint/monitoring/aggregators/window.py +493 -0
- truthound/checkpoint/monitoring/collectors/__init__.py +17 -0
- truthound/checkpoint/monitoring/collectors/base.py +257 -0
- truthound/checkpoint/monitoring/collectors/memory_collector.py +617 -0
- truthound/checkpoint/monitoring/collectors/prometheus_collector.py +451 -0
- truthound/checkpoint/monitoring/collectors/redis_collector.py +518 -0
- truthound/checkpoint/monitoring/events.py +410 -0
- truthound/checkpoint/monitoring/protocols.py +636 -0
- truthound/checkpoint/monitoring/service.py +578 -0
- truthound/checkpoint/monitoring/views/__init__.py +17 -0
- truthound/checkpoint/monitoring/views/base.py +172 -0
- truthound/checkpoint/monitoring/views/queue_view.py +220 -0
- truthound/checkpoint/monitoring/views/task_view.py +240 -0
- truthound/checkpoint/monitoring/views/worker_view.py +263 -0
- truthound/checkpoint/registry.py +337 -0
- truthound/checkpoint/runner.py +356 -0
- truthound/checkpoint/transaction/__init__.py +133 -0
- truthound/checkpoint/transaction/base.py +389 -0
- truthound/checkpoint/transaction/compensatable.py +537 -0
- truthound/checkpoint/transaction/coordinator.py +576 -0
- truthound/checkpoint/transaction/executor.py +622 -0
- truthound/checkpoint/transaction/idempotency.py +534 -0
- truthound/checkpoint/transaction/saga/__init__.py +143 -0
- truthound/checkpoint/transaction/saga/builder.py +584 -0
- truthound/checkpoint/transaction/saga/definition.py +515 -0
- truthound/checkpoint/transaction/saga/event_store.py +542 -0
- truthound/checkpoint/transaction/saga/patterns.py +833 -0
- truthound/checkpoint/transaction/saga/runner.py +718 -0
- truthound/checkpoint/transaction/saga/state_machine.py +793 -0
- truthound/checkpoint/transaction/saga/strategies.py +780 -0
- truthound/checkpoint/transaction/saga/testing.py +886 -0
- truthound/checkpoint/triggers/__init__.py +58 -0
- truthound/checkpoint/triggers/base.py +237 -0
- truthound/checkpoint/triggers/event.py +385 -0
- truthound/checkpoint/triggers/schedule.py +355 -0
- truthound/cli.py +2358 -0
- truthound/cli_modules/__init__.py +124 -0
- truthound/cli_modules/advanced/__init__.py +45 -0
- truthound/cli_modules/advanced/benchmark.py +343 -0
- truthound/cli_modules/advanced/docs.py +225 -0
- truthound/cli_modules/advanced/lineage.py +209 -0
- truthound/cli_modules/advanced/ml.py +320 -0
- truthound/cli_modules/advanced/realtime.py +196 -0
- truthound/cli_modules/checkpoint/__init__.py +46 -0
- truthound/cli_modules/checkpoint/init.py +114 -0
- truthound/cli_modules/checkpoint/list.py +71 -0
- truthound/cli_modules/checkpoint/run.py +159 -0
- truthound/cli_modules/checkpoint/validate.py +67 -0
- truthound/cli_modules/common/__init__.py +71 -0
- truthound/cli_modules/common/errors.py +414 -0
- truthound/cli_modules/common/options.py +419 -0
- truthound/cli_modules/common/output.py +507 -0
- truthound/cli_modules/common/protocol.py +552 -0
- truthound/cli_modules/core/__init__.py +48 -0
- truthound/cli_modules/core/check.py +123 -0
- truthound/cli_modules/core/compare.py +104 -0
- truthound/cli_modules/core/learn.py +57 -0
- truthound/cli_modules/core/mask.py +77 -0
- truthound/cli_modules/core/profile.py +65 -0
- truthound/cli_modules/core/scan.py +61 -0
- truthound/cli_modules/profiler/__init__.py +51 -0
- truthound/cli_modules/profiler/auto_profile.py +175 -0
- truthound/cli_modules/profiler/metadata.py +107 -0
- truthound/cli_modules/profiler/suite.py +283 -0
- truthound/cli_modules/registry.py +431 -0
- truthound/cli_modules/scaffolding/__init__.py +89 -0
- truthound/cli_modules/scaffolding/base.py +631 -0
- truthound/cli_modules/scaffolding/commands.py +545 -0
- truthound/cli_modules/scaffolding/plugins.py +1072 -0
- truthound/cli_modules/scaffolding/reporters.py +594 -0
- truthound/cli_modules/scaffolding/validators.py +1127 -0
- truthound/common/__init__.py +18 -0
- truthound/common/resilience/__init__.py +130 -0
- truthound/common/resilience/bulkhead.py +266 -0
- truthound/common/resilience/circuit_breaker.py +516 -0
- truthound/common/resilience/composite.py +332 -0
- truthound/common/resilience/config.py +292 -0
- truthound/common/resilience/protocols.py +217 -0
- truthound/common/resilience/rate_limiter.py +404 -0
- truthound/common/resilience/retry.py +341 -0
- truthound/datadocs/__init__.py +260 -0
- truthound/datadocs/base.py +571 -0
- truthound/datadocs/builder.py +761 -0
- truthound/datadocs/charts.py +764 -0
- truthound/datadocs/dashboard/__init__.py +63 -0
- truthound/datadocs/dashboard/app.py +576 -0
- truthound/datadocs/dashboard/components.py +584 -0
- truthound/datadocs/dashboard/state.py +240 -0
- truthound/datadocs/engine/__init__.py +46 -0
- truthound/datadocs/engine/context.py +376 -0
- truthound/datadocs/engine/pipeline.py +618 -0
- truthound/datadocs/engine/registry.py +469 -0
- truthound/datadocs/exporters/__init__.py +49 -0
- truthound/datadocs/exporters/base.py +198 -0
- truthound/datadocs/exporters/html.py +178 -0
- truthound/datadocs/exporters/json_exporter.py +253 -0
- truthound/datadocs/exporters/markdown.py +284 -0
- truthound/datadocs/exporters/pdf.py +392 -0
- truthound/datadocs/i18n/__init__.py +86 -0
- truthound/datadocs/i18n/catalog.py +960 -0
- truthound/datadocs/i18n/formatting.py +505 -0
- truthound/datadocs/i18n/loader.py +256 -0
- truthound/datadocs/i18n/plurals.py +378 -0
- truthound/datadocs/renderers/__init__.py +42 -0
- truthound/datadocs/renderers/base.py +401 -0
- truthound/datadocs/renderers/custom.py +342 -0
- truthound/datadocs/renderers/jinja.py +697 -0
- truthound/datadocs/sections.py +736 -0
- truthound/datadocs/styles.py +931 -0
- truthound/datadocs/themes/__init__.py +101 -0
- truthound/datadocs/themes/base.py +336 -0
- truthound/datadocs/themes/default.py +417 -0
- truthound/datadocs/themes/enterprise.py +419 -0
- truthound/datadocs/themes/loader.py +336 -0
- truthound/datadocs/themes.py +301 -0
- truthound/datadocs/transformers/__init__.py +57 -0
- truthound/datadocs/transformers/base.py +268 -0
- truthound/datadocs/transformers/enrichers.py +544 -0
- truthound/datadocs/transformers/filters.py +447 -0
- truthound/datadocs/transformers/i18n.py +468 -0
- truthound/datadocs/versioning/__init__.py +62 -0
- truthound/datadocs/versioning/diff.py +639 -0
- truthound/datadocs/versioning/storage.py +497 -0
- truthound/datadocs/versioning/version.py +358 -0
- truthound/datasources/__init__.py +223 -0
- truthound/datasources/_async_protocols.py +222 -0
- truthound/datasources/_protocols.py +159 -0
- truthound/datasources/adapters.py +428 -0
- truthound/datasources/async_base.py +599 -0
- truthound/datasources/async_factory.py +511 -0
- truthound/datasources/base.py +516 -0
- truthound/datasources/factory.py +433 -0
- truthound/datasources/nosql/__init__.py +47 -0
- truthound/datasources/nosql/base.py +487 -0
- truthound/datasources/nosql/elasticsearch.py +801 -0
- truthound/datasources/nosql/mongodb.py +636 -0
- truthound/datasources/pandas_optimized.py +582 -0
- truthound/datasources/pandas_source.py +216 -0
- truthound/datasources/polars_source.py +395 -0
- truthound/datasources/spark_source.py +479 -0
- truthound/datasources/sql/__init__.py +154 -0
- truthound/datasources/sql/base.py +710 -0
- truthound/datasources/sql/bigquery.py +410 -0
- truthound/datasources/sql/cloud_base.py +199 -0
- truthound/datasources/sql/databricks.py +471 -0
- truthound/datasources/sql/mysql.py +316 -0
- truthound/datasources/sql/oracle.py +427 -0
- truthound/datasources/sql/postgresql.py +321 -0
- truthound/datasources/sql/redshift.py +479 -0
- truthound/datasources/sql/snowflake.py +439 -0
- truthound/datasources/sql/sqlite.py +286 -0
- truthound/datasources/sql/sqlserver.py +437 -0
- truthound/datasources/streaming/__init__.py +47 -0
- truthound/datasources/streaming/base.py +350 -0
- truthound/datasources/streaming/kafka.py +670 -0
- truthound/decorators.py +98 -0
- truthound/docs/__init__.py +69 -0
- truthound/docs/extractor.py +971 -0
- truthound/docs/generator.py +601 -0
- truthound/docs/parser.py +1037 -0
- truthound/docs/renderer.py +999 -0
- truthound/drift/__init__.py +22 -0
- truthound/drift/compare.py +189 -0
- truthound/drift/detectors.py +464 -0
- truthound/drift/report.py +160 -0
- truthound/execution/__init__.py +65 -0
- truthound/execution/_protocols.py +324 -0
- truthound/execution/base.py +576 -0
- truthound/execution/distributed/__init__.py +179 -0
- truthound/execution/distributed/aggregations.py +731 -0
- truthound/execution/distributed/arrow_bridge.py +817 -0
- truthound/execution/distributed/base.py +550 -0
- truthound/execution/distributed/dask_engine.py +976 -0
- truthound/execution/distributed/mixins.py +766 -0
- truthound/execution/distributed/protocols.py +756 -0
- truthound/execution/distributed/ray_engine.py +1127 -0
- truthound/execution/distributed/registry.py +446 -0
- truthound/execution/distributed/spark_engine.py +1011 -0
- truthound/execution/distributed/validator_adapter.py +682 -0
- truthound/execution/pandas_engine.py +401 -0
- truthound/execution/polars_engine.py +497 -0
- truthound/execution/pushdown/__init__.py +230 -0
- truthound/execution/pushdown/ast.py +1550 -0
- truthound/execution/pushdown/builder.py +1550 -0
- truthound/execution/pushdown/dialects.py +1072 -0
- truthound/execution/pushdown/executor.py +829 -0
- truthound/execution/pushdown/optimizer.py +1041 -0
- truthound/execution/sql_engine.py +518 -0
- truthound/infrastructure/__init__.py +189 -0
- truthound/infrastructure/audit.py +1515 -0
- truthound/infrastructure/config.py +1133 -0
- truthound/infrastructure/encryption.py +1132 -0
- truthound/infrastructure/logging.py +1503 -0
- truthound/infrastructure/metrics.py +1220 -0
- truthound/lineage/__init__.py +89 -0
- truthound/lineage/base.py +746 -0
- truthound/lineage/impact_analysis.py +474 -0
- truthound/lineage/integrations/__init__.py +22 -0
- truthound/lineage/integrations/openlineage.py +548 -0
- truthound/lineage/tracker.py +512 -0
- truthound/lineage/visualization/__init__.py +33 -0
- truthound/lineage/visualization/protocols.py +145 -0
- truthound/lineage/visualization/renderers/__init__.py +20 -0
- truthound/lineage/visualization/renderers/cytoscape.py +329 -0
- truthound/lineage/visualization/renderers/d3.py +331 -0
- truthound/lineage/visualization/renderers/graphviz.py +276 -0
- truthound/lineage/visualization/renderers/mermaid.py +308 -0
- truthound/maskers.py +113 -0
- truthound/ml/__init__.py +124 -0
- truthound/ml/anomaly_models/__init__.py +31 -0
- truthound/ml/anomaly_models/ensemble.py +362 -0
- truthound/ml/anomaly_models/isolation_forest.py +444 -0
- truthound/ml/anomaly_models/statistical.py +392 -0
- truthound/ml/base.py +1178 -0
- truthound/ml/drift_detection/__init__.py +26 -0
- truthound/ml/drift_detection/concept.py +381 -0
- truthound/ml/drift_detection/distribution.py +361 -0
- truthound/ml/drift_detection/feature.py +442 -0
- truthound/ml/drift_detection/multivariate.py +495 -0
- truthound/ml/monitoring/__init__.py +88 -0
- truthound/ml/monitoring/alerting/__init__.py +33 -0
- truthound/ml/monitoring/alerting/handlers.py +427 -0
- truthound/ml/monitoring/alerting/rules.py +508 -0
- truthound/ml/monitoring/collectors/__init__.py +19 -0
- truthound/ml/monitoring/collectors/composite.py +105 -0
- truthound/ml/monitoring/collectors/drift.py +324 -0
- truthound/ml/monitoring/collectors/performance.py +179 -0
- truthound/ml/monitoring/collectors/quality.py +369 -0
- truthound/ml/monitoring/monitor.py +536 -0
- truthound/ml/monitoring/protocols.py +451 -0
- truthound/ml/monitoring/stores/__init__.py +15 -0
- truthound/ml/monitoring/stores/memory.py +201 -0
- truthound/ml/monitoring/stores/prometheus.py +296 -0
- truthound/ml/rule_learning/__init__.py +25 -0
- truthound/ml/rule_learning/constraint_miner.py +443 -0
- truthound/ml/rule_learning/pattern_learner.py +499 -0
- truthound/ml/rule_learning/profile_learner.py +462 -0
- truthound/multitenancy/__init__.py +326 -0
- truthound/multitenancy/core.py +852 -0
- truthound/multitenancy/integration.py +597 -0
- truthound/multitenancy/isolation.py +630 -0
- truthound/multitenancy/manager.py +770 -0
- truthound/multitenancy/middleware.py +765 -0
- truthound/multitenancy/quota.py +537 -0
- truthound/multitenancy/resolvers.py +603 -0
- truthound/multitenancy/storage.py +703 -0
- truthound/observability/__init__.py +307 -0
- truthound/observability/context.py +531 -0
- truthound/observability/instrumentation.py +611 -0
- truthound/observability/logging.py +887 -0
- truthound/observability/metrics.py +1157 -0
- truthound/observability/tracing/__init__.py +178 -0
- truthound/observability/tracing/baggage.py +310 -0
- truthound/observability/tracing/config.py +426 -0
- truthound/observability/tracing/exporter.py +787 -0
- truthound/observability/tracing/integration.py +1018 -0
- truthound/observability/tracing/otel/__init__.py +146 -0
- truthound/observability/tracing/otel/adapter.py +982 -0
- truthound/observability/tracing/otel/bridge.py +1177 -0
- truthound/observability/tracing/otel/compat.py +681 -0
- truthound/observability/tracing/otel/config.py +691 -0
- truthound/observability/tracing/otel/detection.py +327 -0
- truthound/observability/tracing/otel/protocols.py +426 -0
- truthound/observability/tracing/processor.py +561 -0
- truthound/observability/tracing/propagator.py +757 -0
- truthound/observability/tracing/provider.py +569 -0
- truthound/observability/tracing/resource.py +515 -0
- truthound/observability/tracing/sampler.py +487 -0
- truthound/observability/tracing/span.py +676 -0
- truthound/plugins/__init__.py +198 -0
- truthound/plugins/base.py +599 -0
- truthound/plugins/cli.py +680 -0
- truthound/plugins/dependencies/__init__.py +42 -0
- truthound/plugins/dependencies/graph.py +422 -0
- truthound/plugins/dependencies/resolver.py +417 -0
- truthound/plugins/discovery.py +379 -0
- truthound/plugins/docs/__init__.py +46 -0
- truthound/plugins/docs/extractor.py +444 -0
- truthound/plugins/docs/renderer.py +499 -0
- truthound/plugins/enterprise_manager.py +877 -0
- truthound/plugins/examples/__init__.py +19 -0
- truthound/plugins/examples/custom_validators.py +317 -0
- truthound/plugins/examples/slack_notifier.py +312 -0
- truthound/plugins/examples/xml_reporter.py +254 -0
- truthound/plugins/hooks.py +558 -0
- truthound/plugins/lifecycle/__init__.py +43 -0
- truthound/plugins/lifecycle/hot_reload.py +402 -0
- truthound/plugins/lifecycle/manager.py +371 -0
- truthound/plugins/manager.py +736 -0
- truthound/plugins/registry.py +338 -0
- truthound/plugins/security/__init__.py +93 -0
- truthound/plugins/security/exceptions.py +332 -0
- truthound/plugins/security/policies.py +348 -0
- truthound/plugins/security/protocols.py +643 -0
- truthound/plugins/security/sandbox/__init__.py +45 -0
- truthound/plugins/security/sandbox/context.py +158 -0
- truthound/plugins/security/sandbox/engines/__init__.py +19 -0
- truthound/plugins/security/sandbox/engines/container.py +379 -0
- truthound/plugins/security/sandbox/engines/noop.py +144 -0
- truthound/plugins/security/sandbox/engines/process.py +336 -0
- truthound/plugins/security/sandbox/factory.py +211 -0
- truthound/plugins/security/signing/__init__.py +57 -0
- truthound/plugins/security/signing/service.py +330 -0
- truthound/plugins/security/signing/trust_store.py +368 -0
- truthound/plugins/security/signing/verifier.py +459 -0
- truthound/plugins/versioning/__init__.py +41 -0
- truthound/plugins/versioning/constraints.py +297 -0
- truthound/plugins/versioning/resolver.py +329 -0
- truthound/profiler/__init__.py +1729 -0
- truthound/profiler/_lazy.py +452 -0
- truthound/profiler/ab_testing/__init__.py +80 -0
- truthound/profiler/ab_testing/analysis.py +449 -0
- truthound/profiler/ab_testing/base.py +257 -0
- truthound/profiler/ab_testing/experiment.py +395 -0
- truthound/profiler/ab_testing/tracking.py +368 -0
- truthound/profiler/auto_threshold.py +1170 -0
- truthound/profiler/base.py +579 -0
- truthound/profiler/cache_patterns.py +911 -0
- truthound/profiler/caching.py +1303 -0
- truthound/profiler/column_profiler.py +712 -0
- truthound/profiler/comparison.py +1007 -0
- truthound/profiler/custom_patterns.py +1170 -0
- truthound/profiler/dashboard/__init__.py +50 -0
- truthound/profiler/dashboard/app.py +476 -0
- truthound/profiler/dashboard/components.py +457 -0
- truthound/profiler/dashboard/config.py +72 -0
- truthound/profiler/distributed/__init__.py +83 -0
- truthound/profiler/distributed/base.py +281 -0
- truthound/profiler/distributed/dask_backend.py +498 -0
- truthound/profiler/distributed/local_backend.py +293 -0
- truthound/profiler/distributed/profiler.py +304 -0
- truthound/profiler/distributed/ray_backend.py +374 -0
- truthound/profiler/distributed/spark_backend.py +375 -0
- truthound/profiler/distributed.py +1366 -0
- truthound/profiler/enterprise_sampling.py +1065 -0
- truthound/profiler/errors.py +488 -0
- truthound/profiler/evolution/__init__.py +91 -0
- truthound/profiler/evolution/alerts.py +426 -0
- truthound/profiler/evolution/changes.py +206 -0
- truthound/profiler/evolution/compatibility.py +365 -0
- truthound/profiler/evolution/detector.py +372 -0
- truthound/profiler/evolution/protocols.py +121 -0
- truthound/profiler/generators/__init__.py +48 -0
- truthound/profiler/generators/base.py +384 -0
- truthound/profiler/generators/ml_rules.py +375 -0
- truthound/profiler/generators/pattern_rules.py +384 -0
- truthound/profiler/generators/schema_rules.py +267 -0
- truthound/profiler/generators/stats_rules.py +324 -0
- truthound/profiler/generators/suite_generator.py +857 -0
- truthound/profiler/i18n.py +1542 -0
- truthound/profiler/incremental.py +554 -0
- truthound/profiler/incremental_validation.py +1710 -0
- truthound/profiler/integration/__init__.py +73 -0
- truthound/profiler/integration/adapters.py +345 -0
- truthound/profiler/integration/context.py +371 -0
- truthound/profiler/integration/executor.py +527 -0
- truthound/profiler/integration/naming.py +75 -0
- truthound/profiler/integration/protocols.py +243 -0
- truthound/profiler/memory.py +1185 -0
- truthound/profiler/migration/__init__.py +60 -0
- truthound/profiler/migration/base.py +345 -0
- truthound/profiler/migration/manager.py +444 -0
- truthound/profiler/migration/v1_0_to_v1_1.py +484 -0
- truthound/profiler/ml/__init__.py +73 -0
- truthound/profiler/ml/base.py +244 -0
- truthound/profiler/ml/classifier.py +507 -0
- truthound/profiler/ml/feature_extraction.py +604 -0
- truthound/profiler/ml/pretrained.py +448 -0
- truthound/profiler/ml_inference.py +1276 -0
- truthound/profiler/native_patterns.py +815 -0
- truthound/profiler/observability.py +1184 -0
- truthound/profiler/process_timeout.py +1566 -0
- truthound/profiler/progress.py +568 -0
- truthound/profiler/progress_callbacks.py +1734 -0
- truthound/profiler/quality.py +1345 -0
- truthound/profiler/resilience.py +1180 -0
- truthound/profiler/sampled_matcher.py +794 -0
- truthound/profiler/sampling.py +1288 -0
- truthound/profiler/scheduling/__init__.py +82 -0
- truthound/profiler/scheduling/protocols.py +214 -0
- truthound/profiler/scheduling/scheduler.py +474 -0
- truthound/profiler/scheduling/storage.py +457 -0
- truthound/profiler/scheduling/triggers.py +449 -0
- truthound/profiler/schema.py +603 -0
- truthound/profiler/streaming.py +685 -0
- truthound/profiler/streaming_patterns.py +1354 -0
- truthound/profiler/suite_cli.py +625 -0
- truthound/profiler/suite_config.py +789 -0
- truthound/profiler/suite_export.py +1268 -0
- truthound/profiler/table_profiler.py +547 -0
- truthound/profiler/timeout.py +565 -0
- truthound/profiler/validation.py +1532 -0
- truthound/profiler/visualization/__init__.py +118 -0
- truthound/profiler/visualization/base.py +346 -0
- truthound/profiler/visualization/generator.py +1259 -0
- truthound/profiler/visualization/plotly_renderer.py +811 -0
- truthound/profiler/visualization/renderers.py +669 -0
- truthound/profiler/visualization/sections.py +540 -0
- truthound/profiler/visualization.py +2122 -0
- truthound/profiler/yaml_validation.py +1151 -0
- truthound/py.typed +0 -0
- truthound/ratelimit/__init__.py +248 -0
- truthound/ratelimit/algorithms.py +1108 -0
- truthound/ratelimit/core.py +573 -0
- truthound/ratelimit/integration.py +532 -0
- truthound/ratelimit/limiter.py +663 -0
- truthound/ratelimit/middleware.py +700 -0
- truthound/ratelimit/policy.py +792 -0
- truthound/ratelimit/storage.py +763 -0
- truthound/rbac/__init__.py +340 -0
- truthound/rbac/core.py +976 -0
- truthound/rbac/integration.py +760 -0
- truthound/rbac/manager.py +1052 -0
- truthound/rbac/middleware.py +842 -0
- truthound/rbac/policy.py +954 -0
- truthound/rbac/storage.py +878 -0
- truthound/realtime/__init__.py +141 -0
- truthound/realtime/adapters/__init__.py +43 -0
- truthound/realtime/adapters/base.py +533 -0
- truthound/realtime/adapters/kafka.py +487 -0
- truthound/realtime/adapters/kinesis.py +479 -0
- truthound/realtime/adapters/mock.py +243 -0
- truthound/realtime/base.py +553 -0
- truthound/realtime/factory.py +382 -0
- truthound/realtime/incremental.py +660 -0
- truthound/realtime/processing/__init__.py +67 -0
- truthound/realtime/processing/exactly_once.py +575 -0
- truthound/realtime/processing/state.py +547 -0
- truthound/realtime/processing/windows.py +647 -0
- truthound/realtime/protocols.py +569 -0
- truthound/realtime/streaming.py +605 -0
- truthound/realtime/testing/__init__.py +32 -0
- truthound/realtime/testing/containers.py +615 -0
- truthound/realtime/testing/fixtures.py +484 -0
- truthound/report.py +280 -0
- truthound/reporters/__init__.py +46 -0
- truthound/reporters/_protocols.py +30 -0
- truthound/reporters/base.py +324 -0
- truthound/reporters/ci/__init__.py +66 -0
- truthound/reporters/ci/azure.py +436 -0
- truthound/reporters/ci/base.py +509 -0
- truthound/reporters/ci/bitbucket.py +567 -0
- truthound/reporters/ci/circleci.py +547 -0
- truthound/reporters/ci/detection.py +364 -0
- truthound/reporters/ci/factory.py +182 -0
- truthound/reporters/ci/github.py +388 -0
- truthound/reporters/ci/gitlab.py +471 -0
- truthound/reporters/ci/jenkins.py +525 -0
- truthound/reporters/console_reporter.py +299 -0
- truthound/reporters/factory.py +211 -0
- truthound/reporters/html_reporter.py +524 -0
- truthound/reporters/json_reporter.py +256 -0
- truthound/reporters/markdown_reporter.py +280 -0
- truthound/reporters/sdk/__init__.py +174 -0
- truthound/reporters/sdk/builder.py +558 -0
- truthound/reporters/sdk/mixins.py +1150 -0
- truthound/reporters/sdk/schema.py +1493 -0
- truthound/reporters/sdk/templates.py +666 -0
- truthound/reporters/sdk/testing.py +968 -0
- truthound/scanners.py +170 -0
- truthound/scheduling/__init__.py +122 -0
- truthound/scheduling/cron.py +1136 -0
- truthound/scheduling/presets.py +212 -0
- truthound/schema.py +275 -0
- truthound/secrets/__init__.py +173 -0
- truthound/secrets/base.py +618 -0
- truthound/secrets/cloud.py +682 -0
- truthound/secrets/integration.py +507 -0
- truthound/secrets/manager.py +633 -0
- truthound/secrets/oidc/__init__.py +172 -0
- truthound/secrets/oidc/base.py +902 -0
- truthound/secrets/oidc/credential_provider.py +623 -0
- truthound/secrets/oidc/exchangers.py +1001 -0
- truthound/secrets/oidc/github/__init__.py +110 -0
- truthound/secrets/oidc/github/claims.py +718 -0
- truthound/secrets/oidc/github/enhanced_provider.py +693 -0
- truthound/secrets/oidc/github/trust_policy.py +742 -0
- truthound/secrets/oidc/github/verification.py +723 -0
- truthound/secrets/oidc/github/workflow.py +691 -0
- truthound/secrets/oidc/providers.py +825 -0
- truthound/secrets/providers.py +506 -0
- truthound/secrets/resolver.py +495 -0
- truthound/stores/__init__.py +177 -0
- truthound/stores/backends/__init__.py +18 -0
- truthound/stores/backends/_protocols.py +340 -0
- truthound/stores/backends/azure_blob.py +530 -0
- truthound/stores/backends/concurrent_filesystem.py +915 -0
- truthound/stores/backends/connection_pool.py +1365 -0
- truthound/stores/backends/database.py +743 -0
- truthound/stores/backends/filesystem.py +538 -0
- truthound/stores/backends/gcs.py +399 -0
- truthound/stores/backends/memory.py +354 -0
- truthound/stores/backends/s3.py +434 -0
- truthound/stores/backpressure/__init__.py +84 -0
- truthound/stores/backpressure/base.py +375 -0
- truthound/stores/backpressure/circuit_breaker.py +434 -0
- truthound/stores/backpressure/monitor.py +376 -0
- truthound/stores/backpressure/strategies.py +677 -0
- truthound/stores/base.py +551 -0
- truthound/stores/batching/__init__.py +65 -0
- truthound/stores/batching/base.py +305 -0
- truthound/stores/batching/buffer.py +370 -0
- truthound/stores/batching/store.py +248 -0
- truthound/stores/batching/writer.py +521 -0
- truthound/stores/caching/__init__.py +60 -0
- truthound/stores/caching/backends.py +684 -0
- truthound/stores/caching/base.py +356 -0
- truthound/stores/caching/store.py +305 -0
- truthound/stores/compression/__init__.py +193 -0
- truthound/stores/compression/adaptive.py +694 -0
- truthound/stores/compression/base.py +514 -0
- truthound/stores/compression/pipeline.py +868 -0
- truthound/stores/compression/providers.py +672 -0
- truthound/stores/compression/streaming.py +832 -0
- truthound/stores/concurrency/__init__.py +81 -0
- truthound/stores/concurrency/atomic.py +556 -0
- truthound/stores/concurrency/index.py +775 -0
- truthound/stores/concurrency/locks.py +576 -0
- truthound/stores/concurrency/manager.py +482 -0
- truthound/stores/encryption/__init__.py +297 -0
- truthound/stores/encryption/base.py +952 -0
- truthound/stores/encryption/keys.py +1191 -0
- truthound/stores/encryption/pipeline.py +903 -0
- truthound/stores/encryption/providers.py +953 -0
- truthound/stores/encryption/streaming.py +950 -0
- truthound/stores/expectations.py +227 -0
- truthound/stores/factory.py +246 -0
- truthound/stores/migration/__init__.py +75 -0
- truthound/stores/migration/base.py +480 -0
- truthound/stores/migration/manager.py +347 -0
- truthound/stores/migration/registry.py +382 -0
- truthound/stores/migration/store.py +559 -0
- truthound/stores/observability/__init__.py +106 -0
- truthound/stores/observability/audit.py +718 -0
- truthound/stores/observability/config.py +270 -0
- truthound/stores/observability/factory.py +208 -0
- truthound/stores/observability/metrics.py +636 -0
- truthound/stores/observability/protocols.py +410 -0
- truthound/stores/observability/store.py +570 -0
- truthound/stores/observability/tracing.py +784 -0
- truthound/stores/replication/__init__.py +76 -0
- truthound/stores/replication/base.py +260 -0
- truthound/stores/replication/monitor.py +269 -0
- truthound/stores/replication/store.py +439 -0
- truthound/stores/replication/syncer.py +391 -0
- truthound/stores/results.py +359 -0
- truthound/stores/retention/__init__.py +77 -0
- truthound/stores/retention/base.py +378 -0
- truthound/stores/retention/policies.py +621 -0
- truthound/stores/retention/scheduler.py +279 -0
- truthound/stores/retention/store.py +526 -0
- truthound/stores/streaming/__init__.py +138 -0
- truthound/stores/streaming/base.py +801 -0
- truthound/stores/streaming/database.py +984 -0
- truthound/stores/streaming/filesystem.py +719 -0
- truthound/stores/streaming/reader.py +629 -0
- truthound/stores/streaming/s3.py +843 -0
- truthound/stores/streaming/writer.py +790 -0
- truthound/stores/tiering/__init__.py +108 -0
- truthound/stores/tiering/base.py +462 -0
- truthound/stores/tiering/manager.py +249 -0
- truthound/stores/tiering/policies.py +692 -0
- truthound/stores/tiering/store.py +526 -0
- truthound/stores/versioning/__init__.py +56 -0
- truthound/stores/versioning/base.py +376 -0
- truthound/stores/versioning/store.py +660 -0
- truthound/stores/versioning/strategies.py +353 -0
- truthound/types.py +56 -0
- truthound/validators/__init__.py +774 -0
- truthound/validators/aggregate/__init__.py +27 -0
- truthound/validators/aggregate/central.py +116 -0
- truthound/validators/aggregate/extremes.py +116 -0
- truthound/validators/aggregate/spread.py +118 -0
- truthound/validators/aggregate/sum.py +64 -0
- truthound/validators/aggregate/type.py +78 -0
- truthound/validators/anomaly/__init__.py +93 -0
- truthound/validators/anomaly/base.py +431 -0
- truthound/validators/anomaly/ml_based.py +1190 -0
- truthound/validators/anomaly/multivariate.py +647 -0
- truthound/validators/anomaly/statistical.py +599 -0
- truthound/validators/base.py +1089 -0
- truthound/validators/business_rule/__init__.py +46 -0
- truthound/validators/business_rule/base.py +147 -0
- truthound/validators/business_rule/checksum.py +509 -0
- truthound/validators/business_rule/financial.py +526 -0
- truthound/validators/cache.py +733 -0
- truthound/validators/completeness/__init__.py +39 -0
- truthound/validators/completeness/conditional.py +73 -0
- truthound/validators/completeness/default.py +98 -0
- truthound/validators/completeness/empty.py +103 -0
- truthound/validators/completeness/nan.py +337 -0
- truthound/validators/completeness/null.py +152 -0
- truthound/validators/cross_table/__init__.py +17 -0
- truthound/validators/cross_table/aggregate.py +333 -0
- truthound/validators/cross_table/row_count.py +122 -0
- truthound/validators/datetime/__init__.py +29 -0
- truthound/validators/datetime/format.py +78 -0
- truthound/validators/datetime/freshness.py +269 -0
- truthound/validators/datetime/order.py +73 -0
- truthound/validators/datetime/parseable.py +185 -0
- truthound/validators/datetime/range.py +202 -0
- truthound/validators/datetime/timezone.py +69 -0
- truthound/validators/distribution/__init__.py +49 -0
- truthound/validators/distribution/distribution.py +128 -0
- truthound/validators/distribution/monotonic.py +119 -0
- truthound/validators/distribution/outlier.py +178 -0
- truthound/validators/distribution/quantile.py +80 -0
- truthound/validators/distribution/range.py +254 -0
- truthound/validators/distribution/set.py +125 -0
- truthound/validators/distribution/statistical.py +459 -0
- truthound/validators/drift/__init__.py +79 -0
- truthound/validators/drift/base.py +427 -0
- truthound/validators/drift/multi_feature.py +401 -0
- truthound/validators/drift/numeric.py +395 -0
- truthound/validators/drift/psi.py +446 -0
- truthound/validators/drift/statistical.py +510 -0
- truthound/validators/enterprise.py +1658 -0
- truthound/validators/geospatial/__init__.py +80 -0
- truthound/validators/geospatial/base.py +97 -0
- truthound/validators/geospatial/boundary.py +238 -0
- truthound/validators/geospatial/coordinate.py +351 -0
- truthound/validators/geospatial/distance.py +399 -0
- truthound/validators/geospatial/polygon.py +665 -0
- truthound/validators/i18n/__init__.py +308 -0
- truthound/validators/i18n/bidi.py +571 -0
- truthound/validators/i18n/catalogs.py +570 -0
- truthound/validators/i18n/dialects.py +763 -0
- truthound/validators/i18n/extended_catalogs.py +549 -0
- truthound/validators/i18n/formatting.py +1434 -0
- truthound/validators/i18n/loader.py +1020 -0
- truthound/validators/i18n/messages.py +521 -0
- truthound/validators/i18n/plural.py +683 -0
- truthound/validators/i18n/protocols.py +855 -0
- truthound/validators/i18n/tms.py +1162 -0
- truthound/validators/localization/__init__.py +53 -0
- truthound/validators/localization/base.py +122 -0
- truthound/validators/localization/chinese.py +362 -0
- truthound/validators/localization/japanese.py +275 -0
- truthound/validators/localization/korean.py +524 -0
- truthound/validators/memory/__init__.py +94 -0
- truthound/validators/memory/approximate_knn.py +506 -0
- truthound/validators/memory/base.py +547 -0
- truthound/validators/memory/sgd_online.py +719 -0
- truthound/validators/memory/streaming_ecdf.py +753 -0
- truthound/validators/ml_feature/__init__.py +54 -0
- truthound/validators/ml_feature/base.py +249 -0
- truthound/validators/ml_feature/correlation.py +299 -0
- truthound/validators/ml_feature/leakage.py +344 -0
- truthound/validators/ml_feature/null_impact.py +270 -0
- truthound/validators/ml_feature/scale.py +264 -0
- truthound/validators/multi_column/__init__.py +89 -0
- truthound/validators/multi_column/arithmetic.py +284 -0
- truthound/validators/multi_column/base.py +231 -0
- truthound/validators/multi_column/comparison.py +273 -0
- truthound/validators/multi_column/consistency.py +312 -0
- truthound/validators/multi_column/statistical.py +299 -0
- truthound/validators/optimization/__init__.py +164 -0
- truthound/validators/optimization/aggregation.py +563 -0
- truthound/validators/optimization/covariance.py +556 -0
- truthound/validators/optimization/geo.py +626 -0
- truthound/validators/optimization/graph.py +587 -0
- truthound/validators/optimization/orchestrator.py +970 -0
- truthound/validators/optimization/profiling.py +1312 -0
- truthound/validators/privacy/__init__.py +223 -0
- truthound/validators/privacy/base.py +635 -0
- truthound/validators/privacy/ccpa.py +670 -0
- truthound/validators/privacy/gdpr.py +728 -0
- truthound/validators/privacy/global_patterns.py +604 -0
- truthound/validators/privacy/plugins.py +867 -0
- truthound/validators/profiling/__init__.py +52 -0
- truthound/validators/profiling/base.py +175 -0
- truthound/validators/profiling/cardinality.py +312 -0
- truthound/validators/profiling/entropy.py +391 -0
- truthound/validators/profiling/frequency.py +455 -0
- truthound/validators/pushdown_support.py +660 -0
- truthound/validators/query/__init__.py +91 -0
- truthound/validators/query/aggregate.py +346 -0
- truthound/validators/query/base.py +246 -0
- truthound/validators/query/column.py +249 -0
- truthound/validators/query/expression.py +274 -0
- truthound/validators/query/result.py +323 -0
- truthound/validators/query/row_count.py +264 -0
- truthound/validators/referential/__init__.py +80 -0
- truthound/validators/referential/base.py +395 -0
- truthound/validators/referential/cascade.py +391 -0
- truthound/validators/referential/circular.py +563 -0
- truthound/validators/referential/foreign_key.py +624 -0
- truthound/validators/referential/orphan.py +485 -0
- truthound/validators/registry.py +112 -0
- truthound/validators/schema/__init__.py +41 -0
- truthound/validators/schema/column_count.py +142 -0
- truthound/validators/schema/column_exists.py +80 -0
- truthound/validators/schema/column_order.py +82 -0
- truthound/validators/schema/column_pair.py +85 -0
- truthound/validators/schema/column_pair_set.py +195 -0
- truthound/validators/schema/column_type.py +94 -0
- truthound/validators/schema/multi_column.py +53 -0
- truthound/validators/schema/multi_column_aggregate.py +175 -0
- truthound/validators/schema/referential.py +274 -0
- truthound/validators/schema/table_schema.py +91 -0
- truthound/validators/schema_validator.py +219 -0
- truthound/validators/sdk/__init__.py +250 -0
- truthound/validators/sdk/builder.py +680 -0
- truthound/validators/sdk/decorators.py +474 -0
- truthound/validators/sdk/enterprise/__init__.py +211 -0
- truthound/validators/sdk/enterprise/docs.py +725 -0
- truthound/validators/sdk/enterprise/fuzzing.py +659 -0
- truthound/validators/sdk/enterprise/licensing.py +709 -0
- truthound/validators/sdk/enterprise/manager.py +543 -0
- truthound/validators/sdk/enterprise/resources.py +628 -0
- truthound/validators/sdk/enterprise/sandbox.py +766 -0
- truthound/validators/sdk/enterprise/signing.py +603 -0
- truthound/validators/sdk/enterprise/templates.py +865 -0
- truthound/validators/sdk/enterprise/versioning.py +659 -0
- truthound/validators/sdk/templates.py +757 -0
- truthound/validators/sdk/testing.py +807 -0
- truthound/validators/security/__init__.py +181 -0
- truthound/validators/security/redos/__init__.py +182 -0
- truthound/validators/security/redos/core.py +861 -0
- truthound/validators/security/redos/cpu_monitor.py +593 -0
- truthound/validators/security/redos/cve_database.py +791 -0
- truthound/validators/security/redos/ml/__init__.py +155 -0
- truthound/validators/security/redos/ml/base.py +785 -0
- truthound/validators/security/redos/ml/datasets.py +618 -0
- truthound/validators/security/redos/ml/features.py +359 -0
- truthound/validators/security/redos/ml/models.py +1000 -0
- truthound/validators/security/redos/ml/predictor.py +507 -0
- truthound/validators/security/redos/ml/storage.py +632 -0
- truthound/validators/security/redos/ml/training.py +571 -0
- truthound/validators/security/redos/ml_analyzer.py +937 -0
- truthound/validators/security/redos/optimizer.py +674 -0
- truthound/validators/security/redos/profiler.py +682 -0
- truthound/validators/security/redos/re2_engine.py +709 -0
- truthound/validators/security/redos.py +886 -0
- truthound/validators/security/sql_security.py +1247 -0
- truthound/validators/streaming/__init__.py +126 -0
- truthound/validators/streaming/base.py +292 -0
- truthound/validators/streaming/completeness.py +210 -0
- truthound/validators/streaming/mixin.py +575 -0
- truthound/validators/streaming/range.py +308 -0
- truthound/validators/streaming/sources.py +846 -0
- truthound/validators/string/__init__.py +57 -0
- truthound/validators/string/casing.py +158 -0
- truthound/validators/string/charset.py +96 -0
- truthound/validators/string/format.py +501 -0
- truthound/validators/string/json.py +77 -0
- truthound/validators/string/json_schema.py +184 -0
- truthound/validators/string/length.py +104 -0
- truthound/validators/string/like_pattern.py +237 -0
- truthound/validators/string/regex.py +202 -0
- truthound/validators/string/regex_extended.py +435 -0
- truthound/validators/table/__init__.py +88 -0
- truthound/validators/table/base.py +78 -0
- truthound/validators/table/column_count.py +198 -0
- truthound/validators/table/freshness.py +362 -0
- truthound/validators/table/row_count.py +251 -0
- truthound/validators/table/schema.py +333 -0
- truthound/validators/table/size.py +285 -0
- truthound/validators/timeout/__init__.py +102 -0
- truthound/validators/timeout/advanced/__init__.py +247 -0
- truthound/validators/timeout/advanced/circuit_breaker.py +675 -0
- truthound/validators/timeout/advanced/prediction.py +773 -0
- truthound/validators/timeout/advanced/priority.py +618 -0
- truthound/validators/timeout/advanced/redis_backend.py +770 -0
- truthound/validators/timeout/advanced/retry.py +721 -0
- truthound/validators/timeout/advanced/sampling.py +788 -0
- truthound/validators/timeout/advanced/sla.py +661 -0
- truthound/validators/timeout/advanced/telemetry.py +804 -0
- truthound/validators/timeout/cascade.py +477 -0
- truthound/validators/timeout/deadline.py +657 -0
- truthound/validators/timeout/degradation.py +525 -0
- truthound/validators/timeout/distributed.py +597 -0
- truthound/validators/timeseries/__init__.py +89 -0
- truthound/validators/timeseries/base.py +326 -0
- truthound/validators/timeseries/completeness.py +617 -0
- truthound/validators/timeseries/gap.py +485 -0
- truthound/validators/timeseries/monotonic.py +310 -0
- truthound/validators/timeseries/seasonality.py +422 -0
- truthound/validators/timeseries/trend.py +510 -0
- truthound/validators/uniqueness/__init__.py +59 -0
- truthound/validators/uniqueness/approximate.py +475 -0
- truthound/validators/uniqueness/distinct_values.py +253 -0
- truthound/validators/uniqueness/duplicate.py +118 -0
- truthound/validators/uniqueness/primary_key.py +140 -0
- truthound/validators/uniqueness/unique.py +191 -0
- truthound/validators/uniqueness/within_record.py +599 -0
- truthound/validators/utils.py +756 -0
- truthound-1.0.8.dist-info/METADATA +474 -0
- truthound-1.0.8.dist-info/RECORD +877 -0
- truthound-1.0.8.dist-info/WHEEL +4 -0
- truthound-1.0.8.dist-info/entry_points.txt +2 -0
- truthound-1.0.8.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
"""OIDC Identity Provider Implementations.
|
|
2
|
+
|
|
3
|
+
This module provides OIDC token retrieval for various CI/CD platforms:
|
|
4
|
+
- GitHub Actions
|
|
5
|
+
- GitLab CI/CD
|
|
6
|
+
- CircleCI
|
|
7
|
+
- Bitbucket Pipelines
|
|
8
|
+
|
|
9
|
+
Each provider handles the platform-specific token request mechanism.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import TYPE_CHECKING, Any
|
|
19
|
+
|
|
20
|
+
from truthound.secrets.oidc.base import (
|
|
21
|
+
BaseOIDCProvider,
|
|
22
|
+
CIProvider,
|
|
23
|
+
OIDCToken,
|
|
24
|
+
OIDCProviderNotAvailableError,
|
|
25
|
+
OIDCTokenError,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# GitHub Actions OIDC Provider
|
|
37
|
+
# =============================================================================
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class GitHubActionsConfig:
|
|
42
|
+
"""Configuration for GitHub Actions OIDC provider.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
audience: Token audience (default: based on cloud provider).
|
|
46
|
+
request_timeout: HTTP request timeout in seconds.
|
|
47
|
+
enable_cache: Whether to cache tokens.
|
|
48
|
+
cache_ttl_seconds: Token cache TTL.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
audience: str | None = None
|
|
52
|
+
request_timeout: float = 30.0
|
|
53
|
+
enable_cache: bool = True
|
|
54
|
+
cache_ttl_seconds: int = 300
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class GitHubActionsOIDCProvider(BaseOIDCProvider):
|
|
58
|
+
"""GitHub Actions OIDC token provider.
|
|
59
|
+
|
|
60
|
+
Retrieves OIDC tokens from GitHub Actions environment using the
|
|
61
|
+
ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
62
|
+
environment variables.
|
|
63
|
+
|
|
64
|
+
Note: The workflow must have `id-token: write` permission:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
permissions:
|
|
68
|
+
id-token: write
|
|
69
|
+
contents: read
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> provider = GitHubActionsOIDCProvider(
|
|
74
|
+
... audience="sts.amazonaws.com",
|
|
75
|
+
... )
|
|
76
|
+
>>> token = provider.get_token()
|
|
77
|
+
>>> print(token.claims.repository) # "owner/repo"
|
|
78
|
+
|
|
79
|
+
Token Claims (GitHub-specific):
|
|
80
|
+
- iss: https://token.actions.githubusercontent.com
|
|
81
|
+
- sub: repo:owner/repo:ref:refs/heads/main
|
|
82
|
+
- aud: configured audience
|
|
83
|
+
- repository: owner/repo
|
|
84
|
+
- repository_owner: owner
|
|
85
|
+
- ref: refs/heads/main
|
|
86
|
+
- sha: commit SHA
|
|
87
|
+
- actor: triggering user
|
|
88
|
+
- workflow: workflow name
|
|
89
|
+
- run_id: run ID
|
|
90
|
+
- environment: deployment environment (if any)
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
# Environment variables
|
|
94
|
+
TOKEN_URL_VAR = "ACTIONS_ID_TOKEN_REQUEST_URL"
|
|
95
|
+
TOKEN_VAR = "ACTIONS_ID_TOKEN_REQUEST_TOKEN"
|
|
96
|
+
GITHUB_ACTIONS_VAR = "GITHUB_ACTIONS"
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
audience: str | None = None,
|
|
101
|
+
*,
|
|
102
|
+
config: GitHubActionsConfig | None = None,
|
|
103
|
+
**kwargs: Any,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Initialize GitHub Actions OIDC provider.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
audience: Default token audience.
|
|
109
|
+
config: Full configuration object.
|
|
110
|
+
**kwargs: Additional base class arguments.
|
|
111
|
+
"""
|
|
112
|
+
self._config = config or GitHubActionsConfig(audience=audience)
|
|
113
|
+
if audience:
|
|
114
|
+
self._config.audience = audience
|
|
115
|
+
|
|
116
|
+
super().__init__(
|
|
117
|
+
cache_ttl_seconds=self._config.cache_ttl_seconds,
|
|
118
|
+
enable_cache=self._config.enable_cache,
|
|
119
|
+
**kwargs,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def name(self) -> str:
|
|
124
|
+
return "github_actions"
|
|
125
|
+
|
|
126
|
+
def is_available(self) -> bool:
|
|
127
|
+
"""Check if running in GitHub Actions with OIDC support."""
|
|
128
|
+
return (
|
|
129
|
+
os.environ.get(self.GITHUB_ACTIONS_VAR) == "true"
|
|
130
|
+
and self.TOKEN_URL_VAR in os.environ
|
|
131
|
+
and self.TOKEN_VAR in os.environ
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def _fetch_token(self, audience: str | None = None) -> str:
|
|
135
|
+
"""Fetch OIDC token from GitHub Actions.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
audience: Token audience (falls back to config).
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Raw JWT token string.
|
|
142
|
+
"""
|
|
143
|
+
import urllib.request
|
|
144
|
+
import urllib.error
|
|
145
|
+
import urllib.parse
|
|
146
|
+
|
|
147
|
+
token_url = os.environ.get(self.TOKEN_URL_VAR)
|
|
148
|
+
bearer_token = os.environ.get(self.TOKEN_VAR)
|
|
149
|
+
|
|
150
|
+
if not token_url or not bearer_token:
|
|
151
|
+
raise OIDCProviderNotAvailableError(
|
|
152
|
+
self.name,
|
|
153
|
+
f"Missing {self.TOKEN_URL_VAR} or {self.TOKEN_VAR}",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Use provided audience or config default
|
|
157
|
+
effective_audience = audience or self._config.audience
|
|
158
|
+
|
|
159
|
+
# Build URL with audience parameter
|
|
160
|
+
if effective_audience:
|
|
161
|
+
parsed = urllib.parse.urlparse(token_url)
|
|
162
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
163
|
+
query["audience"] = [effective_audience]
|
|
164
|
+
new_query = urllib.parse.urlencode(query, doseq=True)
|
|
165
|
+
token_url = urllib.parse.urlunparse(
|
|
166
|
+
parsed._replace(query=new_query)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Make request
|
|
170
|
+
request = urllib.request.Request(
|
|
171
|
+
token_url,
|
|
172
|
+
headers={
|
|
173
|
+
"Authorization": f"bearer {bearer_token}",
|
|
174
|
+
"Accept": "application/json; api-version=2.0",
|
|
175
|
+
"Content-Type": "application/json",
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
with urllib.request.urlopen(
|
|
181
|
+
request, timeout=self._config.request_timeout
|
|
182
|
+
) as response:
|
|
183
|
+
data = json.loads(response.read())
|
|
184
|
+
return data["value"]
|
|
185
|
+
|
|
186
|
+
except urllib.error.HTTPError as e:
|
|
187
|
+
error_body = ""
|
|
188
|
+
try:
|
|
189
|
+
error_body = e.read().decode()
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
raise OIDCTokenError(
|
|
193
|
+
f"HTTP {e.code}: {error_body or e.reason}",
|
|
194
|
+
provider=self.name,
|
|
195
|
+
) from e
|
|
196
|
+
except urllib.error.URLError as e:
|
|
197
|
+
raise OIDCTokenError(
|
|
198
|
+
f"Network error: {e.reason}",
|
|
199
|
+
provider=self.name,
|
|
200
|
+
) from e
|
|
201
|
+
except KeyError:
|
|
202
|
+
raise OIDCTokenError(
|
|
203
|
+
"Response missing 'value' field",
|
|
204
|
+
provider=self.name,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def get_environment_info(self) -> dict[str, str | None]:
|
|
208
|
+
"""Get GitHub Actions environment information."""
|
|
209
|
+
return {
|
|
210
|
+
"repository": os.environ.get("GITHUB_REPOSITORY"),
|
|
211
|
+
"ref": os.environ.get("GITHUB_REF"),
|
|
212
|
+
"sha": os.environ.get("GITHUB_SHA"),
|
|
213
|
+
"actor": os.environ.get("GITHUB_ACTOR"),
|
|
214
|
+
"workflow": os.environ.get("GITHUB_WORKFLOW"),
|
|
215
|
+
"run_id": os.environ.get("GITHUB_RUN_ID"),
|
|
216
|
+
"run_number": os.environ.get("GITHUB_RUN_NUMBER"),
|
|
217
|
+
"event_name": os.environ.get("GITHUB_EVENT_NAME"),
|
|
218
|
+
"job": os.environ.get("GITHUB_JOB"),
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# =============================================================================
|
|
223
|
+
# GitLab CI OIDC Provider
|
|
224
|
+
# =============================================================================
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@dataclass
|
|
228
|
+
class GitLabCIConfig:
|
|
229
|
+
"""Configuration for GitLab CI OIDC provider.
|
|
230
|
+
|
|
231
|
+
Attributes:
|
|
232
|
+
audience: Token audience.
|
|
233
|
+
request_timeout: HTTP request timeout in seconds.
|
|
234
|
+
enable_cache: Whether to cache tokens.
|
|
235
|
+
cache_ttl_seconds: Token cache TTL.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
audience: str | None = None
|
|
239
|
+
request_timeout: float = 30.0
|
|
240
|
+
enable_cache: bool = True
|
|
241
|
+
cache_ttl_seconds: int = 300
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class GitLabCIOIDCProvider(BaseOIDCProvider):
|
|
245
|
+
"""GitLab CI OIDC token provider.
|
|
246
|
+
|
|
247
|
+
Retrieves OIDC tokens from GitLab CI using the CI_JOB_JWT_V2
|
|
248
|
+
environment variable (or CI_JOB_JWT for older versions).
|
|
249
|
+
|
|
250
|
+
Note: Must be enabled in CI/CD settings and job must use:
|
|
251
|
+
|
|
252
|
+
```yaml
|
|
253
|
+
job:
|
|
254
|
+
id_tokens:
|
|
255
|
+
GITLAB_OIDC_TOKEN:
|
|
256
|
+
aud: https://your-audience.example.com
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Token Claims (GitLab-specific):
|
|
260
|
+
- iss: https://gitlab.com (or self-hosted URL)
|
|
261
|
+
- sub: project_path:{group}/{project}:ref_type:{type}:ref:{ref}
|
|
262
|
+
- aud: configured audience
|
|
263
|
+
- project_path: group/project
|
|
264
|
+
- ref: branch/tag name
|
|
265
|
+
- ref_path: refs/heads/main or refs/tags/v1.0
|
|
266
|
+
- pipeline_id: pipeline ID
|
|
267
|
+
- pipeline_source: trigger source (push, schedule, etc.)
|
|
268
|
+
- user_id: triggering user ID
|
|
269
|
+
- user_login: triggering user login
|
|
270
|
+
- namespace_path: group path
|
|
271
|
+
- environment: deployment environment (if any)
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
# Environment variables
|
|
275
|
+
JWT_V2_VAR = "CI_JOB_JWT_V2"
|
|
276
|
+
JWT_V1_VAR = "CI_JOB_JWT"
|
|
277
|
+
GITLAB_CI_VAR = "GITLAB_CI"
|
|
278
|
+
OIDC_TOKEN_VAR = "GITLAB_OIDC_TOKEN"
|
|
279
|
+
|
|
280
|
+
def __init__(
|
|
281
|
+
self,
|
|
282
|
+
audience: str | None = None,
|
|
283
|
+
*,
|
|
284
|
+
token_variable: str | None = None,
|
|
285
|
+
config: GitLabCIConfig | None = None,
|
|
286
|
+
**kwargs: Any,
|
|
287
|
+
) -> None:
|
|
288
|
+
"""Initialize GitLab CI OIDC provider.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
audience: Default token audience.
|
|
292
|
+
token_variable: Custom environment variable for the token.
|
|
293
|
+
config: Full configuration object.
|
|
294
|
+
**kwargs: Additional base class arguments.
|
|
295
|
+
"""
|
|
296
|
+
self._config = config or GitLabCIConfig(audience=audience)
|
|
297
|
+
if audience:
|
|
298
|
+
self._config.audience = audience
|
|
299
|
+
self._token_variable = token_variable
|
|
300
|
+
|
|
301
|
+
super().__init__(
|
|
302
|
+
cache_ttl_seconds=self._config.cache_ttl_seconds,
|
|
303
|
+
enable_cache=self._config.enable_cache,
|
|
304
|
+
**kwargs,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def name(self) -> str:
|
|
309
|
+
return "gitlab_ci"
|
|
310
|
+
|
|
311
|
+
def is_available(self) -> bool:
|
|
312
|
+
"""Check if running in GitLab CI with OIDC support."""
|
|
313
|
+
if os.environ.get(self.GITLAB_CI_VAR) != "true":
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
# Check for any of the token sources
|
|
317
|
+
if self._token_variable and os.environ.get(self._token_variable):
|
|
318
|
+
return True
|
|
319
|
+
if os.environ.get(self.OIDC_TOKEN_VAR):
|
|
320
|
+
return True
|
|
321
|
+
if os.environ.get(self.JWT_V2_VAR):
|
|
322
|
+
return True
|
|
323
|
+
if os.environ.get(self.JWT_V1_VAR):
|
|
324
|
+
return True
|
|
325
|
+
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
def _fetch_token(self, audience: str | None = None) -> str:
|
|
329
|
+
"""Fetch OIDC token from GitLab CI environment.
|
|
330
|
+
|
|
331
|
+
GitLab provides the token directly in environment variables,
|
|
332
|
+
no HTTP request needed.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
audience: Token audience (not used, set in CI config).
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Raw JWT token string.
|
|
339
|
+
"""
|
|
340
|
+
# Priority order for token sources
|
|
341
|
+
token_sources = [
|
|
342
|
+
self._token_variable,
|
|
343
|
+
self.OIDC_TOKEN_VAR,
|
|
344
|
+
self.JWT_V2_VAR,
|
|
345
|
+
self.JWT_V1_VAR,
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
for source in token_sources:
|
|
349
|
+
if source and (token := os.environ.get(source)):
|
|
350
|
+
return token
|
|
351
|
+
|
|
352
|
+
raise OIDCProviderNotAvailableError(
|
|
353
|
+
self.name,
|
|
354
|
+
"No OIDC token found in environment. "
|
|
355
|
+
"Ensure id_tokens is configured in your .gitlab-ci.yml",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
def get_environment_info(self) -> dict[str, str | None]:
|
|
359
|
+
"""Get GitLab CI environment information."""
|
|
360
|
+
return {
|
|
361
|
+
"project_path": os.environ.get("CI_PROJECT_PATH"),
|
|
362
|
+
"project_id": os.environ.get("CI_PROJECT_ID"),
|
|
363
|
+
"ref": os.environ.get("CI_COMMIT_REF_NAME"),
|
|
364
|
+
"sha": os.environ.get("CI_COMMIT_SHA"),
|
|
365
|
+
"pipeline_id": os.environ.get("CI_PIPELINE_ID"),
|
|
366
|
+
"pipeline_source": os.environ.get("CI_PIPELINE_SOURCE"),
|
|
367
|
+
"job_id": os.environ.get("CI_JOB_ID"),
|
|
368
|
+
"job_name": os.environ.get("CI_JOB_NAME"),
|
|
369
|
+
"user_login": os.environ.get("GITLAB_USER_LOGIN"),
|
|
370
|
+
"user_id": os.environ.get("GITLAB_USER_ID"),
|
|
371
|
+
"environment": os.environ.get("CI_ENVIRONMENT_NAME"),
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# =============================================================================
|
|
376
|
+
# CircleCI OIDC Provider
|
|
377
|
+
# =============================================================================
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@dataclass
|
|
381
|
+
class CircleCIConfig:
|
|
382
|
+
"""Configuration for CircleCI OIDC provider.
|
|
383
|
+
|
|
384
|
+
Attributes:
|
|
385
|
+
audience: Token audience.
|
|
386
|
+
request_timeout: HTTP request timeout in seconds.
|
|
387
|
+
enable_cache: Whether to cache tokens.
|
|
388
|
+
cache_ttl_seconds: Token cache TTL.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
audience: str | None = None
|
|
392
|
+
request_timeout: float = 30.0
|
|
393
|
+
enable_cache: bool = True
|
|
394
|
+
cache_ttl_seconds: int = 300
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class CircleCIOIDCProvider(BaseOIDCProvider):
|
|
398
|
+
"""CircleCI OIDC token provider.
|
|
399
|
+
|
|
400
|
+
Retrieves OIDC tokens from CircleCI using the CIRCLE_OIDC_TOKEN
|
|
401
|
+
environment variable (v2) or CIRCLE_OIDC_TOKEN_V2 for newer versions.
|
|
402
|
+
|
|
403
|
+
Note: Must have OIDC enabled in project settings. The token is
|
|
404
|
+
automatically injected for jobs with a configured context.
|
|
405
|
+
|
|
406
|
+
Token Claims (CircleCI-specific):
|
|
407
|
+
- iss: https://oidc.circleci.com/org/{org-id}
|
|
408
|
+
- sub: org/{org-id}/project/{project-id}/user/{user-id}
|
|
409
|
+
- aud: configured audience
|
|
410
|
+
- oidc.circleci.com/project-id: project UUID
|
|
411
|
+
- oidc.circleci.com/context-ids: list of context IDs
|
|
412
|
+
- oidc.circleci.com/vcs-origin: VCS origin URL
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
# Environment variables
|
|
416
|
+
OIDC_TOKEN_VAR = "CIRCLE_OIDC_TOKEN"
|
|
417
|
+
OIDC_TOKEN_V2_VAR = "CIRCLE_OIDC_TOKEN_V2"
|
|
418
|
+
CIRCLECI_VAR = "CIRCLECI"
|
|
419
|
+
|
|
420
|
+
def __init__(
|
|
421
|
+
self,
|
|
422
|
+
audience: str | None = None,
|
|
423
|
+
*,
|
|
424
|
+
config: CircleCIConfig | None = None,
|
|
425
|
+
**kwargs: Any,
|
|
426
|
+
) -> None:
|
|
427
|
+
"""Initialize CircleCI OIDC provider.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
audience: Default token audience.
|
|
431
|
+
config: Full configuration object.
|
|
432
|
+
**kwargs: Additional base class arguments.
|
|
433
|
+
"""
|
|
434
|
+
self._config = config or CircleCIConfig(audience=audience)
|
|
435
|
+
if audience:
|
|
436
|
+
self._config.audience = audience
|
|
437
|
+
|
|
438
|
+
super().__init__(
|
|
439
|
+
cache_ttl_seconds=self._config.cache_ttl_seconds,
|
|
440
|
+
enable_cache=self._config.enable_cache,
|
|
441
|
+
**kwargs,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def name(self) -> str:
|
|
446
|
+
return "circleci"
|
|
447
|
+
|
|
448
|
+
def is_available(self) -> bool:
|
|
449
|
+
"""Check if running in CircleCI with OIDC support."""
|
|
450
|
+
if os.environ.get(self.CIRCLECI_VAR) != "true":
|
|
451
|
+
return False
|
|
452
|
+
|
|
453
|
+
return bool(
|
|
454
|
+
os.environ.get(self.OIDC_TOKEN_V2_VAR)
|
|
455
|
+
or os.environ.get(self.OIDC_TOKEN_VAR)
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
def _fetch_token(self, audience: str | None = None) -> str:
|
|
459
|
+
"""Fetch OIDC token from CircleCI environment.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
audience: Token audience (not used, set in project settings).
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Raw JWT token string.
|
|
466
|
+
"""
|
|
467
|
+
token = os.environ.get(self.OIDC_TOKEN_V2_VAR) or os.environ.get(
|
|
468
|
+
self.OIDC_TOKEN_VAR
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
if not token:
|
|
472
|
+
raise OIDCProviderNotAvailableError(
|
|
473
|
+
self.name,
|
|
474
|
+
"CIRCLE_OIDC_TOKEN not found. Ensure OIDC is enabled "
|
|
475
|
+
"in project settings and a context is configured.",
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
return token
|
|
479
|
+
|
|
480
|
+
def get_environment_info(self) -> dict[str, str | None]:
|
|
481
|
+
"""Get CircleCI environment information."""
|
|
482
|
+
return {
|
|
483
|
+
"project_reponame": os.environ.get("CIRCLE_PROJECT_REPONAME"),
|
|
484
|
+
"project_username": os.environ.get("CIRCLE_PROJECT_USERNAME"),
|
|
485
|
+
"branch": os.environ.get("CIRCLE_BRANCH"),
|
|
486
|
+
"sha": os.environ.get("CIRCLE_SHA1"),
|
|
487
|
+
"build_num": os.environ.get("CIRCLE_BUILD_NUM"),
|
|
488
|
+
"job": os.environ.get("CIRCLE_JOB"),
|
|
489
|
+
"workflow_id": os.environ.get("CIRCLE_WORKFLOW_ID"),
|
|
490
|
+
"workflow_job_id": os.environ.get("CIRCLE_WORKFLOW_JOB_ID"),
|
|
491
|
+
"username": os.environ.get("CIRCLE_USERNAME"),
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
# =============================================================================
|
|
496
|
+
# Bitbucket Pipelines OIDC Provider
|
|
497
|
+
# =============================================================================
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
@dataclass
|
|
501
|
+
class BitbucketPipelinesConfig:
|
|
502
|
+
"""Configuration for Bitbucket Pipelines OIDC provider.
|
|
503
|
+
|
|
504
|
+
Attributes:
|
|
505
|
+
audience: Token audience.
|
|
506
|
+
identity_provider: Identity provider name configured in Bitbucket.
|
|
507
|
+
request_timeout: HTTP request timeout in seconds.
|
|
508
|
+
enable_cache: Whether to cache tokens.
|
|
509
|
+
cache_ttl_seconds: Token cache TTL.
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
audience: str | None = None
|
|
513
|
+
identity_provider: str | None = None
|
|
514
|
+
request_timeout: float = 30.0
|
|
515
|
+
enable_cache: bool = True
|
|
516
|
+
cache_ttl_seconds: int = 300
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
class BitbucketPipelinesOIDCProvider(BaseOIDCProvider):
|
|
520
|
+
"""Bitbucket Pipelines OIDC token provider.
|
|
521
|
+
|
|
522
|
+
Retrieves OIDC tokens from Bitbucket Pipelines using the
|
|
523
|
+
bitbucket-request-oidc-token pipe or environment variable.
|
|
524
|
+
|
|
525
|
+
Note: Must configure OIDC identity provider in repository settings.
|
|
526
|
+
|
|
527
|
+
Token Claims (Bitbucket-specific):
|
|
528
|
+
- iss: https://api.bitbucket.org/2.0/workspaces/{workspace}/pipelines-config/identity/oidc
|
|
529
|
+
- sub: {repository-uuid}:{step-uuid}
|
|
530
|
+
- aud: configured audience
|
|
531
|
+
- repository_uuid: repository UUID
|
|
532
|
+
- branch_name: branch name
|
|
533
|
+
- workspace: workspace slug
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
# Environment variables
|
|
537
|
+
OIDC_TOKEN_VAR = "BITBUCKET_STEP_OIDC_TOKEN"
|
|
538
|
+
BITBUCKET_VAR = "BITBUCKET_BUILD_NUMBER"
|
|
539
|
+
|
|
540
|
+
def __init__(
|
|
541
|
+
self,
|
|
542
|
+
audience: str | None = None,
|
|
543
|
+
*,
|
|
544
|
+
identity_provider: str | None = None,
|
|
545
|
+
config: BitbucketPipelinesConfig | None = None,
|
|
546
|
+
**kwargs: Any,
|
|
547
|
+
) -> None:
|
|
548
|
+
"""Initialize Bitbucket Pipelines OIDC provider.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
audience: Default token audience.
|
|
552
|
+
identity_provider: Identity provider name.
|
|
553
|
+
config: Full configuration object.
|
|
554
|
+
**kwargs: Additional base class arguments.
|
|
555
|
+
"""
|
|
556
|
+
self._config = config or BitbucketPipelinesConfig(
|
|
557
|
+
audience=audience,
|
|
558
|
+
identity_provider=identity_provider,
|
|
559
|
+
)
|
|
560
|
+
if audience:
|
|
561
|
+
self._config.audience = audience
|
|
562
|
+
if identity_provider:
|
|
563
|
+
self._config.identity_provider = identity_provider
|
|
564
|
+
|
|
565
|
+
super().__init__(
|
|
566
|
+
cache_ttl_seconds=self._config.cache_ttl_seconds,
|
|
567
|
+
enable_cache=self._config.enable_cache,
|
|
568
|
+
**kwargs,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
@property
|
|
572
|
+
def name(self) -> str:
|
|
573
|
+
return "bitbucket_pipelines"
|
|
574
|
+
|
|
575
|
+
def is_available(self) -> bool:
|
|
576
|
+
"""Check if running in Bitbucket Pipelines with OIDC support."""
|
|
577
|
+
return (
|
|
578
|
+
os.environ.get(self.BITBUCKET_VAR) is not None
|
|
579
|
+
and os.environ.get(self.OIDC_TOKEN_VAR) is not None
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
def _fetch_token(self, audience: str | None = None) -> str:
|
|
583
|
+
"""Fetch OIDC token from Bitbucket Pipelines environment.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
audience: Token audience.
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
Raw JWT token string.
|
|
590
|
+
"""
|
|
591
|
+
token = os.environ.get(self.OIDC_TOKEN_VAR)
|
|
592
|
+
|
|
593
|
+
if not token:
|
|
594
|
+
raise OIDCProviderNotAvailableError(
|
|
595
|
+
self.name,
|
|
596
|
+
f"{self.OIDC_TOKEN_VAR} not found. "
|
|
597
|
+
"Ensure OIDC is configured in repository settings "
|
|
598
|
+
"and bitbucket-request-oidc-token pipe is used.",
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
return token
|
|
602
|
+
|
|
603
|
+
def get_environment_info(self) -> dict[str, str | None]:
|
|
604
|
+
"""Get Bitbucket Pipelines environment information."""
|
|
605
|
+
return {
|
|
606
|
+
"repo_slug": os.environ.get("BITBUCKET_REPO_SLUG"),
|
|
607
|
+
"repo_uuid": os.environ.get("BITBUCKET_REPO_UUID"),
|
|
608
|
+
"workspace": os.environ.get("BITBUCKET_WORKSPACE"),
|
|
609
|
+
"branch": os.environ.get("BITBUCKET_BRANCH"),
|
|
610
|
+
"commit": os.environ.get("BITBUCKET_COMMIT"),
|
|
611
|
+
"build_number": os.environ.get("BITBUCKET_BUILD_NUMBER"),
|
|
612
|
+
"pipeline_uuid": os.environ.get("BITBUCKET_PIPELINE_UUID"),
|
|
613
|
+
"step_uuid": os.environ.get("BITBUCKET_STEP_UUID"),
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
# =============================================================================
|
|
618
|
+
# Generic OIDC Provider
|
|
619
|
+
# =============================================================================
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
@dataclass
|
|
623
|
+
class GenericOIDCConfig:
|
|
624
|
+
"""Configuration for generic OIDC provider.
|
|
625
|
+
|
|
626
|
+
Attributes:
|
|
627
|
+
name: Provider name.
|
|
628
|
+
token_url: URL to fetch token from.
|
|
629
|
+
token_header_name: Authorization header name.
|
|
630
|
+
token_header_value_env: Env var containing header value.
|
|
631
|
+
token_response_field: JSON field containing the token.
|
|
632
|
+
audience_param: Query parameter name for audience.
|
|
633
|
+
request_timeout: HTTP request timeout.
|
|
634
|
+
enable_cache: Whether to cache tokens.
|
|
635
|
+
cache_ttl_seconds: Token cache TTL.
|
|
636
|
+
"""
|
|
637
|
+
|
|
638
|
+
name: str = "generic"
|
|
639
|
+
token_url_env: str = ""
|
|
640
|
+
token_header_name: str = "Authorization"
|
|
641
|
+
token_header_value_env: str = ""
|
|
642
|
+
token_response_field: str = "token"
|
|
643
|
+
audience_param: str = "audience"
|
|
644
|
+
request_timeout: float = 30.0
|
|
645
|
+
enable_cache: bool = True
|
|
646
|
+
cache_ttl_seconds: int = 300
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
class GenericOIDCProvider(BaseOIDCProvider):
|
|
650
|
+
"""Generic OIDC token provider for custom CI platforms.
|
|
651
|
+
|
|
652
|
+
Allows configuration of a custom OIDC token endpoint for CI platforms
|
|
653
|
+
not directly supported.
|
|
654
|
+
|
|
655
|
+
Example:
|
|
656
|
+
>>> provider = GenericOIDCProvider(
|
|
657
|
+
... config=GenericOIDCConfig(
|
|
658
|
+
... name="my-ci",
|
|
659
|
+
... token_url_env="MY_CI_TOKEN_URL",
|
|
660
|
+
... token_header_value_env="MY_CI_TOKEN",
|
|
661
|
+
... token_response_field="id_token",
|
|
662
|
+
... ),
|
|
663
|
+
... )
|
|
664
|
+
"""
|
|
665
|
+
|
|
666
|
+
def __init__(
|
|
667
|
+
self,
|
|
668
|
+
config: GenericOIDCConfig,
|
|
669
|
+
**kwargs: Any,
|
|
670
|
+
) -> None:
|
|
671
|
+
"""Initialize generic OIDC provider.
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
config: Provider configuration.
|
|
675
|
+
**kwargs: Additional base class arguments.
|
|
676
|
+
"""
|
|
677
|
+
self._config = config
|
|
678
|
+
|
|
679
|
+
super().__init__(
|
|
680
|
+
cache_ttl_seconds=self._config.cache_ttl_seconds,
|
|
681
|
+
enable_cache=self._config.enable_cache,
|
|
682
|
+
**kwargs,
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
@property
|
|
686
|
+
def name(self) -> str:
|
|
687
|
+
return self._config.name
|
|
688
|
+
|
|
689
|
+
def is_available(self) -> bool:
|
|
690
|
+
"""Check if the configured environment variables are present."""
|
|
691
|
+
if self._config.token_url_env:
|
|
692
|
+
return os.environ.get(self._config.token_url_env) is not None
|
|
693
|
+
return False
|
|
694
|
+
|
|
695
|
+
def _fetch_token(self, audience: str | None = None) -> str:
|
|
696
|
+
"""Fetch OIDC token from configured endpoint.
|
|
697
|
+
|
|
698
|
+
Args:
|
|
699
|
+
audience: Token audience.
|
|
700
|
+
|
|
701
|
+
Returns:
|
|
702
|
+
Raw JWT token string.
|
|
703
|
+
"""
|
|
704
|
+
import urllib.request
|
|
705
|
+
import urllib.error
|
|
706
|
+
import urllib.parse
|
|
707
|
+
|
|
708
|
+
token_url = os.environ.get(self._config.token_url_env, "")
|
|
709
|
+
if not token_url:
|
|
710
|
+
raise OIDCProviderNotAvailableError(
|
|
711
|
+
self.name,
|
|
712
|
+
f"Missing {self._config.token_url_env}",
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
# Add audience parameter if provided
|
|
716
|
+
if audience and self._config.audience_param:
|
|
717
|
+
parsed = urllib.parse.urlparse(token_url)
|
|
718
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
719
|
+
query[self._config.audience_param] = [audience]
|
|
720
|
+
new_query = urllib.parse.urlencode(query, doseq=True)
|
|
721
|
+
token_url = urllib.parse.urlunparse(
|
|
722
|
+
parsed._replace(query=new_query)
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
# Build headers
|
|
726
|
+
headers = {"Accept": "application/json"}
|
|
727
|
+
if self._config.token_header_value_env:
|
|
728
|
+
header_value = os.environ.get(self._config.token_header_value_env, "")
|
|
729
|
+
if header_value:
|
|
730
|
+
headers[self._config.token_header_name] = header_value
|
|
731
|
+
|
|
732
|
+
request = urllib.request.Request(token_url, headers=headers)
|
|
733
|
+
|
|
734
|
+
try:
|
|
735
|
+
with urllib.request.urlopen(
|
|
736
|
+
request, timeout=self._config.request_timeout
|
|
737
|
+
) as response:
|
|
738
|
+
data = json.loads(response.read())
|
|
739
|
+
return data[self._config.token_response_field]
|
|
740
|
+
|
|
741
|
+
except urllib.error.HTTPError as e:
|
|
742
|
+
raise OIDCTokenError(
|
|
743
|
+
f"HTTP {e.code}: {e.reason}",
|
|
744
|
+
provider=self.name,
|
|
745
|
+
) from e
|
|
746
|
+
except KeyError:
|
|
747
|
+
raise OIDCTokenError(
|
|
748
|
+
f"Response missing '{self._config.token_response_field}' field",
|
|
749
|
+
provider=self.name,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
# =============================================================================
|
|
754
|
+
# Detection Functions
|
|
755
|
+
# =============================================================================
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def detect_ci_oidc_provider() -> BaseOIDCProvider | None:
|
|
759
|
+
"""Detect and return the OIDC provider for the current CI environment.
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
Appropriate OIDC provider instance, or None if not in a supported CI.
|
|
763
|
+
|
|
764
|
+
Example:
|
|
765
|
+
>>> provider = detect_ci_oidc_provider()
|
|
766
|
+
>>> if provider:
|
|
767
|
+
... token = provider.get_token(audience="sts.amazonaws.com")
|
|
768
|
+
"""
|
|
769
|
+
# Try each provider in order of likelihood
|
|
770
|
+
providers: list[type[BaseOIDCProvider]] = [
|
|
771
|
+
GitHubActionsOIDCProvider,
|
|
772
|
+
GitLabCIOIDCProvider,
|
|
773
|
+
CircleCIOIDCProvider,
|
|
774
|
+
BitbucketPipelinesOIDCProvider,
|
|
775
|
+
]
|
|
776
|
+
|
|
777
|
+
for provider_cls in providers:
|
|
778
|
+
try:
|
|
779
|
+
provider = provider_cls()
|
|
780
|
+
if provider.is_available():
|
|
781
|
+
logger.info(f"Detected OIDC provider: {provider.name}")
|
|
782
|
+
return provider
|
|
783
|
+
except Exception as e:
|
|
784
|
+
logger.debug(f"Provider {provider_cls.__name__} check failed: {e}")
|
|
785
|
+
|
|
786
|
+
logger.debug("No OIDC provider detected in current environment")
|
|
787
|
+
return None
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def is_oidc_available() -> bool:
|
|
791
|
+
"""Check if OIDC authentication is available in the current environment.
|
|
792
|
+
|
|
793
|
+
Returns:
|
|
794
|
+
True if an OIDC provider is available.
|
|
795
|
+
|
|
796
|
+
Example:
|
|
797
|
+
>>> if is_oidc_available():
|
|
798
|
+
... # Use OIDC authentication
|
|
799
|
+
... creds = get_oidc_credentials()
|
|
800
|
+
... else:
|
|
801
|
+
... # Fall back to traditional credentials
|
|
802
|
+
... creds = get_static_credentials()
|
|
803
|
+
"""
|
|
804
|
+
provider = detect_ci_oidc_provider()
|
|
805
|
+
return provider is not None
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def get_ci_provider_type() -> CIProvider:
|
|
809
|
+
"""Detect the CI provider type from environment.
|
|
810
|
+
|
|
811
|
+
Returns:
|
|
812
|
+
CIProvider enum value.
|
|
813
|
+
"""
|
|
814
|
+
if os.environ.get("GITHUB_ACTIONS") == "true":
|
|
815
|
+
return CIProvider.GITHUB_ACTIONS
|
|
816
|
+
if os.environ.get("GITLAB_CI") == "true":
|
|
817
|
+
return CIProvider.GITLAB_CI
|
|
818
|
+
if os.environ.get("CIRCLECI") == "true":
|
|
819
|
+
return CIProvider.CIRCLECI
|
|
820
|
+
if os.environ.get("BITBUCKET_BUILD_NUMBER"):
|
|
821
|
+
return CIProvider.BITBUCKET
|
|
822
|
+
if os.environ.get("JENKINS_URL"):
|
|
823
|
+
return CIProvider.JENKINS
|
|
824
|
+
|
|
825
|
+
return CIProvider.UNKNOWN
|