fire-planner: add examples ingest Job (toggled) + weekly CronJob
Adds the K8s plumbing for the Reddit FIRE-examples ingest path:
- ExternalSecret fire-planner-examples-reddit (Reddit OAuth from
Vault secret/viktor.trading_bot_reddit_{client_id,client_secret}).
- ExternalSecret fire-planner-examples-claude (claude-agent-service
bearer from Vault secret/claude-agent-service.api_bearer_token).
- kubernetes_job_v1.examples_bulk_ingest — one-shot bulk Job toggled
via var.run_examples_bulk_ingest (default false). Timestamp-named so
each (true) transition creates a fresh Job; lifecycle ignores the
name so re-plans don't propose phantom renames.
- kubernetes_cron_job_v1.examples_weekly_delta — Sunday 04:00 UTC
--top=week --limit=200 incremental run.
Both runners share the env_from plumbing of the existing recompute
CronJob (fire-planner-secrets, fire-planner-db-creds,
wealthfolio-sync-db-creds) plus examples-specific vars
(REDDIT_USER_AGENT, LLAMA_CPP_BASE_URL, CLAUDE_AGENT_SERVICE_URL,
plus the three secret-backed env vars).
Plan-only this commit — actual apply lands in Task 17 after the
ingest image build.
This commit is contained in:
parent
4dff834c8a
commit
0044c3a8ea
1 changed files with 316 additions and 1 deletions
|
|
@ -546,7 +546,7 @@ module "ingress_api" {
|
|||
ingress_path = ["/api/"]
|
||||
tls_secret_name = var.tls_secret_name
|
||||
# auth = "none": XHR-based API endpoints; forward-auth 302+cookie-dance breaks CORS preflight and browser fetch().
|
||||
auth = "none"
|
||||
auth = "none"
|
||||
}
|
||||
|
||||
# Plan-time read of the ESO-created K8s Secret for Grafana datasource
|
||||
|
|
@ -605,3 +605,318 @@ resource "kubernetes_config_map" "grafana_fire_planner_datasource" {
|
|||
|
||||
# CI retrigger 2026-05-16T13:42:57+00:00 — bulk enrollment apply (pipeline #689 killed)
|
||||
# CI retrigger v2 2026-05-16T13:46:35+00:00
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Reddit FIRE examples ingest — Job (bulk, toggled) + weekly CronJob
|
||||
# Backs the fire_planner.examples module. See:
|
||||
# ~/code/fire-planner/docs/plans/2026-05-28-reddit-examples-{design,plan}.md
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
variable "llama_cpp_base_url" {
|
||||
type = string
|
||||
description = "llama-cpp /v1/chat/completions endpoint for primary LLM extraction"
|
||||
default = "http://llama-cpp.llama-cpp.svc.cluster.local:8000/v1/chat/completions"
|
||||
}
|
||||
|
||||
variable "claude_agent_service_url" {
|
||||
type = string
|
||||
description = "claude-agent-service /v1/chat/completions endpoint for Tier 2 fallback"
|
||||
default = "http://claude-agent-service.claude-agent.svc.cluster.local:8080/v1/chat/completions"
|
||||
}
|
||||
|
||||
variable "run_examples_bulk_ingest" {
|
||||
type = bool
|
||||
description = "Flip to true once to bulk-populate fire_example. Reset to false after."
|
||||
default = false
|
||||
}
|
||||
|
||||
# Reddit OAuth creds pulled from Vault secret/viktor.
|
||||
resource "kubernetes_manifest" "external_secret_examples_reddit" {
|
||||
manifest = {
|
||||
apiVersion = "external-secrets.io/v1beta1"
|
||||
kind = "ExternalSecret"
|
||||
metadata = {
|
||||
name = "fire-planner-examples-reddit"
|
||||
namespace = local.namespace
|
||||
}
|
||||
spec = {
|
||||
refreshInterval = "1h"
|
||||
secretStoreRef = {
|
||||
name = "vault-kv"
|
||||
kind = "ClusterSecretStore"
|
||||
}
|
||||
target = {
|
||||
name = "fire-planner-examples-reddit"
|
||||
}
|
||||
data = [
|
||||
{
|
||||
secretKey = "REDDIT_CLIENT_ID"
|
||||
remoteRef = {
|
||||
key = "viktor"
|
||||
property = "trading_bot_reddit_client_id"
|
||||
}
|
||||
},
|
||||
{
|
||||
secretKey = "REDDIT_CLIENT_SECRET"
|
||||
remoteRef = {
|
||||
key = "viktor"
|
||||
property = "trading_bot_reddit_client_secret"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
depends_on = [kubernetes_namespace.fire_planner]
|
||||
}
|
||||
|
||||
# claude-agent-service bearer pulled separately so its rotation cadence
|
||||
# is decoupled from the Reddit creds.
|
||||
resource "kubernetes_manifest" "external_secret_examples_claude" {
|
||||
manifest = {
|
||||
apiVersion = "external-secrets.io/v1beta1"
|
||||
kind = "ExternalSecret"
|
||||
metadata = {
|
||||
name = "fire-planner-examples-claude"
|
||||
namespace = local.namespace
|
||||
}
|
||||
spec = {
|
||||
refreshInterval = "1h"
|
||||
secretStoreRef = {
|
||||
name = "vault-kv"
|
||||
kind = "ClusterSecretStore"
|
||||
}
|
||||
target = {
|
||||
name = "fire-planner-examples-claude"
|
||||
}
|
||||
data = [
|
||||
{
|
||||
secretKey = "CLAUDE_AGENT_BEARER"
|
||||
remoteRef = {
|
||||
key = "claude-agent-service"
|
||||
property = "api_bearer_token"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
depends_on = [kubernetes_namespace.fire_planner]
|
||||
}
|
||||
|
||||
# Bulk one-shot Job — toggled via var.run_examples_bulk_ingest. Flip to
|
||||
# true once, apply, wait for completion, flip back. The timestamp() in
|
||||
# the name ensures Terraform creates a fresh Job on each (true)
|
||||
# transition rather than refusing to recreate an existing one.
|
||||
resource "kubernetes_job_v1" "examples_bulk_ingest" {
|
||||
count = var.run_examples_bulk_ingest ? 1 : 0
|
||||
metadata {
|
||||
name = "fire-planner-examples-bulk-${formatdate("YYYYMMDDhhmm", timestamp())}"
|
||||
namespace = kubernetes_namespace.fire_planner.metadata[0].name
|
||||
}
|
||||
spec {
|
||||
backoff_limit = 0
|
||||
template {
|
||||
metadata {
|
||||
labels = local.labels
|
||||
}
|
||||
spec {
|
||||
restart_policy = "OnFailure"
|
||||
image_pull_secrets {
|
||||
name = "registry-credentials"
|
||||
}
|
||||
container {
|
||||
name = "ingest"
|
||||
image = local.image
|
||||
image_pull_policy = "IfNotPresent"
|
||||
command = ["python", "-m", "fire_planner", "examples", "ingest",
|
||||
"--top=all,year", "--limit=1000"]
|
||||
|
||||
# DB plumbing — mirror the fire_planner_recompute CronJob.
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "fire-planner-secrets"
|
||||
}
|
||||
}
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "fire-planner-db-creds"
|
||||
}
|
||||
}
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "wealthfolio-sync-db-creds"
|
||||
}
|
||||
}
|
||||
|
||||
# Examples-specific vars.
|
||||
env {
|
||||
name = "REDDIT_CLIENT_ID"
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = "fire-planner-examples-reddit"
|
||||
key = "REDDIT_CLIENT_ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
env {
|
||||
name = "REDDIT_CLIENT_SECRET"
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = "fire-planner-examples-reddit"
|
||||
key = "REDDIT_CLIENT_SECRET"
|
||||
}
|
||||
}
|
||||
}
|
||||
env {
|
||||
name = "CLAUDE_AGENT_BEARER"
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = "fire-planner-examples-claude"
|
||||
key = "CLAUDE_AGENT_BEARER"
|
||||
}
|
||||
}
|
||||
}
|
||||
env {
|
||||
name = "REDDIT_USER_AGENT"
|
||||
value = "fire-planner/0.1"
|
||||
}
|
||||
env {
|
||||
name = "LLAMA_CPP_BASE_URL"
|
||||
value = var.llama_cpp_base_url
|
||||
}
|
||||
env {
|
||||
name = "CLAUDE_AGENT_SERVICE_URL"
|
||||
value = var.claude_agent_service_url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycle {
|
||||
# The name embeds a timestamp so a re-plan after time has passed
|
||||
# would otherwise propose a no-op rename. Ignore.
|
||||
# KYVERNO_LIFECYCLE_V1
|
||||
ignore_changes = [
|
||||
metadata[0].name,
|
||||
spec[0].template[0].spec[0].dns_config,
|
||||
]
|
||||
}
|
||||
depends_on = [
|
||||
kubernetes_manifest.external_secret,
|
||||
kubernetes_manifest.db_external_secret,
|
||||
kubernetes_manifest.wealthfolio_sync_db_external_secret,
|
||||
kubernetes_manifest.external_secret_examples_reddit,
|
||||
kubernetes_manifest.external_secret_examples_claude,
|
||||
]
|
||||
}
|
||||
|
||||
# Weekly delta — top-of-week milestone posts. Sunday 04:00 UTC.
|
||||
resource "kubernetes_cron_job_v1" "examples_weekly_delta" {
|
||||
metadata {
|
||||
name = "fire-planner-examples-weekly"
|
||||
namespace = kubernetes_namespace.fire_planner.metadata[0].name
|
||||
}
|
||||
spec {
|
||||
schedule = "0 4 * * 0"
|
||||
concurrency_policy = "Forbid"
|
||||
successful_jobs_history_limit = 3
|
||||
failed_jobs_history_limit = 3
|
||||
job_template {
|
||||
metadata {
|
||||
labels = local.labels
|
||||
}
|
||||
spec {
|
||||
backoff_limit = 0
|
||||
ttl_seconds_after_finished = 86400
|
||||
template {
|
||||
metadata {
|
||||
labels = local.labels
|
||||
}
|
||||
spec {
|
||||
restart_policy = "OnFailure"
|
||||
image_pull_secrets {
|
||||
name = "registry-credentials"
|
||||
}
|
||||
container {
|
||||
name = "ingest"
|
||||
image = local.image
|
||||
image_pull_policy = "IfNotPresent"
|
||||
command = ["python", "-m", "fire_planner", "examples", "ingest",
|
||||
"--top=week", "--limit=200"]
|
||||
|
||||
# DB plumbing — mirror the fire_planner_recompute CronJob.
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "fire-planner-secrets"
|
||||
}
|
||||
}
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "fire-planner-db-creds"
|
||||
}
|
||||
}
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "wealthfolio-sync-db-creds"
|
||||
}
|
||||
}
|
||||
|
||||
# Examples-specific vars — keep in sync with the bulk Job.
|
||||
env {
|
||||
name = "REDDIT_CLIENT_ID"
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = "fire-planner-examples-reddit"
|
||||
key = "REDDIT_CLIENT_ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
env {
|
||||
name = "REDDIT_CLIENT_SECRET"
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = "fire-planner-examples-reddit"
|
||||
key = "REDDIT_CLIENT_SECRET"
|
||||
}
|
||||
}
|
||||
}
|
||||
env {
|
||||
name = "CLAUDE_AGENT_BEARER"
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = "fire-planner-examples-claude"
|
||||
key = "CLAUDE_AGENT_BEARER"
|
||||
}
|
||||
}
|
||||
}
|
||||
env {
|
||||
name = "REDDIT_USER_AGENT"
|
||||
value = "fire-planner/0.1"
|
||||
}
|
||||
env {
|
||||
name = "LLAMA_CPP_BASE_URL"
|
||||
value = var.llama_cpp_base_url
|
||||
}
|
||||
env {
|
||||
name = "CLAUDE_AGENT_SERVICE_URL"
|
||||
value = var.claude_agent_service_url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
# KYVERNO_LIFECYCLE_V1
|
||||
ignore_changes = [spec[0].job_template[0].spec[0].template[0].spec[0].dns_config]
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
kubernetes_manifest.external_secret,
|
||||
kubernetes_manifest.db_external_secret,
|
||||
kubernetes_manifest.wealthfolio_sync_db_external_secret,
|
||||
kubernetes_manifest.external_secret_examples_reddit,
|
||||
kubernetes_manifest.external_secret_examples_claude,
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue