a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
653 lines
22 KiB
Bash
Executable File
653 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Promtail to Alloy Config Converter
|
|
#
|
|
# Parses an existing Promtail YAML configuration and outputs equivalent
|
|
# Grafana Alloy River syntax. Requires yq for YAML parsing.
|
|
#
|
|
# Usage:
|
|
# ./promtail-to-alloy.sh promtail.yml
|
|
# ./promtail-to-alloy.sh promtail.yml -o config.alloy
|
|
# ./promtail-to-alloy.sh --input /etc/promtail/config.yml --output /etc/alloy/config.alloy
|
|
#
|
|
# Author: Phil Connor
|
|
# Contact: contact@mylinux.work
|
|
# Website: https://mylinux.work
|
|
# License: MIT
|
|
# Version: 1.0
|
|
|
|
set -euo pipefail
|
|
|
|
# Constants
|
|
VERSION="1.0"
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
|
|
# Defaults
|
|
INPUT=""
|
|
OUTPUT=""
|
|
CUSTOM_HOSTNAME=""
|
|
DRY_RUN=false
|
|
|
|
# Counters for summary
|
|
COUNT_SCRAPE_CONFIGS=0
|
|
COUNT_PIPELINE_STAGES=0
|
|
COUNT_RELABEL_RULES=0
|
|
|
|
# --- Functions ---
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $SCRIPT_NAME [OPTIONS] [PROMTAIL_CONFIG]
|
|
|
|
Promtail to Alloy Config Converter v${VERSION}
|
|
|
|
Reads a Promtail YAML configuration file and outputs equivalent Grafana Alloy
|
|
River syntax to stdout or a file.
|
|
|
|
Options:
|
|
-i, --input FILE Promtail YAML config file (or pass as positional arg)
|
|
-o, --output FILE Output file (default: stdout)
|
|
--hostname NAME Override hostname label (default: extracted from config
|
|
or \$(hostname -s))
|
|
--dry-run Parse and show what would be converted without generating
|
|
-h, --help Show this help
|
|
--version Show version
|
|
|
|
Examples:
|
|
$SCRIPT_NAME promtail.yml
|
|
$SCRIPT_NAME promtail.yml -o config.alloy
|
|
$SCRIPT_NAME --input /etc/promtail/config.yml --output /etc/alloy/config.alloy
|
|
$SCRIPT_NAME --dry-run promtail.yml
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
check_deps() {
|
|
if ! command -v yq &>/dev/null; then
|
|
echo "ERROR: yq is required but not installed." >&2
|
|
echo "" >&2
|
|
echo "Install yq (mikefarah version):" >&2
|
|
echo " # Binary install:" >&2
|
|
echo " sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64" >&2
|
|
echo " sudo chmod +x /usr/local/bin/yq" >&2
|
|
echo "" >&2
|
|
echo " # Or via snap:" >&2
|
|
echo " sudo snap install yq" >&2
|
|
echo "" >&2
|
|
echo " See: https://github.com/mikefarah/yq" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
sanitize_label() {
|
|
local raw="$1"
|
|
echo "$raw" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_]/_/g' | sed 's/^_//;s/_$//' | sed 's/__*/_/g'
|
|
}
|
|
|
|
escape_river_string() {
|
|
local raw="$1"
|
|
# Double backslashes for River string literals
|
|
echo "$raw" | sed 's/\\/\\\\/g'
|
|
}
|
|
|
|
generate_header() {
|
|
local input_file="$1"
|
|
local input_basename
|
|
input_basename="$(basename "$input_file")"
|
|
|
|
echo "// Grafana Alloy Configuration"
|
|
echo "// Converted from Promtail config: ${input_basename}"
|
|
echo "// Date: $(date +%Y-%m-%d)"
|
|
echo "// Converter: ${SCRIPT_NAME} v${VERSION}"
|
|
echo ""
|
|
}
|
|
|
|
extract_clients() {
|
|
local file="$1"
|
|
local client_count
|
|
client_count=$(yq '.clients | length' "$file" 2>/dev/null || echo "0")
|
|
|
|
if [[ "$client_count" -eq 0 ]]; then
|
|
echo >&2 "WARN: No clients found in config, using placeholder URL"
|
|
echo "// Loki endpoint"
|
|
echo 'loki.write "default" {'
|
|
echo ' endpoint {'
|
|
echo ' url = "http://loki:3100/loki/api/v1/push"'
|
|
echo ' }'
|
|
echo '}'
|
|
return
|
|
fi
|
|
|
|
# Use the first client URL for the default write endpoint
|
|
local url
|
|
url=$(yq '.clients[0].url' "$file")
|
|
|
|
echo "// Loki endpoint"
|
|
echo 'loki.write "default" {'
|
|
echo ' endpoint {'
|
|
echo " url = \"${url}\""
|
|
echo ' }'
|
|
echo '}'
|
|
}
|
|
|
|
detect_hostname() {
|
|
local file="$1"
|
|
|
|
if [[ -n "$CUSTOM_HOSTNAME" ]]; then
|
|
echo "$CUSTOM_HOSTNAME"
|
|
return
|
|
fi
|
|
|
|
# Try to extract from first scrape_config's static_configs labels
|
|
local host
|
|
host=$(yq '.scrape_configs[0].static_configs[0].labels.host // ""' "$file" 2>/dev/null || echo "")
|
|
if [[ -n "$host" && "$host" != "null" ]]; then
|
|
echo "$host"
|
|
return
|
|
fi
|
|
|
|
hostname -s 2>/dev/null || echo "server"
|
|
}
|
|
|
|
convert_relabel_configs() {
|
|
local file="$1"
|
|
local sc_index="$2"
|
|
local label="$3"
|
|
local forward_to="$4"
|
|
|
|
local relabel_count
|
|
relabel_count=$(yq ".scrape_configs[${sc_index}].relabel_configs | length" "$file" 2>/dev/null || echo "0")
|
|
|
|
if [[ "$relabel_count" -eq 0 || "$relabel_count" == "null" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
COUNT_RELABEL_RULES=$((COUNT_RELABEL_RULES + relabel_count))
|
|
|
|
echo ""
|
|
echo "// Relabel rules: ${label}"
|
|
echo "loki.relabel \"${label}\" {"
|
|
echo " forward_to = [${forward_to}]"
|
|
|
|
for ((r = 0; r < relabel_count; r++)); do
|
|
echo ""
|
|
echo " rule {"
|
|
|
|
# source_labels
|
|
local sl_count
|
|
sl_count=$(yq ".scrape_configs[${sc_index}].relabel_configs[${r}].source_labels | length" "$file" 2>/dev/null || echo "0")
|
|
if [[ "$sl_count" -gt 0 && "$sl_count" != "null" ]]; then
|
|
local sl_arr=""
|
|
for ((s = 0; s < sl_count; s++)); do
|
|
local sl_val
|
|
sl_val=$(yq ".scrape_configs[${sc_index}].relabel_configs[${r}].source_labels[${s}]" "$file")
|
|
if [[ -n "$sl_arr" ]]; then
|
|
sl_arr+=", "
|
|
fi
|
|
sl_arr+="\"${sl_val}\""
|
|
done
|
|
echo " source_labels = [${sl_arr}]"
|
|
fi
|
|
|
|
# target_label
|
|
local tl
|
|
tl=$(yq ".scrape_configs[${sc_index}].relabel_configs[${r}].target_label // \"\"" "$file")
|
|
if [[ -n "$tl" && "$tl" != "null" ]]; then
|
|
echo " target_label = \"${tl}\""
|
|
fi
|
|
|
|
# regex
|
|
local rx
|
|
rx=$(yq ".scrape_configs[${sc_index}].relabel_configs[${r}].regex // \"\"" "$file")
|
|
if [[ -n "$rx" && "$rx" != "null" ]]; then
|
|
rx=$(escape_river_string "$rx")
|
|
echo " regex = \"${rx}\""
|
|
fi
|
|
|
|
# action
|
|
local act
|
|
act=$(yq ".scrape_configs[${sc_index}].relabel_configs[${r}].action // \"\"" "$file")
|
|
if [[ -n "$act" && "$act" != "null" ]]; then
|
|
echo " action = \"${act}\""
|
|
fi
|
|
|
|
# replacement
|
|
local repl
|
|
repl=$(yq ".scrape_configs[${sc_index}].relabel_configs[${r}].replacement // \"\"" "$file")
|
|
if [[ -n "$repl" && "$repl" != "null" ]]; then
|
|
repl=$(escape_river_string "$repl")
|
|
echo " replacement = \"${repl}\""
|
|
fi
|
|
|
|
echo " }"
|
|
done
|
|
|
|
echo "}"
|
|
return 0
|
|
}
|
|
|
|
convert_pipeline_stages() {
|
|
local file="$1"
|
|
local sc_index="$2"
|
|
local label="$3"
|
|
|
|
local stage_count
|
|
stage_count=$(yq ".scrape_configs[${sc_index}].pipeline_stages | length" "$file" 2>/dev/null || echo "0")
|
|
|
|
if [[ "$stage_count" -eq 0 || "$stage_count" == "null" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
COUNT_PIPELINE_STAGES=$((COUNT_PIPELINE_STAGES + stage_count))
|
|
|
|
echo ""
|
|
echo "// Pipeline: ${label}"
|
|
echo "loki.process \"${label}\" {"
|
|
echo " forward_to = [loki.write.default.receiver]"
|
|
|
|
for ((p = 0; p < stage_count; p++)); do
|
|
# Determine stage type — yq returns the first key
|
|
local stage_type
|
|
stage_type=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}] | keys | .[0]" "$file")
|
|
|
|
echo ""
|
|
case "$stage_type" in
|
|
regex)
|
|
local expr
|
|
expr=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].regex.expression" "$file")
|
|
expr=$(escape_river_string "$expr")
|
|
echo " stage.regex {"
|
|
echo " expression = \"${expr}\""
|
|
echo " }"
|
|
;;
|
|
json)
|
|
echo " stage.json {"
|
|
echo " expressions = {"
|
|
local json_keys
|
|
json_keys=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].json.expressions | keys | .[]" "$file" 2>/dev/null || true)
|
|
if [[ -n "$json_keys" ]]; then
|
|
while IFS= read -r key; do
|
|
local val
|
|
val=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].json.expressions.${key}" "$file")
|
|
if [[ "$val" == "null" || -z "$val" ]]; then
|
|
echo " ${key} = \"\","
|
|
else
|
|
echo " ${key} = \"${val}\","
|
|
fi
|
|
done <<< "$json_keys"
|
|
fi
|
|
echo " }"
|
|
echo " }"
|
|
;;
|
|
logfmt)
|
|
echo " stage.logfmt {}"
|
|
;;
|
|
labels)
|
|
echo " stage.labels {"
|
|
echo " values = {"
|
|
local lbl_keys
|
|
lbl_keys=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].labels | keys | .[]" "$file" 2>/dev/null || true)
|
|
if [[ -n "$lbl_keys" ]]; then
|
|
while IFS= read -r key; do
|
|
local val
|
|
val=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].labels.${key} // \"\"" "$file")
|
|
if [[ "$val" == "null" ]]; then
|
|
val=""
|
|
fi
|
|
echo " ${key} = \"${val}\","
|
|
done <<< "$lbl_keys"
|
|
fi
|
|
echo " }"
|
|
echo " }"
|
|
;;
|
|
timestamp)
|
|
local ts_source ts_format
|
|
ts_source=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].timestamp.source" "$file")
|
|
ts_format=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].timestamp.format" "$file")
|
|
echo " stage.timestamp {"
|
|
echo " source = \"${ts_source}\""
|
|
echo " format = \"${ts_format}\""
|
|
echo " }"
|
|
;;
|
|
output)
|
|
local out_source
|
|
out_source=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].output.source" "$file")
|
|
echo " stage.output {"
|
|
echo " source = \"${out_source}\""
|
|
echo " }"
|
|
;;
|
|
drop)
|
|
local drop_expr drop_reason
|
|
drop_expr=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].drop.expression // \"\"" "$file")
|
|
drop_reason=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].drop.drop_counter_reason // \"converted\"" "$file")
|
|
drop_expr=$(escape_river_string "$drop_expr")
|
|
echo " stage.drop {"
|
|
if [[ -n "$drop_expr" && "$drop_expr" != "null" ]]; then
|
|
echo " expression = \"${drop_expr}\""
|
|
fi
|
|
echo " drop_counter_reason = \"${drop_reason}\""
|
|
echo " }"
|
|
;;
|
|
match)
|
|
local match_sel
|
|
match_sel=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].match.selector" "$file")
|
|
echo " stage.match {"
|
|
echo " selector = \"${match_sel}\""
|
|
# Nested stages in match are complex; output a placeholder comment
|
|
local nested_count
|
|
nested_count=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].match.stages | length" "$file" 2>/dev/null || echo "0")
|
|
if [[ "$nested_count" -gt 0 && "$nested_count" != "null" ]]; then
|
|
echo " // NOTE: ${nested_count} nested stage(s) — review and convert manually"
|
|
fi
|
|
echo " }"
|
|
;;
|
|
multiline)
|
|
local ml_firstline ml_max_lines
|
|
ml_firstline=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].multiline.firstline" "$file")
|
|
ml_max_lines=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].multiline.max_lines // 128" "$file")
|
|
ml_firstline=$(escape_river_string "$ml_firstline")
|
|
echo " stage.multiline {"
|
|
echo " firstline = \"${ml_firstline}\""
|
|
echo " max_lines = ${ml_max_lines}"
|
|
echo " }"
|
|
;;
|
|
static_labels)
|
|
echo " stage.static_labels {"
|
|
echo " values = {"
|
|
local sl_keys
|
|
sl_keys=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].static_labels | keys | .[]" "$file" 2>/dev/null || true)
|
|
if [[ -n "$sl_keys" ]]; then
|
|
while IFS= read -r key; do
|
|
local val
|
|
val=$(yq ".scrape_configs[${sc_index}].pipeline_stages[${p}].static_labels.${key}" "$file")
|
|
echo " ${key} = \"${val}\","
|
|
done <<< "$sl_keys"
|
|
fi
|
|
echo " }"
|
|
echo " }"
|
|
;;
|
|
*)
|
|
echo " // Unsupported stage type: ${stage_type} — convert manually"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo "}"
|
|
return 0
|
|
}
|
|
|
|
convert_journal_source() {
|
|
local file="$1"
|
|
local sc_index="$2"
|
|
local label="$3"
|
|
local host="$4"
|
|
|
|
local forward_target="loki.write.default.receiver"
|
|
|
|
# Check for relabel_configs — if present, forward through relabel first
|
|
local has_relabel=false
|
|
local relabel_count
|
|
relabel_count=$(yq ".scrape_configs[${sc_index}].relabel_configs | length" "$file" 2>/dev/null || echo "0")
|
|
if [[ "$relabel_count" -gt 0 && "$relabel_count" != "null" ]]; then
|
|
has_relabel=true
|
|
forward_target="loki.relabel.${label}.receiver"
|
|
fi
|
|
|
|
# Extract journal config
|
|
local max_age
|
|
max_age=$(yq ".scrape_configs[${sc_index}].journal.max_age // \"12h\"" "$file")
|
|
|
|
echo ""
|
|
echo "// Journal source: ${label}"
|
|
echo "loki.source.journal \"${label}\" {"
|
|
echo " max_age = \"${max_age}\""
|
|
|
|
# Journal labels
|
|
local jl_keys
|
|
jl_keys=$(yq ".scrape_configs[${sc_index}].journal.labels | keys | .[]" "$file" 2>/dev/null || true)
|
|
if [[ -n "$jl_keys" ]]; then
|
|
echo " labels = {"
|
|
while IFS= read -r key; do
|
|
local val
|
|
val=$(yq ".scrape_configs[${sc_index}].journal.labels.${key}" "$file")
|
|
echo " ${key} = \"${val}\","
|
|
done <<< "$jl_keys"
|
|
echo " }"
|
|
fi
|
|
|
|
if [[ "$has_relabel" == true ]]; then
|
|
echo " relabel_rules = loki.relabel.${label}.rules"
|
|
fi
|
|
|
|
echo " forward_to = [${forward_target}]"
|
|
echo "}"
|
|
|
|
# Generate relabel block if needed
|
|
if [[ "$has_relabel" == true ]]; then
|
|
convert_relabel_configs "$file" "$sc_index" "$label" "loki.write.default.receiver"
|
|
fi
|
|
}
|
|
|
|
convert_file_source() {
|
|
local file="$1"
|
|
local sc_index="$2"
|
|
local label="$3"
|
|
local host="$4"
|
|
|
|
local forward_target="loki.write.default.receiver"
|
|
|
|
# Check for pipeline_stages — if present, forward through process
|
|
local has_pipeline=false
|
|
local stage_count
|
|
stage_count=$(yq ".scrape_configs[${sc_index}].pipeline_stages | length" "$file" 2>/dev/null || echo "0")
|
|
if [[ "$stage_count" -gt 0 && "$stage_count" != "null" ]]; then
|
|
has_pipeline=true
|
|
forward_target="loki.process.${label}.receiver"
|
|
fi
|
|
|
|
# Iterate static_configs
|
|
local sc_count
|
|
sc_count=$(yq ".scrape_configs[${sc_index}].static_configs | length" "$file" 2>/dev/null || echo "0")
|
|
|
|
if [[ "$sc_count" -eq 0 || "$sc_count" == "null" ]]; then
|
|
echo >&2 "WARN: scrape_config[${sc_index}] '${label}' has no static_configs, skipping"
|
|
return
|
|
fi
|
|
|
|
echo ""
|
|
echo "// File source: ${label}"
|
|
echo "loki.source.file \"${label}\" {"
|
|
echo " targets = ["
|
|
|
|
for ((t = 0; t < sc_count; t++)); do
|
|
# Get __path__
|
|
local path_val
|
|
path_val=$(yq ".scrape_configs[${sc_index}].static_configs[${t}].labels.__path__ // \"\"" "$file")
|
|
if [[ -z "$path_val" || "$path_val" == "null" ]]; then
|
|
# Try targets array format
|
|
path_val=$(yq ".scrape_configs[${sc_index}].static_configs[${t}].targets[0].__path__ // \"\"" "$file" 2>/dev/null || echo "")
|
|
fi
|
|
|
|
echo " {"
|
|
|
|
if [[ -n "$path_val" && "$path_val" != "null" ]]; then
|
|
echo " \"__path__\" = \"${path_val}\","
|
|
fi
|
|
|
|
# Get other labels (excluding __path__)
|
|
local label_keys
|
|
label_keys=$(yq ".scrape_configs[${sc_index}].static_configs[${t}].labels | keys | .[]" "$file" 2>/dev/null || true)
|
|
if [[ -n "$label_keys" ]]; then
|
|
while IFS= read -r lk; do
|
|
if [[ "$lk" == "__path__" ]]; then
|
|
continue
|
|
fi
|
|
local lv
|
|
lv=$(yq ".scrape_configs[${sc_index}].static_configs[${t}].labels.${lk}" "$file")
|
|
# Pad alignment
|
|
printf ' "%-8s = "%s",\n' "${lk}\"" "${lv}"
|
|
done <<< "$label_keys"
|
|
fi
|
|
|
|
# Add host label if not already present
|
|
if ! echo "$label_keys" | grep -q '^host$'; then
|
|
printf ' "%-8s = "%s",\n' 'host"' "${host}"
|
|
fi
|
|
|
|
echo " },"
|
|
done
|
|
|
|
echo " ]"
|
|
echo " forward_to = [${forward_target}]"
|
|
echo "}"
|
|
|
|
# Generate pipeline_stages block if present
|
|
if [[ "$has_pipeline" == true ]]; then
|
|
convert_pipeline_stages "$file" "$sc_index" "$label"
|
|
fi
|
|
}
|
|
|
|
extract_scrape_configs() {
|
|
local file="$1"
|
|
local host="$2"
|
|
|
|
local sc_total
|
|
sc_total=$(yq '.scrape_configs | length' "$file" 2>/dev/null || echo "0")
|
|
|
|
if [[ "$sc_total" -eq 0 || "$sc_total" == "null" ]]; then
|
|
echo >&2 "WARN: No scrape_configs found in config"
|
|
return
|
|
fi
|
|
|
|
COUNT_SCRAPE_CONFIGS=$sc_total
|
|
|
|
for ((i = 0; i < sc_total; i++)); do
|
|
local job_name
|
|
job_name=$(yq ".scrape_configs[${i}].job_name" "$file")
|
|
local label
|
|
label=$(sanitize_label "$job_name")
|
|
|
|
# Detect journal vs file source
|
|
local has_journal
|
|
has_journal=$(yq ".scrape_configs[${i}].journal // \"\"" "$file" 2>/dev/null || echo "")
|
|
|
|
if [[ -n "$has_journal" && "$has_journal" != "null" && "$has_journal" != "" ]]; then
|
|
convert_journal_source "$file" "$i" "$label" "$host"
|
|
else
|
|
convert_file_source "$file" "$i" "$label" "$host"
|
|
fi
|
|
done
|
|
}
|
|
|
|
generate_config() {
|
|
local file="$1"
|
|
local host="$2"
|
|
|
|
generate_header "$file"
|
|
extract_clients "$file"
|
|
extract_scrape_configs "$file" "$host"
|
|
echo ""
|
|
}
|
|
|
|
# --- Main ---
|
|
|
|
main() {
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-i|--input) INPUT="$2"; shift 2 ;;
|
|
-o|--output) OUTPUT="$2"; shift 2 ;;
|
|
--hostname) CUSTOM_HOSTNAME="$2"; shift 2 ;;
|
|
--dry-run) DRY_RUN=true; shift ;;
|
|
-h|--help) usage ;;
|
|
--version) echo "$SCRIPT_NAME v${VERSION}"; exit 0 ;;
|
|
-*) echo "ERROR: Unknown option: $1" >&2; echo "" >&2; usage ;;
|
|
*)
|
|
# Positional argument — treat as input file
|
|
if [[ -z "$INPUT" ]]; then
|
|
INPUT="$1"
|
|
else
|
|
echo "ERROR: Unexpected argument: $1" >&2
|
|
exit 1
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate input
|
|
if [[ -z "$INPUT" ]]; then
|
|
echo "ERROR: No input file specified" >&2
|
|
echo "" >&2
|
|
usage
|
|
fi
|
|
|
|
if [[ ! -f "$INPUT" ]]; then
|
|
echo "ERROR: Input file not found: ${INPUT}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Check dependencies
|
|
check_deps
|
|
|
|
# Validate YAML
|
|
if ! yq '.' "$INPUT" &>/dev/null; then
|
|
echo "ERROR: Failed to parse YAML: ${INPUT}" >&2
|
|
echo "Ensure the file is valid YAML and yq can read it." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Detect hostname
|
|
local host
|
|
host=$(detect_hostname "$INPUT")
|
|
|
|
# Dry-run mode
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
echo "Dry run — parsing ${INPUT}" >&2
|
|
echo "" >&2
|
|
|
|
local sc_total
|
|
sc_total=$(yq '.scrape_configs | length' "$INPUT" 2>/dev/null || echo "0")
|
|
local client_count
|
|
client_count=$(yq '.clients | length' "$INPUT" 2>/dev/null || echo "0")
|
|
|
|
echo " Clients: ${client_count}" >&2
|
|
echo " Scrape configs: ${sc_total}" >&2
|
|
echo " Hostname: ${host}" >&2
|
|
echo "" >&2
|
|
|
|
for ((i = 0; i < sc_total; i++)); do
|
|
local jn
|
|
jn=$(yq ".scrape_configs[${i}].job_name" "$INPUT")
|
|
local has_j
|
|
has_j=$(yq ".scrape_configs[${i}].journal // \"\"" "$INPUT" 2>/dev/null || echo "")
|
|
local src_type="file"
|
|
if [[ -n "$has_j" && "$has_j" != "null" && "$has_j" != "" ]]; then
|
|
src_type="journal"
|
|
fi
|
|
local ps_count
|
|
ps_count=$(yq ".scrape_configs[${i}].pipeline_stages | length" "$INPUT" 2>/dev/null || echo "0")
|
|
local rl_count
|
|
rl_count=$(yq ".scrape_configs[${i}].relabel_configs | length" "$INPUT" 2>/dev/null || echo "0")
|
|
|
|
echo " [${i}] ${jn} (${src_type}) — ${ps_count} pipeline stages, ${rl_count} relabel rules" >&2
|
|
done
|
|
echo "" >&2
|
|
echo "Run without --dry-run to generate the Alloy config." >&2
|
|
exit 0
|
|
fi
|
|
|
|
# Generate config
|
|
if [[ -n "$OUTPUT" ]]; then
|
|
generate_config "$INPUT" "$host" > "$OUTPUT"
|
|
echo >&2 "Config written to: ${OUTPUT}"
|
|
else
|
|
generate_config "$INPUT" "$host"
|
|
fi
|
|
|
|
# Summary to stderr
|
|
echo >&2 "Converted ${COUNT_SCRAPE_CONFIGS} scrape configs, ${COUNT_PIPELINE_STAGES} pipeline stages, ${COUNT_RELABEL_RULES} relabel rules"
|
|
}
|
|
|
|
main "$@"
|