Variables
Variables make views interactive by allowing users to dynamically control which data is fetched. Variables are defined in the templating section and their values are substituted into queries using the $(var.<key>) syntax.
Overview
Variables in Mission Control allow you to:
- Create interactive dropdowns users can adjust
- Populate options from static lists or dynamic sources
- Build dependent variables (e.g., filter namespaces by cluster)
- Use variable values in queries to control what data is fetched
- Maintain separate cache entries per variable combination
Variables vs Column Filters
Mission Control has two ways to filter view data. Both are server-side, but they operate at different stages:
┌─────────────────────────────────────┐
│ Sources │
│ (configs, prometheus, checks, etc) │
└──────────────────┬──────────────────┘
│
▼
┌──────────────────┐
│ Execute Queries │ ◀── Variables filter here
└────────┬─────────┘ (only matching data is fetched)
│
▼
┌────────────────────┐
│ In-memory SQLite │
│ (joins/merge) │
└──────────┬─────────┘
│
▼
┌─────────────────────────────────────┐
│ PostgreSQL Table │
│ (view_<namespace>_<name>) │
└──────────────────┬──────────────────┘
│
▼
┌────────────────┐
│ PostgREST API │ ◀── Column Filters add WHERE clauses here
└───────┬────────┘
│
▼
┌──────────┐
│ UI │
└──────────┘
| Variables | Column Filters | |
|---|---|---|
| Defined in | spec.templating | spec.columns[].filter |
| When filtering happens | At source query time | At display time (PostgreSQL query) |
| Effect | Only matching data is fetched and stored in the view table | All data is in PostgreSQL; UI queries with WHERE clauses |
| Use case | Scoping large datasets (e.g., select a cluster/namespace) | Narrowing down within already-materialized results |
| Cache impact | Each variable combination has its own cache | No cache impact |
Example: If you have 10,000 pods across 5 clusters:
- Variable with
clusterselection → Only pods from that cluster are fetched from the source and stored (~2,000 rows in PostgreSQL) - Column filter on
cluster→ All 10,000 pods are stored in PostgreSQL; user's filter selection adds a WHERE clause to the query
Use variables when you need to scope down large datasets at the source. Use column filters when you want users to explore within already-reasonable result sets.
Basic Variable Structure
templating:
# Simple static values variable
- key: environment
label: 'Environment'
values: ['dev', 'staging', 'prod']
default: 'dev'
# Dynamic values from config items
- key: cluster
label: 'Cluster'
valueFrom:
config:
types: ['Kubernetes::Cluster']
default: 'us-east-1'
Variable Properties
Required Properties
| Property | Type | Description |
|---|---|---|
key | string | Unique identifier for the variable (used in template expressions) |
label | string | Display name shown to users |
Optional Properties
| Property | Type | Description |
|---|---|---|
default | string | Default value when view loads |
values | array | Static list of options (mutually exclusive with valueFrom) |
valueFrom | object | Dynamic options from config items (mutually exclusive with values) |
dependsOn | array | List of variable keys this depends on |
Variable Types
Static Values Variable
Use predefined options:
- key: loglevel
label: 'Log Level'
values: ['debug', 'info', 'warning', 'error', 'fatal']
default: 'info'
Use cases: Environments, log levels, predefined status filters, feature flags
Dynamic Values Variable
Load options from configuration items:
- key: cluster
label: 'Kubernetes Cluster'
valueFrom:
config:
types: ['Kubernetes::Cluster'] # Search for Cluster config items
tagSelector: 'environment=prod' # Optional: filter configs
limit: 100 # Optional: limit results
Use cases: Select from actual resources, allow any config type as filter, dynamic lists
The variable will be populated with config item names as options. Users can select one value.
Dependent Variables
Create variables that depend on other variables:
templating:
# First variable (no dependencies)
- key: cluster
label: 'Cluster'
valueFrom:
config:
types: ['Kubernetes::Cluster']
# Dependent variable (filters based on cluster)
- key: namespace
label: 'Namespace'
valueFrom:
config:
types: ['Kubernetes::Namespace']
tagSelector: 'cluster=$(var.cluster)' # Reference parent variable
dependsOn: ['cluster']
default: 'default'
# Another dependent variable
- key: pod
label: 'Pod'
valueFrom:
config:
types: ['Kubernetes::Pod']
# Can depend on multiple variables
tagSelector: 'cluster=$(var.cluster),namespace=$(var.namespace)'
dependsOn: ['cluster', 'namespace']
How it works:
- Cluster variable loads first (no dependencies)
- User selects a cluster
- Namespace variable's options are dynamically filtered by the selected cluster
- User selects a namespace
- Pod variable's options are filtered by both cluster AND namespace
Using Variables in Queries
Variables are available in query definitions using the $(var.<key>) syntax:
In Config Queries
queries:
pods:
configs:
types: ['Kubernetes::Pod']
tagSelector: 'cluster=$(var.cluster),namespace=$(var.namespace)'
search: '$(var.pod_search)'
In Prometheus Queries
queries:
metrics:
prometheus:
query: |
up{cluster="$(var.cluster)",namespace="$(var.namespace)"}
In Custom Merge Queries
merge: |
SELECT * FROM configs
WHERE cluster = '$(var.cluster)'
AND namespace = '$(var.namespace)'
Using Variables in Mappings
Variables can also be used in column mapping expressions:
mapping:
cluster_display: "'$(var.cluster)'"
pod_name: 'row.name'
Variable Dependency Resolution
When views have multiple variables, Mission Control resolves them using topological sorting:
- Level 0: Variables with no dependencies load first
- Level 1: Variables depending on Level 0 variables load next
- Level N: Variables are resolved in dependency order
Circular dependency detection: Mission Control validates that no circular dependencies exist and rejects invalid configurations.
Example resolution order:
cluster (Level 0 - no deps)
↓
namespace (Level 1 - depends on cluster)
pod (Level 1 - depends on cluster)
↓
container (Level 2 - depends on pod)
Variable Scope and Caching
Each unique combination of variable values creates a separate cache entry:
cluster=us-east-1, namespace=default
→ separate cache entry
cluster=us-east-1, namespace=kube-system
→ different cache entry
cluster=eu-central-1, namespace=default
→ another cache entry
This means:
- Users switching between variable combinations get independent cached results
- No interference between different filter selections
- Each combination maintains its own cache control lifecycle
Examples
Example 1: Simple Static Variables
apiVersion: mission-control.flanksource.com/v1
kind: View
metadata:
name: environment-filter
spec:
templating:
- key: env
label: 'Environment'
values: ['development', 'staging', 'production']
default: 'development'
- key: sort_by
label: 'Sort By'
values: ['name', 'status', 'age']
default: 'name'
queries:
items:
configs:
types: ['Application']
tagSelector: 'environment=$(var.env)'
columns:
- name: item_name
type: string
primaryKey: true
mapping:
item_name: 'row.name'
Example 2: Dynamic Cluster and Namespace Selection
apiVersion: mission-control.flanksource.com/v1
kind: View
metadata:
name: pod-explorer
spec:
display:
title: 'Pod Explorer'
templating:
# Level 0: No dependencies
- key: cluster
label: 'Cluster'
valueFrom:
config:
types: ['Kubernetes::Cluster']
default: 'production'
# Level 1: Depends on cluster
- key: namespace
label: 'Namespace'
valueFrom:
config:
types: ['Kubernetes::Namespace']
tagSelector: 'cluster=$(var.cluster)'
dependsOn: ['cluster']
default: 'default'
columns:
- name: pod_name
type: string
primaryKey: true
- name: status
type: status
queries:
pods:
configs:
types: ['Kubernetes::Pod']
tagSelector: 'cluster=$(var.cluster),namespace=$(var.namespace)'
mapping:
pod_name: 'row.name'
status: 'row.status'
Example 3: Multiple Data Sources with Variables
apiVersion: mission-control.flanksource.com/v1
kind: View
metadata:
name: pod-metrics
spec:
templating:
- key: cluster
label: 'Cluster'
values: ['us-east-1', 'us-west-2', 'eu-central-1']
default: 'us-east-1'
- key: metric_type
label: 'Metric Type'
values: ['cpu', 'memory', 'network']
default: 'cpu'
columns:
- name: pod
type: string
primaryKey: true
- name: value
type: gauge
queries:
pods:
configs:
types: ['Kubernetes::Pod']
tagSelector: 'cluster=$(var.cluster)'
metrics:
prometheus:
query: |
$(var.metric_type)_usage{cluster="$(var.cluster)"}
columns:
pod: string
value: decimal
merge: |
SELECT p.name as pod, m.value as value
FROM pods p
LEFT JOIN metrics m ON p.name = m.pod
mapping:
pod: 'row.pod'
value: 'row.value'
Best Practices
-
Use meaningful keys - Choose variable keys that describe what they filter
key: environment # ✓ Clear
key: env_filter # ✓ Also clear
key: x # ✗ Unclear -
Provide helpful labels - Give users context about what to select
label: "Production Environment" # ✓ Descriptive
label: "Prod" # ✗ Too vague -
Set sensible defaults - Most users select default, so choose a good one
default: "production" # ✓ Safe, commonly used
default: "dev" # ? May not be what most users need -
Order variables logically - Put base filters first, dependent filters later
# ✓ Good order
- cluster # User selects cluster first
- namespace # Then narrows to namespace
- pod # Then finally a specific pod
# ✗ Poor order
- pod # User can't narrow down pods first
- namespace
- cluster -
Limit dynamic variable options - Add reasonable limits to prevent huge lists
valueFrom:
config:
types: ['Kubernetes::Pod']
limit: 100 # Don't load 10,000 pods
Variable Templating Syntax
Variables use simple $(var.key) syntax for substitution:
# In tagSelector
tagSelector: "cluster=$(var.cluster),env=$(var.env)"
# In search fields
search: "$(var.pod_search)"
# In PromQL queries
query: 'up{cluster="$(var.cluster)"}'
# In merge/custom SQL
WHERE cluster = '$(var.cluster)'
Note: String substitution happens before query execution, so values are injected as-is. For dynamic SQL, consider using parameterized queries if dealing with user input.