Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion providers/infrastructure/backend/kro/e2e_devmode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ import (
// NOT need — dev mode swaps the tier images for platform dev images, so the
// instance is created without them to prove the graph renders regardless.
var e2eDevImageStripped = map[string][]string{
"application": {"frontendImage", "backendImage"},
"application": {"frontendImage", "backendImage"},
"simple-webapp": {"image"},
"worker": {"image"},
}

func TestE2EDevelopmentMode(t *testing.T) {
Expand Down
4 changes: 1 addition & 3 deletions providers/infrastructure/backend/kro/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ const (

// e2eMinimalSpecs supplies a valid instance spec for templates that ship no
// sampleValues (those that do — application, database — use them directly).
var e2eMinimalSpecs = map[string]map[string]any{
"redis-cache": {"name": "e2e-redis"},
}
var e2eMinimalSpecs = map[string]map[string]any{}
Comment on lines 82 to +84

// e2ePlatformStamped are spec fields a platform component normally writes onto an
// instance before kro reconciles it — values the user does NOT supply (the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ spec:
- name: KEDGE_GATEWAY_NAMESPACE
value: {{ . | quote }}
{{- end }}
{{- if not .Values.tenantLimitRange.enabled }}
# Opt out of stamping the kedge-defaults LimitRange into tenant
# namespaces (for clusters that manage resource policy themselves).
- name: KEDGE_TENANT_LIMITRANGE
value: "disabled"
{{- end }}
{{- with .Values.development.agentImage }}
# Dev-mode agent injector image (${kedge.devAgentImage}).
- name: KEDGE_DEV_AGENT_IMAGE
Expand Down
8 changes: 8 additions & 0 deletions providers/infrastructure/deploy/chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ application:
name: "cloudflare-tunnel"
namespace: "cfgate-system"

# Default container resource policy stamped into every tenant namespace as a
# LimitRange named "kedge-defaults" (defaultRequest 50m/128Mi, default limit
# 500m/512Mi, max 2cpu/2Gi per container). Create-only — operators may
# hand-tune a tenant's copy without it being overwritten. Disable when the
# runtime cluster manages its own resource policy.
tenantLimitRange:
enabled: true

# Development-mode images (docs/app-studio-template-sandboxes.md). These run
# TENANT code, so production deployments should pin them by digest. Empty
# values fall back to the in-binary defaults (node → the kedge sandbox-runner
Expand Down
116 changes: 116 additions & 0 deletions providers/infrastructure/install/templates/cron-job.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
apiVersion: infrastructure.kedge.faros.sh/v1alpha1
kind: Template
metadata:
name: cron-job
spec:
displayName: "Scheduled job"
description: "A container that runs on a cron schedule and exits — CronJob only, no Service, no URL. Reports, cleanups, periodic syncs."
category: Workloads
version: 0.1.0
backend: kro
# One-click provision defaults for the portal form. busybox's default
# entrypoint exits 0 immediately, so the sample schedule visibly produces
# completed Jobs with zero external setup.
sampleValues:
name: demo-job
image: busybox:stable
schedule: "*/30 * * * *"
# Operational guidance for AI agents discovering this template over MCP.
agent:
usage: |
Runs ONE container on a cron schedule; each run must do its work and
EXIT 0. Choose this for periodic work — reports, cleanups, backups,
syncs. It is the opposite lifecycle of the worker template (a process
that runs forever): a cron-job container that never exits will pile up
overlapping-forbidden runs, not "keep running".

YOU SUPPLY exactly one container image whose ENTRYPOINT performs the
task and exits: 0 on success, non-zero on failure (failed runs are
retried up to 3 times, then recorded as a failed Job). There is no
command/args input — bake the task into the image entrypoint.

SCHEDULE: standard 5-field cron (schedule input, default "0 * * * *" =
hourly, evaluated in UTC). Concurrent runs are forbidden — if a run is
still going when the next tick fires, the new run is skipped. Runs may
occasionally fire late or be skipped; the task must be idempotent and
tolerate that.

This template has NO development mode — develop and test the image
elsewhere, then provision it here. No network exposure; durable state
belongs in a database template instance.

INPUTS you set when provisioning: name, image (both required); schedule.
outputs:
- "status.schedule — the active cron schedule."
# ── Portal presentation ───────────────────────────────────────────────
view:
columns:
- header: Schedule
path: spec.schedule
type: code
detail:
- title: Configuration
fields:
- label: Schedule
path: status.schedule
type: code
- label: Image
path: spec.image
type: code
instanceCRD:
group: infrastructure.kedge.faros.sh
version: v1alpha1
resource: scheduledjobs
kind: ScheduledJob
schema:
type: object
properties:
name:
type: string
description: "Logical name; used as the CronJob name in the tenant namespace."
image:
type: string
description: "Container image whose entrypoint performs the task and exits (0 = success)."
schedule:
type: string
default: "0 * * * *"
description: "Standard 5-field cron expression, evaluated in UTC. Default: hourly."
required:
- name
- image

# ── backendConfig: the kro resource graph ─────────────────────────────
# namespace: default is remapped to the per-tenant namespace on the runtime
# cluster — same convention as every seed template.
backendConfig:
resources:
- id: cronJob
template:
apiVersion: batch/v1
kind: CronJob
metadata:
name: ${schema.spec.name}
namespace: default
spec:
schedule: ${schema.spec.schedule}
# A run still going when the next tick fires wins; the new run is
# skipped. Matches the agent contract above.
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
backoffLimit: 3
template:
metadata:
labels:
app: ${schema.spec.name}
spec:
restartPolicy: Never
containers:
- name: job
image: ${schema.spec.image}
# Echoed from the materialized CronJob (this kro fork forbids ${schema.*}
# in status — status may reference resources only).
status:
schedule: ${cronJob.spec.schedule}
27 changes: 27 additions & 0 deletions providers/infrastructure/install/templates/database.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,33 @@ spec:
- "status.host / status.port - the in-cluster Service endpoint for PostgreSQL."
- "status.ready - ready replica count for the Postgres StatefulSet."
- "status.connectionSecretRef - Secret '<name>-db-credentials' containing host, port, user, dbname, password, and uri keys. Secret values are not surfaced in status."
# ── Portal presentation ───────────────────────────────────────────────
view:
columns:
- header: Version
path: spec.version
type: badge
detail:
- title: Connection
fields:
- label: Host
path: status.host
type: code
- label: Port
path: status.port
type: code
- label: Credentials secret
path: status.connectionSecretRef.name
type: code
- title: Configuration
fields:
- label: Postgres version
path: spec.version
type: badge
- title: Readiness
fields:
- label: Database
path: status.ready
instanceCRD:
group: infrastructure.kedge.faros.sh
version: v1alpha1
Expand Down
69 changes: 60 additions & 9 deletions providers/infrastructure/install/templates/redis-cache.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ metadata:
name: redis-cache
spec:
displayName: "Redis cache"
description: "A Redis instance for caching workloads. StatefulSet + Service; size knob picks the memory limit."
description: "A Redis instance for caching workloadsStatefulSet + Service; the size knob picks the memory bucket. Ephemeral: data does not survive a restart."
category: Databases
version: 0.1.0
version: 0.2.0
backend: kro
sampleValues:
name: cache
Expand All @@ -25,12 +25,46 @@ spec:
`<name>-credentials`, key `uri`. The URI has the form
`redis://:<password>@<name>:6379`. Redis may not be ready the moment a
consuming app starts — retry the initial connection. This template does
not expose Redis publicly, and persistent=false (the default) means data
does not survive a restart.
not expose Redis publicly.

Redis is EPHEMERAL here: no persistence is configured, so a pod restart
loses all data. Treat it strictly as a cache — never as a system of
record; durable state belongs in the database template. The size input
picks the container memory bucket: small=64Mi, medium=256Mi, large=1Gi.
outputs:
- "status.host / status.port — the in-cluster Service endpoint for Redis."
- "status.ready — ready replica count for the Redis StatefulSet."
- "status.connectionSecretRef — Secret '<name>-credentials' containing host, port, password, and uri keys. Secret values are not surfaced in status."
# ── Portal presentation ───────────────────────────────────────────────
view:
columns:
- header: Size
path: spec.size
type: badge
detail:
- title: Connection
fields:
- label: Host
path: status.host
type: code
- label: Port
path: status.port
type: code
- label: Credentials secret
path: status.connectionSecretRef.name
type: code
- title: Configuration
fields:
- label: Size
path: spec.size
type: badge
- label: Redis version
path: spec.version
type: badge
- title: Readiness
fields:
- label: Redis
path: status.ready
instanceCRD:
group: infrastructure.kedge.faros.sh
version: v1alpha1
Expand All @@ -46,14 +80,11 @@ spec:
type: string
enum: ["small", "medium", "large"]
default: "small"
description: "Memory bucket (64Mi / 256Mi / 1Gi)."
description: "Memory bucket, wired to the container's memory request+limit (small=64Mi, medium=256Mi, large=1Gi)."
version:
type: string
enum: ["6", "7"]
default: "7"
persistent:
type: boolean
default: false
required:
- name
# backendConfig is interpreted by the kro backend. Every resource sets
Expand Down Expand Up @@ -168,7 +199,17 @@ spec:
containers:
- name: redis
image: redis:${schema.spec.version}
command: ["redis-server", "--requirepass", "$(REDIS_PASSWORD)"]
# maxmemory sits ~25% under the container limit (working
# memory + fragmentation headroom) with LRU eviction, so
# a full cache evicts instead of OOM-killing the pod.
command:
- redis-server
- --requirepass
- $(REDIS_PASSWORD)
- --maxmemory
- '${schema.spec.size == "small" ? "48mb" : (schema.spec.size == "medium" ? "192mb" : "768mb")}'
- --maxmemory-policy
- allkeys-lru
env:
- name: REDIS_PASSWORD
valueFrom:
Expand All @@ -177,6 +218,16 @@ spec:
key: password
ports:
- containerPort: 6379
# The size bucket bounds the container: memory request ==
# limit (Redis is memory, an OOM-kill under the limit is
# the contract working). Single-quoted: the CEL ternaries
# contain ": " which YAML would otherwise read as mappings.
resources:
requests:
cpu: 50m
memory: '${schema.spec.size == "small" ? "64Mi" : (schema.spec.size == "medium" ? "256Mi" : "1Gi")}'
limits:
memory: '${schema.spec.size == "small" ? "64Mi" : (schema.spec.size == "medium" ? "256Mi" : "1Gi")}'
- id: service
template:
apiVersion: v1
Expand Down
Loading
Loading