Fix deployment tooling: shell scripts and Terraform
Terraform (validated with the real terraform CLI - was never actually
run against this cluster, no state file existed):
- Delete main.tf: it declared a duplicate kubernetes_namespace.core
(also in namespaces.tf) and a duplicate provider "kubernetes" block
(also in providers.tf), both hard errors that would fail
`terraform plan` immediately.
- Fix workloads.tf references to 6 files deleted in the manifest
cleanup (jupiter-sts/svc, atlantis-sts/svc, fiesta-sts/svc) - now
points at the canonical nearle-jupiter/atlantis/fiesta.yaml.
- Fix every kubernetes_manifest resource: they fed multi-document
YAML (multiple '---'-separated docs per file) straight into
yamldecode(), which only parses a single document. Rewrote using a
split-on-'---' + for_each pattern, confirmed safe first by checking
separator counts exactly match document counts for every affected
file (no embedded '---' inside any script/config content).
- Add the doormile namespace; rename kubernetes_namespace to
kubernetes_namespace_v1 (fixes a deprecation warning).
- `terraform validate` now passes clean.
Shell scripts:
- deploy-nearle-stack.sh only applied 4 of the ~13 files in
manifests/nearle/ - missing the ConfigMap/Secrets fiesta/jupiter/
titan/ariane need via envFrom, the fiesta gateway script ConfigMap,
atlantis entirely, and the Gateway/ReferenceGrant/jupiter-cors-proxy
resources. Now applies every file (verified by diffing the
directory listing against the script).
- Added deploy-doormile.sh and deploy-ingress.sh - nothing previously
applied ingress-unified.yaml or traefik-middlewares.yaml at all.
- Rewrote deploy.sh as an orchestrator calling all of the above in
order (previously referenced a manifests/namespace.yaml layout that
hasn't existed since before this repo's initial commit).
- Rewrote check-k8s-status.sh to check the real namespaces
(core/nearle/alaska/doormile/kubernetes-dashboard) instead of a
'nats-backend' namespace that never existed in this repo.
- Fixed a `cd` bug in setup-jetstream.sh that made it change into
shfiles/ and then look for scripts/setup_jetstream.py there (a
child directory that doesn't exist) - it could never have found its
own target file. Now pulls NATS credentials from the live
nats-credentials Secret instead of a third hardcoded copy.
Python scripts:
- sync_manifests.py had hardcoded Windows paths (e:\nats\kubernetes\...)
- replaced with paths relative to the script's own location so it
actually runs here (or anywhere). Verified by running it.
- setup_jetstream.py created durable consumers under different names
than worker.py computes at runtime ({NATS_CONSUMER}_{subject}), so
its max_deliver/ack_wait settings never actually reached the
consumers workers bind to. Naming now derived with the same logic
worker.py uses - verified all 10 derived names match workers.yaml
exactly.
- purge-old-messages.py had hardcoded NATS credentials with no env
var override at all - fixed to match the pattern used everywhere
else.
This commit is contained in:
@@ -7,9 +7,9 @@ import nats
|
||||
import os
|
||||
|
||||
async def purge_messages():
|
||||
nats_url = "nats://nats.workolik.com:4222"
|
||||
nats_user = "admin"
|
||||
nats_password = "package@321#"
|
||||
nats_url = os.getenv("NATS_URL", "nats://nats.workolik.com:4222")
|
||||
nats_user = os.getenv("NATS_USER", "admin")
|
||||
nats_password = os.getenv("NATS_PASSWORD", "")
|
||||
|
||||
try:
|
||||
print(f"Connecting to NATS at {nats_url}...")
|
||||
|
||||
@@ -1,77 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Setup JetStream stream and consumer for NATS
|
||||
Setup JetStream streams and consumers for NATS
|
||||
Run this after NATS is deployed and running
|
||||
|
||||
Durable consumer names are derived with the exact same logic worker.py uses
|
||||
at runtime (NATS_CONSUMER + sanitized subject). WORKER_DOMAINS below must be
|
||||
kept in sync with the NATS_STREAM / NATS_CONSUMER / FILTER_SUBJECT env vars
|
||||
in manifests/core/workers.yaml - if they drift apart, the consumers created
|
||||
here (with their max_deliver/ack_wait settings) will never be the ones the
|
||||
workers actually bind to, and this script's config becomes a no-op.
|
||||
"""
|
||||
import asyncio
|
||||
import nats
|
||||
import os
|
||||
import sys
|
||||
|
||||
WORKER_DOMAINS = [
|
||||
{"stream": "ORDERS", "consumer": "orders-worker", "subjects": [
|
||||
"api.v1.mob.orders.createorder",
|
||||
]},
|
||||
{"stream": "DELIVERIES", "consumer": "deliveries-worker", "subjects": [
|
||||
"api.v1.deliveries.createdeliveries",
|
||||
"api.v1.deliveries.updatedelivery",
|
||||
"api.v2.deliveries.createdeliverylog",
|
||||
]},
|
||||
{"stream": "CUSTOMERS", "consumer": "customers-worker", "retention": "work", "subjects": [
|
||||
"api.v1.mob.customers.login",
|
||||
"api.v1.mob.customers.create",
|
||||
]},
|
||||
{"stream": "RIDER", "consumer": "rider-logs-worker", "subjects": [
|
||||
"api.v2.partners.createriderlog",
|
||||
"api.v2.partners.createbreaklog",
|
||||
"api.v2.partners.updatebreaklog",
|
||||
]},
|
||||
{"stream": "PRODUCTS", "consumer": "products-worker", "subjects": [
|
||||
"api.v1.web.products.create",
|
||||
]},
|
||||
]
|
||||
|
||||
|
||||
def durable_name(consumer: str, subject: str) -> str:
|
||||
"""Mirrors worker.py's durable-name derivation exactly."""
|
||||
suffix = subject.replace(".", "_").replace("*", "all").replace(">", "all")
|
||||
return f"{consumer}_{suffix}"
|
||||
|
||||
|
||||
async def setup_jetstream():
|
||||
"""Configure JetStream with two streams (DELIVERIES, RIDER) and per-subject consumers."""
|
||||
nats_url = os.getenv("NATS_URL", "nats://nats.workolik.com:4222")
|
||||
nats_user = os.getenv("NATS_USER", "admin")
|
||||
nats_password = os.getenv("NATS_PASSWORD", "package@321#")
|
||||
|
||||
# Stream definitions
|
||||
streams = {
|
||||
"DELIVERIES": {
|
||||
"subjects": [
|
||||
"api.v1.deliveries.createdeliveries",
|
||||
"api.v1.deliveries.updatedelivery",
|
||||
"api.v2.deliveries.createdeliverylog",
|
||||
],
|
||||
},
|
||||
"RIDER": {
|
||||
"subjects": [
|
||||
"api.v2.partners.createriderlog",
|
||||
"api.v2.partners.createbreaklog",
|
||||
"api.v2.partners.updatebreaklog",
|
||||
],
|
||||
},
|
||||
"ORDERS": {
|
||||
"subjects": [
|
||||
"api.v1.mob.orders.createorder",
|
||||
],
|
||||
},
|
||||
"PRODUCTS": {
|
||||
"subjects": [
|
||||
"api.v1.web.products.create",
|
||||
],
|
||||
},
|
||||
"CUSTOMERS": {
|
||||
"subjects": [
|
||||
"api.v1.mob.customers.login",
|
||||
"api.v1.mob.customers.create",
|
||||
],
|
||||
"retention": "work" # Special handling for Login queue: delete immediately after ack
|
||||
},
|
||||
}
|
||||
|
||||
# Per-subject durable consumers
|
||||
consumers = {
|
||||
"DELIVERIES": {
|
||||
"api.v1.deliveries.createdeliveries": "deliveries_createdeliveries",
|
||||
"api.v1.deliveries.updatedelivery": "deliveries_updatedelivery",
|
||||
"api.v2.deliveries.createdeliverylog": "deliveries_createdeliverylog",
|
||||
},
|
||||
"RIDER": {
|
||||
"api.v2.partners.createriderlog": "rider_createriderlog",
|
||||
"api.v2.partners.createbreaklog": "rider_createbreaklog",
|
||||
"api.v2.partners.updatebreaklog": "rider_updatebreaklog",
|
||||
},
|
||||
"ORDERS": {
|
||||
"api.v1.mob.orders.createorder": "orders_createorder",
|
||||
},
|
||||
"PRODUCTS": {
|
||||
"api.v1.web.products.create": "products_create",
|
||||
},
|
||||
"CUSTOMERS": {
|
||||
"api.v1.mob.customers.login": "customers_login",
|
||||
"api.v1.mob.customers.create": "customers_create",
|
||||
},
|
||||
}
|
||||
nats_password = os.getenv("NATS_PASSWORD", "")
|
||||
|
||||
try:
|
||||
print(f"Connecting to NATS at {nats_url}...")
|
||||
@@ -85,17 +62,16 @@ async def setup_jetstream():
|
||||
js = nc.jetstream()
|
||||
|
||||
# Create / recreate streams
|
||||
for stream_name, cfg in streams.items():
|
||||
for domain in WORKER_DOMAINS:
|
||||
stream_name = domain["stream"]
|
||||
subjects = domain["subjects"]
|
||||
retention_policy = domain.get("retention", "limits")
|
||||
try:
|
||||
info = await js.stream_info(stream_name)
|
||||
print(f"⚠️ Stream '{stream_name}' already exists with subjects={info.config.subjects}, updating...")
|
||||
|
||||
# Determine retention policy
|
||||
retention_policy = cfg.get("retention", "limits")
|
||||
|
||||
await js.update_stream(
|
||||
name=stream_name,
|
||||
subjects=cfg["subjects"],
|
||||
subjects=subjects,
|
||||
storage="memory",
|
||||
retention=retention_policy,
|
||||
max_age=24 * 60 * 60,
|
||||
@@ -106,13 +82,9 @@ async def setup_jetstream():
|
||||
except Exception as e:
|
||||
if "not found" in str(e).lower() or "404" in str(e).lower():
|
||||
print(f"Creating stream '{stream_name}'...")
|
||||
|
||||
# Determine retention policy (use 'limits' by default, 'work' for queues)
|
||||
retention_policy = cfg.get("retention", "limits")
|
||||
|
||||
await js.add_stream(
|
||||
name=stream_name,
|
||||
subjects=cfg["subjects"],
|
||||
subjects=subjects,
|
||||
storage="memory",
|
||||
retention=retention_policy,
|
||||
max_age=24 * 60 * 60,
|
||||
@@ -123,10 +95,14 @@ async def setup_jetstream():
|
||||
else:
|
||||
print(f"⚠️ Could not inspect stream '{stream_name}': {e}")
|
||||
|
||||
# Create durable consumers per subject
|
||||
# Create durable consumers per subject - names match what worker.py
|
||||
# computes at runtime, so max_deliver/ack_wait here actually apply.
|
||||
print("\nConfiguring consumers...")
|
||||
for stream_name, subject_map in consumers.items():
|
||||
for subject, durable in subject_map.items():
|
||||
for domain in WORKER_DOMAINS:
|
||||
stream_name = domain["stream"]
|
||||
consumer = domain["consumer"]
|
||||
for subject in domain["subjects"]:
|
||||
durable = durable_name(consumer, subject)
|
||||
try:
|
||||
print(f"Creating consumer '{durable}' on stream '{stream_name}' for subject '{subject}'...")
|
||||
await js.add_consumer(
|
||||
@@ -136,7 +112,7 @@ async def setup_jetstream():
|
||||
ack_policy="explicit",
|
||||
deliver_policy="all",
|
||||
max_deliver=5,
|
||||
ack_wait=30,
|
||||
ack_wait=60,
|
||||
)
|
||||
print(f"✅ Consumer '{durable}' created")
|
||||
except Exception as e:
|
||||
@@ -147,12 +123,12 @@ async def setup_jetstream():
|
||||
|
||||
print("\n✅ JetStream setup complete!")
|
||||
print(" Streams:")
|
||||
for name, cfg in streams.items():
|
||||
print(f" - {name}: {', '.join(cfg['subjects'])}")
|
||||
for domain in WORKER_DOMAINS:
|
||||
print(f" - {domain['stream']}: {', '.join(domain['subjects'])}")
|
||||
print(" Consumers:")
|
||||
for stream_name, subject_map in consumers.items():
|
||||
for subject, durable in subject_map.items():
|
||||
print(f" - {durable}: stream={stream_name}, subject={subject}")
|
||||
for domain in WORKER_DOMAINS:
|
||||
for subject in domain["subjects"]:
|
||||
print(f" - {durable_name(domain['consumer'], subject)}: stream={domain['stream']}, subject={subject}")
|
||||
|
||||
await nc.close()
|
||||
sys.exit(0)
|
||||
@@ -163,4 +139,3 @@ async def setup_jetstream():
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(setup_jetstream())
|
||||
|
||||
|
||||
@@ -51,16 +51,18 @@ def update_yaml_with_script(yaml_path, script_path, key_line_start):
|
||||
|
||||
print(f"Successfully updated {yaml_path}")
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Update Fiesta Gateway
|
||||
update_yaml_with_script(
|
||||
r'e:\nats\kubernetes\manifests\nearle\fiesta-gateway.yaml',
|
||||
r'e:\nats\kubernetes\conf\app.py',
|
||||
os.path.join(REPO_ROOT, 'manifests', 'nearle', 'fiesta-gateway.yaml'),
|
||||
os.path.join(REPO_ROOT, 'conf', 'app.py'),
|
||||
' app.py: |'
|
||||
)
|
||||
|
||||
# Update Worker Script
|
||||
update_yaml_with_script(
|
||||
r'e:\nats\kubernetes\manifests\core\worker-script.yaml',
|
||||
r'e:\nats\kubernetes\conf\worker.py',
|
||||
os.path.join(REPO_ROOT, 'manifests', 'core', 'worker-script.yaml'),
|
||||
os.path.join(REPO_ROOT, 'conf', 'worker.py'),
|
||||
' worker.py: |'
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user