Introduction

Some of you have already noticed that I've changed employers. Thanks to this change, I have a new, big playground where I'm just starting to let loose. Today I discovered the huge storage system out in the field 😉

Today I discovered the big storage system out in the field 💾 Unfortunately, I don't have a central spot out there where I can keep an eye on all my toys. That's why I started setting up a Netbox https://netboxlabs.com/docs/netbox/ earlier this week and feeding it data accordingly

But all that work of gathering information is useless if the information isn't updated automatically. So I started adding the devices, models, and everything else you need to the Netbox. I got most of the hardware components set up pretty quickly using Netbox Data Exchange https://netboxlabs.com/ndx/. I had to build a few things myself, but then I was ready to start assembling.

Automatic Inventory Item creation

#created by ♞ raffael.willems@imar-navigation.de
#!/usr/bin/env bash
set -euo pipefail

# needed params
NETBOX_URL="${NETBOX_URL:-defaulturl}"
NETBOX_TOKEN="${NETBOX_TOKEN:-avalidtoken}"
DEVICE_ID="${DEVICE_ID:-159}" # muss entsprechend fuer das jeweilige Treunas einmalig angepasst werden.

if [[ -z "$NETBOX_TOKEN" ]]; then
  echo "Please set NETBOX_TOKEN" >&2
  exit 1
fi

for cmd in zpool glabel midclt jq curl; do
  command -v "$cmd" >/dev/null 2>&1 || {
    echo "Missing required command: $cmd" >&2
    exit 1
  }
done
# get the gptids from zpool and put that info in a array
mapfile -t gptids < <(zpool status | awk '/raidz[0-9]/{n++; next} n==1 && /gptid\// {print $1} n>1{exit}')
# do we got gptids?
if [[ ${#gptids[@]} -eq 0 ]]; then
  echo "No GPTIDs found in the first raidz vdev" >&2
  exit 1
fi

# create a map and fill it with daX only (remove daXp entries)
declare -A gptid_to_da=()
while read -r name status comp; do
  [[ -n "${comp:-}" ]] || continue
  if [[ "$comp" =~ ^(da[0-9]+)(p[0-9]+)?$ ]]; then
    da_base="${BASH_REMATCH[1]}"
    gptid_to_da["$name"]="$da_base"
  fi
done < <(glabel status)

mapfile -t da_names < <(
  for g in "${gptids[@]}"; do
    [[ -n "${gptid_to_da[$g]:-}" ]] && printf '%s\n' "${gptid_to_da[$g]}"
  done
)

if [[ ${#da_names[@]} -eq 0 ]]; then
  echo "No matching da names found for the GPTIDs" >&2
  exit 1
fi

da_regex="$(printf '%s|' "${da_names[@]}")"
da_regex="^(${da_regex%|})$"
# create the Payload for the POST 
midclt call disk.query |
  jq -r '.[] | [.name, .serial, .type, .model] | @tsv' |
  awk -F '\t' -v re="$da_regex" '$1 ~ re' |
  while IFS=$'\t' read -r disk_name serial manufacturer part_id; do
    [[ -n "${disk_name:-}" ]] || continue

    payload=$(jq -n \
      --arg device "$DEVICE_ID" \
      --arg name "$disk_name" \
      --arg serial "$serial" \
      --arg part_id "$part_id" \
      --arg manufacturer "$manufacturer" \
      '{device: ($device | tonumber), name: $name, serial: $serial, part_id: $part_id, manufacturer: {name: $manufacturer}}')

    response=$(curl -sS -w '\n%{http_code}' \
      -X POST \
      -H "Authorization: Token $NETBOX_TOKEN" \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json' \
      "$NETBOX_URL/api/dcim/inventory-items/" \
      -d "$payload")

    http_code=$(printf '%s\n' "$response" | tail -n1)
    body=$(printf '%s\n' "$response" | sed '$d')

    if [[ "$http_code" =~ ^2 ]]; then
      echo "Created $disk_name: serial=$serial, manufacturer=$manufacturer, part_id=$part_id"
    else
      echo "Failed to create $disk_name: HTTP $http_code $body" >&2
    fi
  done

Previous Post