Infrastructure Patterns
Practical jq patterns for infrastructure work: parsing API responses, processing logs, generating configs, and automating operations.
API Response Parsing
Kubernetes
# List all pod names
kubectl get pods -o json | jq -r '.items[].metadata.name'
# Pods not running
kubectl get pods -o json | jq -r '
.items[] |
select(.status.phase != "Running") |
"\(.metadata.name): \(.status.phase)"
'
# Container image versions
kubectl get pods -o json | jq -r '
.items[] |
.spec.containers[] |
"\(.name): \(.image)"
'
# Resource requests/limits
kubectl get pods -o json | jq '
[.items[] | {
pod: .metadata.name,
containers: [.spec.containers[] | {
name: .name,
requests: .resources.requests,
limits: .resources.limits
}]
}]
'
Docker/Podman
# Running containers: name, image, ports
docker ps --format json | jq -s '
[.[] | {
name: .Names,
image: .Image,
ports: .Ports
}]
'
# Container inspect: mounts
docker inspect container_name | jq '
.[0].Mounts | map({
source: .Source,
destination: .Destination,
mode: .Mode
})
'
# Images sorted by size
docker images --format json | jq -s '
sort_by(.Size | gsub("GB|MB|KB"; "") | tonumber) |
reverse |
.[:5] |
map({repository: .Repository, tag: .Tag, size: .Size})
'
GitHub API
# List repos with stars
curl -s "https://api.github.com/users/octocat/repos" | jq '
[.[] | {name: .name, stars: .stargazers_count, url: .html_url}] |
sort_by(.stars) |
reverse
'
# Open issues count by repo
curl -s "https://api.github.com/orgs/kubernetes/repos?per_page=100" | jq '
[.[] | {name: .name, open_issues: .open_issues_count}] |
sort_by(.open_issues) |
reverse |
.[:10]
'
# Recent PRs
gh pr list --json number,title,author,createdAt | jq '
[.[] | {
pr: .number,
title: .title,
author: .author.login,
age: ((now - (.createdAt | fromdateiso8601)) / 86400 | floor | tostring) + " days"
}]
'
Vault
# List secrets
vault kv list -format=json secret/ | jq -r '.[]'
# Parse secret
vault kv get -format=json secret/myapp | jq '.data.data'
# All secret metadata
vault kv metadata get -format=json secret/myapp | jq '{
created: .data.created_time,
updated: .data.updated_time,
version: .data.current_version,
destroyed: .data.destroyed
}'
Log Processing
JSON Logs
# Filter by level
cat app.log | jq -c 'select(.level == "error")'
# Extract fields
cat app.log | jq -r '"\(.timestamp) [\(.level)] \(.message)"'
# Count by level
cat app.log | jq -s '
group_by(.level) |
map({level: .[0].level, count: length}) |
sort_by(.count) |
reverse
'
# Errors in last hour
cat app.log | jq -c '
select(.level == "error") |
select((.timestamp | fromdateiso8601) > (now - 3600))
'
Syslog (systemd journal)
# JSON output from journalctl
journalctl -u sshd -o json --since "1 hour ago" | jq -s '
[.[] | {
time: .SYSLOG_TIMESTAMP,
message: .MESSAGE,
priority: .PRIORITY
}]
'
# SSH auth failures
journalctl -u sshd -o json | jq -s '
[.[] | select(.MESSAGE | contains("Failed password"))] |
length
'
ISE/Network Logs
# Parse ISE RADIUS
cat ise_radius.json | jq '
select(.ise.auth_result == "FAILED") |
{
time: .timestamp,
user: .user.name,
mac: .endpoint.mac,
reason: .ise.failure_reason,
policy: .ise.policy_set
}
'
# Auth failures by user
cat ise_radius.json | jq -s '
[.[] | select(.ise.auth_result == "FAILED")] |
group_by(.user.name) |
map({user: .[0].user.name, failures: length}) |
sort_by(.failures) |
reverse |
.[:10]
'
# Firewall denies by source
cat ftd.json | jq -s '
[.[] | select(.action == "deny")] |
group_by(.src.ip) |
map({ip: .[0].src.ip, count: length}) |
sort_by(.count) |
reverse |
.[:10]
'
Config Generation
Environment Variables
# JSON to env file
echo '{"DB_HOST":"localhost","DB_PORT":5432,"DEBUG":true}' | jq -r '
to_entries |
map("\(.key)=\(.value)") |
.[]
' > .env
# Env file to JSON
cat .env | jq -Rs '
split("\n") |
map(select(length > 0) | split("=") | {(.[0]): .[1]}) |
add
'
Hosts File Generation
echo '[
{"ip": "10.0.0.1", "hostname": "web-01", "aliases": ["www"]},
{"ip": "10.0.0.2", "hostname": "db-01", "aliases": ["mysql", "database"]}
]' | jq -r '
.[] |
"\(.ip)\t\(.hostname) \(.aliases | join(" "))"
'
Output:
10.0.0.1 web-01 www 10.0.0.2 db-01 mysql database
SSH Config
echo '[
{"name": "jump", "host": "jump.example.com", "user": "admin"},
{"name": "web", "host": "10.0.0.1", "user": "deploy", "proxy": "jump"}
]' | jq -r '
.[] |
"Host \(.name)\n HostName \(.host)\n User \(.user)" +
(if .proxy then "\n ProxyJump \(.proxy)" else "" end) +
"\n"
'
Ansible Inventory
# JSON to INI format
echo '{
"webservers": ["web-01", "web-02"],
"databases": ["db-01"]
}' | jq -r '
to_entries |
map("[\(.key)]\n\(.value | join("\n"))") |
join("\n\n")
'
Output:
[webservers] web-01 web-02 [databases] db-01
Data Transformation
CSV to JSON
# Simple CSV (no quotes)
echo 'name,ip,port
web-01,10.0.0.1,80
db-01,10.0.0.2,5432' | jq -Rs '
split("\n") |
map(select(length > 0) | split(",")) |
.[0] as $header |
.[1:] |
map([$header, .] | transpose | map({(.[0]): .[1]}) | add)
'
JSON to CSV
echo '[{"name":"a","val":1},{"name":"b","val":2}]' | jq -r '
(.[0] | keys_unsorted) as $keys |
($keys | join(",")) + "\n" +
(map([.[$keys[]]] | join(",")) | join("\n"))
'
Output:
name,val a,1 b,2
Flatten Nested Structure
echo '{
"server": {
"host": "10.0.0.1",
"config": {
"port": 8080,
"ssl": true
}
}
}' | jq '
[paths(scalars)] as $paths |
reduce $paths[] as $path ({}; . + {($path | join(".")): getpath($path)})
'
Output:
{
"server.host": "10.0.0.1",
"server.config.port": 8080,
"server.config.ssl": true
}
Unflatten to Nested
echo '{"server.host":"10.0.0.1","server.port":8080}' | jq '
to_entries |
reduce .[] as $item ({};
setpath($item.key | split("."); $item.value)
)
'
Output:
{
"server": {
"host": "10.0.0.1",
"port": 8080
}
}
Monitoring & Metrics
Prometheus/JSON Metrics
# Parse Prometheus JSON output
curl -s localhost:9090/api/v1/query?query=up | jq '
.data.result |
map({
job: .metric.job,
instance: .metric.instance,
up: (.value[1] == "1")
})
'
Aggregate Time Series
echo '[
{"time":"2026-03-15T14:00:00Z","value":100},
{"time":"2026-03-15T14:01:00Z","value":150},
{"time":"2026-03-15T14:02:00Z","value":120}
]' | jq '
{
count: length,
min: (map(.value) | min),
max: (map(.value) | max),
avg: ((map(.value) | add) / length),
sum: (map(.value) | add)
}
'
Output:
{
"count": 3,
"min": 100,
"max": 150,
"avg": 123.333,
"sum": 370
}
netapi Integration
ISE Sessions
# Active sessions
netapi ise mnt active-sessions -f json | jq '
[.[] | {
user: .user_name,
mac: .calling_station_id,
ip: .framed_ip_address,
auth_method: .authentication_method
}]
'
# Sessions by policy
netapi ise mnt active-sessions -f json | jq '
group_by(.selected_authz_profile) |
map({profile: .[0].selected_authz_profile, count: length}) |
sort_by(.count) |
reverse
'
GitHub/GitLab
# Recent PRs
netapi github prs -f json | jq '
[.[] | {
number: .number,
title: .title,
author: .user.login,
created: .created_at
}] |
.[:5]
'
# Repo language breakdown
netapi github repo -f json | jq '.language'
Validation Patterns
Schema Validation
# Check required fields
echo '{"name":"server"}' | jq '
def validate:
if has("name") and has("ip") then
"valid"
else
"missing: " + (["name","ip"] - keys | join(", "))
end;
validate
'
# "missing: ip"
Type Validation
echo '{"port":"8080"}' | jq '
if (.port | type) == "number" then
"valid"
else
"port must be number, got " + (.port | type)
end
'
# "port must be number, got string"
Range Validation
echo '{"port":70000}' | jq '
if .port >= 1 and .port <= 65535 then
"valid"
else
"port out of range: " + (.port | tostring)
end
'
# "port out of range: 70000"
One-Liners
# Pretty print
cat ugly.json | jq .
# Compact
jq -c . file.json
# Extract single field
jq -r '.name' file.json
# Count array
jq '. | length' file.json
# Keys only
jq 'keys' file.json
# First N items
jq '.[:5]' file.json
# Last N items
jq '.[-5:]' file.json
# Null check
jq '.field // "default"' file.json
# Multiple files
jq -s 'add' file1.json file2.json
# From string
echo '{"a":1}' | jq '.a'
# To file
jq '.' input.json > output.json
Key Takeaways
-
-rfor shell scripts - Raw output without quotes -
-sfor slurping - Treat multiple objects as array -
-cfor compact - One line per object -
API + jq = power - Parse any JSON API
-
Logs are structured - Treat JSON logs as data
-
Config generation - Build configs from JSON templates
Next Module
yq for YAML - Process YAML configs, Kubernetes manifests, and Ansible.