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

  1. -r for shell scripts - Raw output without quotes

  2. -s for slurping - Treat multiple objects as array

  3. -c for compact - One line per object

  4. API + jq = power - Parse any JSON API

  5. Logs are structured - Treat JSON logs as data

  6. Config generation - Build configs from JSON templates

Next Module

yq for YAML - Process YAML configs, Kubernetes manifests, and Ansible.