Typed configuration & secrets facade for AmpyFin (layering, validation, secrets, control-plane)
Project description
๐ ampy-config
Typed Configuration & Secrets Faรงade for AmpyFin
๐ฏ Single, safe source of truth for configuration and secrets across AmpyFin services
๐ Built to integrate with ampy-bus (control plane over NATS/JetStream) and ampy-proto (payload contracts)
๐ Documentation โข ๐ Quick Start โข ๐ง Usage โข ๐ค Contributing
๐ Table of Contents
- ๐ฏ Why this exists
- โจ Highlights
- ๐ Quick Start
- ๐ Install
- ๐ฏ Basic Usage Examples
- ๐ Integration Examples
- โ Configuration Validation
- ๐ฎ Control Plane
- ๐ Layering Model
- ๐ Secrets
- ๐ป CLI Usage
- ๐ Python Integration
- ๐น Go Client Usage
- ๐ Schema Examples
- ๐ Environment Variables
- ๐ง Troubleshooting
- ๐ก๏ธ Security
- ๐ค Contributing
๐ฏ Why this exists (the problem)
Without a unified configuration layer, distributed trading systems tend to develop:
โ ๏ธ Common Issues:
- ENV/YAML sprawl โ drift, surprises, outages
- Secret handling risks โ credentials in logs, brittle rotations, no redaction
- Non-reproducibility โ can't reconstruct exactly which parameters were live for a given trade/run
- Inconsistent runtime behavior โ some services reload, others require restarts
ampy-config provides a single, typed, validated, observable configuration view with clean secret indirection and a runtime control plane for safe updates.
โจ Highlights (what you get)
| Feature | Description |
|---|---|
| ๐ Typed schema + validation | JSON Schema + semantic cross-field checks |
| ๐ Layering & precedence | defaults โ environment profile โ overlays โ ENV allowlist โ runtime overrides |
| ๐ Secret indirection | secret://โฆ, aws-sm://โฆ, gcp-sm://โฆ with caching, rotation, and universal redaction |
| ๐ฎ Control plane for updates | config_preview โ config_apply โ config_applied events on NATS (JetStream) |
| ๐ Auditability & observability | provenance for each key; logs/metrics/traces (no secrets) |
| ๐ Language-agnostic | produces plain YAML effective config for Python, Go, C++, etc. |
๐ Quick Start
โ ๏ธ Prerequisites - NATS Server Required
ampy-config requires a running NATS server for configuration management:
# Start NATS with JetStream (required)
docker run --rm -d --name nats -p 4222:4222 nats:2.10 -js
# Or install NATS server locally
go install github.com/nats-io/nats-server/v2@latest
nats-server -js
Verify NATS is running:
# Test connection
nats --server "nats://localhost:4222" server info
๐จ Common Issue: If you get
nats: no servers available for connectionerror, NATS server is not running. Start it with the command above.
๐ Install
๐ Python / PyPI
pip install ampy-config
Developer mode (local repo):
pip install -e .
๐น Go Client
Library:
go get github.com/AmpyFin/ampy-config/go/ampyconfig@v1.1.5
Binaries:
cd go/ampyconfig
make # builds bin/ampyconfig-{ops,agent,listener}
๐ฆ Available on pkg.go.dev
๐ง Optional secret backends
| Backend | Install Command | Use Case |
|---|---|---|
| ๐ HashiCorp Vault | pip install hvac |
Enterprise secret management |
| โ๏ธ AWS Secrets Manager | pip install boto3 |
AWS-native secret storage |
| ๐ GCP Secret Manager | pip install google-cloud-secret-manager |
Google Cloud secret storage |
๐ก Tip: You do not need to sign up for all of these. Choose one or more real backends for your deployment; the library gracefully falls back to a local JSON file in development.
๐ฏ Basic Usage Examples
๐ Python Quick Start
import asyncio
from ampy_config.layering import build_effective_config
# Build effective configuration
cfg, _ = build_effective_config(
schema_path="schema/ampy-config.schema.json",
defaults_path="config/defaults.yaml",
profile_yaml="examples/dev.yaml",
overlays=[],
service_overrides=[],
env_allowlist_path="env_allowlist.txt",
env_file=None,
runtime_overrides_path="runtime/overrides.yaml",
)
# Get configuration values
nats_url = cfg["bus"]["nats_url"]
topic_prefix = cfg["bus"]["topic_prefix"]
risk_limit = cfg["oms"]["risk"]["max_order_notional_usd"]
print(f"NATS URL: {nats_url}")
print(f"Topic Prefix: {topic_prefix}")
print(f"Risk Limit: {risk_limit}")
๐น Go Quick Start
package main
import (
"fmt"
"log"
"github.com/AmpyFin/ampy-config/go/ampyconfig"
"github.com/nats-io/nats.go"
)
func main() {
// Connect to NATS
nc, err := nats.Connect("nats://localhost:4222")
if err != nil {
log.Fatal(err)
}
defer nc.Close()
// Create ampy-config client
client := ampyconfig.New("nats://localhost:4222", "ampy.dev", "runtime/overrides.yaml")
// Get configuration values
natsURL, err := client.Get("bus.nats_url")
if err != nil {
log.Fatal(err)
}
topicPrefix, err := client.Get("bus.topic_prefix")
if err != nil {
log.Fatal(err)
}
fmt.Printf("NATS URL: %s\n", natsURL)
fmt.Printf("Topic Prefix: %s\n", topicPrefix)
}
๐งช Test Your Setup
Python validation test:
python -c "
from ampy_config.layering import build_effective_config
try:
cfg, _ = build_effective_config(
'schema/ampy-config.schema.json',
'config/defaults.yaml',
'examples/dev.yaml',
[], [], 'env_allowlist.txt', None, 'runtime/overrides.yaml'
)
print('โ
ampy-config working correctly!')
print(f'Bus config: {cfg[\"bus\"]}')
except Exception as e:
print(f'โ Error: {e}')
"
Go validation test:
go run - << 'EOF'
package main
import (
"fmt"
"log"
"github.com/AmpyFin/ampy-config/go/ampyconfig"
)
func main() {
client := ampyconfig.New("nats://localhost:4222", "ampy.dev", "runtime/overrides.yaml")
// Test getting a value
value, err := client.Get("test.key")
if err != nil {
log.Printf("Expected error for missing key: %v", err)
}
fmt.Println("โ
ampy-config working correctly!")
}
EOF
๐ Integration Examples
With ampy-bus
Python integration:
import asyncio
from ampy_config.layering import build_effective_config
from ampy_config.bus.ampy_bus import AmpyBus
async def create_bus_from_config():
# Build configuration
cfg, _ = build_effective_config(
schema_path="schema/ampy-config.schema.json",
defaults_path="config/defaults.yaml",
profile_yaml="examples/dev.yaml",
overlays=[], service_overrides=[],
env_allowlist_path="env_allowlist.txt",
env_file=None,
runtime_overrides_path="runtime/overrides.yaml",
)
# Get bus configuration
nats_url = cfg["bus"]["nats_url"]
stream_name = cfg["bus"]["stream_name"]
topic_prefix = cfg["bus"]["topic_prefix"]
# Create bus
bus = AmpyBus(nats_url)
await bus.connect()
return bus, topic_prefix
Go integration:
import (
"github.com/AmpyFin/ampy-config/go/ampyconfig"
"github.com/AmpyFin/ampy-bus/pkg/ampybus/natsbinding"
)
func createBusFromConfig() (*natsbinding.Bus, error) {
// Create config client
client := ampyconfig.New("nats://localhost:4222", "ampy.dev", "runtime/overrides.yaml")
// Get configuration values
natsURL, err := client.Get("bus.nats_url")
if err != nil {
return nil, err
}
streamName, err := client.Get("bus.stream_name")
if err != nil {
return nil, err
}
topicPrefix, err := client.Get("bus.topic_prefix")
if err != nil {
return nil, err
}
// Create bus configuration
config := natsbinding.Config{
URLs: []string{natsURL},
StreamName: streamName,
Subjects: []string{topicPrefix + ".>"},
DurablePrefix: "ampy-trading",
}
return natsbinding.NewBus(config)
}
With ampy-proto
Go integration:
import (
"github.com/AmpyFin/ampy-config/go/ampyconfig"
bars "github.com/AmpyFin/ampy-proto/v2/gen/go/ampy/bars/v1"
common "github.com/AmpyFin/ampy-proto/v2/gen/go/ampy/common/v1"
)
func createBarFromConfig(client *ampyconfig.Client) (*bars.Bar, error) {
// Get trading configuration
symbol, err := client.Get("trading.symbol")
if err != nil {
return nil, err
}
mic, err := client.Get("trading.mic")
if err != nil {
return nil, err
}
// Create bar with config values
bar := &bars.Bar{
Security: &common.SecurityId{
Symbol: symbol,
Mic: mic,
},
// ... other fields
}
return bar, nil
}
Dynamic Configuration Updates
Python:
async def setup_config_updates(bus, topic_prefix):
async def on_config_apply(subject, data):
print(f"Config applied: {data}")
# Reload your application configuration
reload_config()
async def on_config_preview(subject, data):
print(f"Config preview: {data}")
# Subscribe to configuration events
await bus.subscribe_json(f"{topic_prefix}.control.v1.config_apply", on_config_apply)
await bus.subscribe_json(f"{topic_prefix}.control.v1.config_preview", on_config_preview)
def reload_config():
# Reload your application configuration
print("Reloading configuration...")
Go:
func setupConfigUpdates(client *ampyconfig.Client) error {
// Subscribe to configuration changes
err := client.Subscribe(func(event ampyconfig.ConfigEvent) {
switch event.Type {
case "config_preview":
fmt.Printf("Config preview: %s\n", event.Data)
case "config_apply":
fmt.Printf("Config applied: %s\n", event.Data)
// Reload configuration
reloadConfig()
}
})
return err
}
func reloadConfig() {
// Reload your application configuration
fmt.Println("Reloading configuration...")
}
โ Configuration Validation
Required Configuration Keys
Python validation:
def validate_config(cfg):
"""Validate that all required configuration keys are present"""
required_keys = [
"bus.nats_url",
"bus.stream_name",
"bus.topic_prefix",
"oms.risk.max_order_notional_usd",
"trading.symbol",
"trading.mic",
]
missing_keys = []
for key_path in required_keys:
keys = key_path.split('.')
current = cfg
try:
for key in keys:
current = current[key]
except (KeyError, TypeError):
missing_keys.append(key_path)
if missing_keys:
raise ValueError(f"Missing required config keys: {missing_keys}")
return True
# Usage
try:
cfg, _ = build_effective_config(...)
validate_config(cfg)
print("โ
Configuration validation passed")
except ValueError as e:
print(f"โ Configuration validation failed: {e}")
Go validation:
func validateConfig(client *ampyconfig.Client) error {
requiredKeys := []string{
"bus.nats_url",
"bus.stream_name",
"bus.topic_prefix",
"trading.symbol",
"trading.mic",
}
for _, key := range requiredKeys {
if _, err := client.Get(key); err != nil {
return fmt.Errorf("missing required config key: %s", key)
}
}
return nil
}
// Usage
if err := validateConfig(client); err != nil {
log.Fatalf("Configuration validation failed: %v", err)
}
Schema Validation
Validate configuration files:
# Validate single file
python tools/validate.py examples/dev.yaml
# Validate multiple files
python tools/validate.py examples/*.yaml
# Validate with explicit schema
python tools/validate.py --schema schema/ampy-config.schema.json examples/dev.yaml
Programmatic validation:
from ampy_config.layering import build_effective_config
try:
cfg, provenance = build_effective_config(
schema_path="schema/ampy-config.schema.json", # Enables schema validation
defaults_path="config/defaults.yaml",
profile_yaml="examples/dev.yaml",
overlays=[],
service_overrides=[],
env_allowlist_path="env_allowlist.txt",
env_file=None,
runtime_overrides_path="runtime/overrides.yaml",
)
print("โ
Schema validation passed")
except Exception as e:
print(f"โ Schema validation failed: {e}")
Configuration File Examples
defaults.yaml:
# Default configuration values
bus:
nats_url: "nats://localhost:4222"
stream_name: "AMPY_TRADING"
topic_prefix: "ampy.dev"
durable_prefix: "ampy-trading"
trading:
symbol: "AAPL"
mic: "XNAS"
risk_limit: 10000
position_limit: 1000
signals:
ma_short_period: 10
ma_long_period: 20
threshold: 0.5
logging:
level: "info"
format: "json"
development.yaml:
# Development overrides
bus:
nats_url: "nats://localhost:4222"
topic_prefix: "ampy.dev"
trading:
symbol: "AAPL"
risk_limit: 1000 # Lower limit for dev
logging:
level: "debug"
format: "text"
production.yaml:
# Production overrides
bus:
nats_url: "nats://prod-nats:4222"
topic_prefix: "ampy.prod"
trading:
risk_limit: 100000
position_limit: 10000
logging:
level: "warn"
format: "json"
runtime/overrides.yaml:
# Runtime dynamic overrides (can be updated via NATS)
trading:
risk_limit: 5000 # Updated during runtime
signals:
threshold: 0.7 # Updated during runtime
๐ฎ Control plane (NATS/JetStream)
Start a local NATS with JetStream:
docker run --rm -d --name nats -p 4222:4222 nats:2.10 -js
export NATS_URL="nats://127.0.0.1:4222"
Provision the stream and durable consumers (once). Using the nats CLI:
# Stream to cover all control-plane subjects
nats --server "$NATS_URL" stream add ampy-control \
--subjects "ampy.*.control.v1.*" \
--retention limits --max-age 24h --storage file \
--max-msgs 10000 --max-bytes 100MB --discard old --defaults
# Agent durables (pull + explicit ack)
nats --server "$NATS_URL" consumer add ampy-control ampy-config-agent-ampy-dev-control-v1-config-preview \
--filter "ampy.dev.control.v1.config_preview" --pull --deliver all --ack explicit --defaults
nats --server "$NATS_URL" consumer add ampy-control ampy-config-agent-ampy-dev-control-v1-config-apply \
--filter "ampy.dev.control.v1.config_apply" --pull --deliver all --ack explicit --defaults
nats --server "$NATS_URL" consumer add ampy-control ampy-config-agent-ampy-dev-control-v1-secret-rotated \
--filter "ampy.dev.control.v1.secret_rotated" --pull --deliver all --ack explicit --defaults
Verify setup:
nats --server "$NATS_URL" stream ls
nats --server "$NATS_URL" consumer ls ampy-control
โก Note: The library can also auto-provision if permitted, but explicit creation is more predictable for local dev and CI.
๐ Layering model
Effective config = merge in this order (later overrides earlier):
graph TD
A[๐ Defaults<br/>config/defaults.yaml] --> B[๐ Environment Profile<br/>examples/dev.yaml]
B --> C[๐ Overlays<br/>--overlay path]
C --> D[๐ง ENV Allowlist<br/>env_allowlist.txt]
D --> E[โก Runtime Overrides<br/>runtime/overrides.yaml]
E --> F[โ
Final Config]
| Layer | Description | Example |
|---|---|---|
| 1๏ธโฃ Defaults | Checked-in base config | config/defaults.yaml |
| 2๏ธโฃ Environment profile | Environment-specific settings | examples/dev.yaml, examples/paper.yaml, examples/prod.yaml |
| 3๏ธโฃ Overlays | Region/cluster/service YAMLs | --overlay path (repeatable) |
| 4๏ธโฃ ENV allowlist | Environment variable mapping | env_allowlist.txt maps allowed env keys |
| 5๏ธโฃ Runtime overrides | Live configuration updates | runtime/overrides.yaml (written by agent) |
Each key tracks provenance: where it came from (defaults/profile/overlay/ENV/runtime).
๐ Units & types
| Type | Format | Examples |
|---|---|---|
| โฑ๏ธ Durations | String format | 150ms, 2s, 5m, 1h |
| ๐ Sizes | String format | 128KiB, 1MiB |
| ๐ท๏ธ Domains | Explicit prefixes | oms.*, ingest.*, broker.*, ml.*, warehouse.*, fx.*, metrics, logging, tracing, security.*, feature_flags.* |
๐ Secrets (indirection, caching, rotation, redaction)
Use references, not literal values:
| Backend | Format | Example |
|---|---|---|
| ๐ Vault | secret://vault/<path>#<key> |
secret://vault/tiingo#token |
| โ๏ธ AWS SM | aws-sm://<name>?versionStage=AWSCURRENT |
aws-sm://ALPACA_SECRET?versionStage=AWSCURRENT |
| ๐ GCP SM | gcp-sm://projects/<project>/secrets/<name>/versions/latest |
gcp-sm://projects/demo/secrets/AMPY_API/versions/latest |
Local development fallback file (.secrets.local.json):
{
"secret://vault/tiingo#token": "TIINGO_LOCAL_DEV_TOKEN",
"aws-sm://ALPACA_SECRET?versionStage=AWSCURRENT": "ALPACA_LOCAL_DEV_SECRET",
"gcp-sm://projects/demo/secrets/AMPY_API/versions/latest": "AMPY_LOCAL_DEV_API"
}
๐ Security: Secrets are always redacted in logs/metrics/traces; rotation is signaled via
secret_rotatedevents.
๐ป CLI usage
All commands are available via python -m ampy_config.cli โฆ (works without global entrypoints).
๐จ Render effective config
python -m ampy_config.cli render \
--profile dev \
--resolve-secrets redacted \
--provenance
Write it to a file:
python -m ampy_config.cli render \
--profile dev \
--resolve-secrets redacted \
--output /tmp/effective.yaml
Resolve values (dev only; requires .secrets.local.json or configured backends):
AMPY_CONFIG_LOCAL_SECRETS=.secrets.local.json \
python -m ampy_config.cli render --profile dev --resolve-secrets values
โ Validate (schema + semantic checks)
python tools/validate.py examples/dev.yaml
# Or explicitly:
python tools/validate.py --schema schema/ampy-config.schema.json examples/*.yaml
๐ง Secrets utilities
# Resolve (redacted by default)
python -m ampy_config.cli secret get "aws-sm://ALPACA_SECRET?versionStage=AWSCURRENT"
# Print plain (development only)
python -m ampy_config.cli secret get --plain "secret://vault/tiingo#token"
# Invalidate cache entry
python -m ampy_config.cli secret rotate "gcp-sm://projects/demo/secrets/AMPY_API/versions/latest"
๐ค Run the agent
export NATS_URL="nats://127.0.0.1:4222"
export AMPY_CONFIG_SERVICE="ampy-config-agent"
python -m ampy_config.cli agent --profile dev
It subscribes to:
ampy.dev.control.v1.config_preview
ampy.dev.control.v1.config_apply
ampy.dev.control.v1.secret_rotated
โก Ops: preview & apply a runtime override
Create an overlay:
cat >/tmp/overlay.yaml <<'YAML'
oms:
risk:
max_order_notional_usd: 77777
YAML
Preview (validate only):
python -m ampy_config.cli ops preview \
--profile dev \
--overlay-file /tmp/overlay.yaml \
--expires-at "2025-12-31T23:59:59Z" \
--reason "intraday risk tightening" \
--dry-run
Apply (persist) and wait until it's effective in the resolved view:
python -m ampy_config.cli ops apply \
--profile dev \
--overlay-file /tmp/overlay.yaml \
--wait-applied --timeout 20
Then verify:
python -m ampy_config.cli render \
--profile dev \
--runtime runtime/overrides.yaml \
--resolve-secrets redacted \
--provenance
๐ Use from a service (Python example)
# examples/service_skel.py
import asyncio, os
from ampy_config.layering import build_effective_config
from ampy_config.bus.ampy_bus import AmpyBus
from ampy_config.control.events import subjects
async def main():
cfg, _ = build_effective_config(
schema_path="schema/ampy-config.schema.json",
defaults_path="config/defaults.yaml",
profile_yaml="examples/dev.yaml",
overlays=[],
service_overrides=[],
env_allowlist_path="env_allowlist.txt",
env_file=None,
runtime_overrides_path="runtime/overrides.yaml",
)
print("[service] max_order_notional_usd =", cfg["oms"]["risk"]["max_order_notional_usd"])
bus = AmpyBus(os.environ.get("NATS_URL"))
await bus.connect()
subs = subjects(cfg["bus"]["topic_prefix"])
async def on_apply(subject, data):
# Re-build after apply; in real code, youโd update state atomically & validate
new_cfg, _ = build_effective_config(
"schema/ampy-config.schema.json",
"config/defaults.yaml",
"examples/dev.yaml",
[], [], "env_allowlist.txt", None, "runtime/overrides.yaml"
)
print("[service] updated max_order_notional_usd =", new_cfg["oms"]["risk"]["max_order_notional_usd"])
await bus.subscribe_json(subs["apply"], on_apply)
while True:
await asyncio.sleep(1)
if __name__ == "__main__":
os.environ.setdefault("AMPY_CONFIG_SERVICE", "ampy-service-demo")
os.environ.setdefault("NATS_URL", "nats://127.0.0.1:4222")
asyncio.run(main())
๐น Go Client Usage
๐ API Reference
Core Types:
// Client structure
type Client struct {
// ... internal fields
}
// Configuration event
type ConfigEvent struct {
Type string
Data string
}
// Configuration methods
func New(natsURL, topicPrefix, configPath string) *Client
func (c *Client) Get(key string) (string, error)
func (c *Client) GetAll() (map[string]interface{}, error)
func (c *Client) Subscribe(handler func(ConfigEvent)) error
func (c *Client) Close() error
Key Methods:
// Create client
func New(natsURL, topicPrefix, configPath string) *Client
// Get configuration value
func (c *Client) Get(key string) (string, error)
// Get all configuration
func (c *Client) GetAll() (map[string]interface{}, error)
// Subscribe to config events
func (c *Client) Subscribe(handler func(ConfigEvent)) error
// Close client
func (c *Client) Close() error
๐ Basic Usage
Create and use client:
package main
import (
"fmt"
"log"
"github.com/AmpyFin/ampy-config/go/ampyconfig"
)
func main() {
// Create ampy-config client
client := ampyconfig.New("nats://localhost:4222", "ampy.dev", "runtime/overrides.yaml")
defer client.Close()
// Get configuration values
natsURL, err := client.Get("bus.nats_url")
if err != nil {
log.Fatal(err)
}
topicPrefix, err := client.Get("bus.topic_prefix")
if err != nil {
log.Fatal(err)
}
fmt.Printf("NATS URL: %s\n", natsURL)
fmt.Printf("Topic Prefix: %s\n", topicPrefix)
}
Subscribe to configuration changes:
func setupConfigUpdates(client *ampyconfig.Client) error {
// Subscribe to configuration changes
err := client.Subscribe(func(event ampyconfig.ConfigEvent) {
switch event.Type {
case "config_preview":
fmt.Printf("Config preview: %s\n", event.Data)
case "config_apply":
fmt.Printf("Config applied: %s\n", event.Data)
// Reload configuration
reloadConfig()
}
})
return err
}
func reloadConfig() {
// Reload your application configuration
fmt.Println("Reloading configuration...")
}
๐ ๏ธ Command Line Tools
Start the agent:
./bin/ampyconfig-agent \
-nats "$NATS_URL" \
-topic ampy/dev \
-runtime runtime/overrides.yaml \
-service ampy-config-agent \
-log info
Apply configuration changes:
cat >/tmp/overlay.yaml <<'YAML'
oms:
risk:
max_order_notional_usd: 123456
YAML
./bin/ampyconfig-ops \
-nats "$NATS_URL" \
-topic ampy/dev \
-overlay-file /tmp/overlay.yaml \
-wait-applied -timeout 20 \
-runtime runtime/overrides.yaml
Available binaries:
ampyconfig-opsโ publishconfig_preview,config_apply,secret_rotatedampyconfig-agentโ consume control events and persistruntime/overrides.yamlampyconfig-listenerโ example service listener that reacts to changes
๐ Status: v0 thin client โ Python
ampy-configremains the source of truth for schema validation and layering. This Go module focuses on control-plane parity and operational UX.
๐ Go / C++ services
- Parse the effective YAML (rendered by ops at boot or on a schedule)
- Subscribe to the same control-plane subjects and re-load your resolved config (or just read
runtime/overrides.yaml) when aconfig_applyis observed - Keep reloads transactional for safety-critical domains
๐ Schema notes (metrics example)
The schema allows either OTLP (with endpoint) or Prometheus (with port):
"metrics": {
"type": "object",
"additionalProperties": false,
"properties": {
"exporter": { "type": "string", "enum": ["otlp", "prom"] },
"endpoint": { "type": "string" },
"sampling_ratio": { "type": "number", "minimum": 0, "maximum": 1 },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 }
},
"required": ["exporter"]
}
Examples:
# OTLP
metrics:
exporter: otlp
endpoint: https://otel.dev.ampyfin.com:4317
sampling_ratio: 0.25
# Prometheus
metrics:
exporter: prom
port: 9464
๐ Environment variables
| Variable | Description | Example |
|---|---|---|
NATS_URL |
NATS server URL | nats://127.0.0.1:4222 |
AMPY_CONFIG_SERVICE |
Logical service name (used to derive durable names) | ampy-config-agent |
AMPY_CONFIG_RUNTIME_OVERRIDES |
Path for persisted runtime overrides | runtime/overrides.yaml |
AMPY_CONFIG_LOCAL_SECRETS |
Path to local dev secrets JSON | .secrets.local.json |
AMPY_CONFIG_SECRET_TTL_MS |
Secrets cache TTL in milliseconds | 120000 |
AMPY_CONFIG_JS_FALLBACK |
Force direct NATS subscription fallback | 1 (skip JetStream) |
Secret Backend Variables:
- ๐ Vault:
VAULT_ADDR,VAULT_TOKEN(if usingsecret://) - โ๏ธ AWS:
AWS_DEFAULT_REGION+ credentials (if usingaws-sm://) - ๐ GCP:
GOOGLE_APPLICATION_CREDENTIALS(if usinggcp-sm://)
๐ง Troubleshooting
๐จ Common Issues & Solutions
| Issue | Cause | Solution |
|---|---|---|
nats: no servers available for connection |
NATS server is not running | Start NATS: docker run --rm -d --name nats -p 4222:4222 nats:2.10 -js |
config key not found |
Configuration key doesn't exist | Check your YAML files and ensure the key path is correct (e.g., bus.nats_url) |
yaml: unmarshal errors |
Invalid YAML syntax | Validate YAML syntax in your configuration files |
permission denied |
File permission issues | Check file permissions for configuration files and runtime directory |
| Configuration not updating | Not subscribing to config events | Ensure you're subscribing to config_apply events and handling updates |
| ๐ค Agent only shows one subscription | Blocked while initializing a secret backend | Unset or configure that backend properly, or run with only local secrets in dev |
| โฐ No messages consumed / timeouts | NATS_URL points to wrong port, JetStream disabled |
Check NATS_URL, enable JetStream, verify ampy-control stream & consumers exist |
| โ Apply says OK but value didn't change | Agent didn't write runtime/overrides.yaml |
Verify file path via AMPY_CONFIG_RUNTIME_OVERRIDES and service reloads on config_apply |
| โ ๏ธ Schema validation passes but semantic check fails | Semantic checks run after schema validation | Fix the offending values called out in the error |
๐ Debug Mode
Python debug:
# Enable debug logging
import logging
logging.basicConfig(level=logging.DEBUG)
# Add debug logging to your config loading
def debug_config(cfg):
if cfg.get("debug", {}).get("enabled") == True:
print("๐ DEBUG: Configuration loaded successfully")
print(f"๐ DEBUG: All config: {cfg}")
# Usage
cfg, _ = build_effective_config(...)
debug_config(cfg)
Go debug:
// Enable debug logging
client := ampyconfig.New("nats://localhost:4222", "ampy.dev", "runtime/overrides.yaml")
// Add debug logging
func debugConfig(client *ampyconfig.Client) {
if value, err := client.Get("debug.enabled"); err == nil && value == "true" {
fmt.Println("๐ DEBUG: Configuration loaded successfully")
// Print all configuration
if allConfig, err := client.GetAll(); err == nil {
fmt.Printf("๐ DEBUG: All config: %+v\n", allConfig)
}
}
}
๐งช Quick Health Check
Test NATS connection:
# Test NATS server
nats --server "nats://localhost:4222" server info
# Test JetStream
nats --server "nats://localhost:4222" stream ls
Test configuration loading:
# Python
python -c "
from ampy_config.layering import build_effective_config
try:
cfg, _ = build_effective_config(
'schema/ampy-config.schema.json',
'config/defaults.yaml',
'examples/dev.yaml',
[], [], 'env_allowlist.txt', None, 'runtime/overrides.yaml'
)
print('โ
Configuration loading works!')
except Exception as e:
print(f'โ Error: {e}')
"
# Go
go run - << 'EOF'
package main
import (
"fmt"
"github.com/AmpyFin/ampy-config/go/ampyconfig"
)
func main() {
client := ampyconfig.New("nats://localhost:4222", "ampy.dev", "runtime/overrides.yaml")
fmt.Println("โ
ampy-config client created successfully!")
}
EOF
๐ก๏ธ Security notes
- ๐ Secrets are never logged; redaction is enforced throughout the library
- ๐จ Prefer fail-shut for safety-critical domains (OMS risk, broker creds) and fail-open for low-risk knobs (metric sampling)
- ๐ Ensure access to secret backends is locked down with least privilege
๐ค Contributing
PRs welcome! Please include tests for new config keys, validation rules, and control-plane flows.
Before submitting:
pytest -q
python tools/validate.py examples/*.yaml
๐ License
Apache-2.0 (proposed). See LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ampy_config-1.1.5.tar.gz.
File metadata
- Download URL: ampy_config-1.1.5.tar.gz
- Upload date:
- Size: 47.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6504ac787c347de8952f3513719d752541f692edf460e1ddfdc828d1ded74842
|
|
| MD5 |
1649a0277a3b5c0904d7ebc8a1ebdc7f
|
|
| BLAKE2b-256 |
8ade39e5c8bb6ec77a4efdd40a81f48af0187905cf7f163d62c8e993c546d071
|
File details
Details for the file ampy_config-1.1.5-py3-none-any.whl.
File metadata
- Download URL: ampy_config-1.1.5-py3-none-any.whl
- Upload date:
- Size: 62.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
524b634308018e277c3852c3c812a9fab75fda250659d65b19a82842613f7bfc
|
|
| MD5 |
88007566b0eead5d28404ca3834319fc
|
|
| BLAKE2b-256 |
c54811aeb62c22bef5c0ac0dd588f4406f83d1e1f9d81373d9a4566fda245d8f
|