March 5

Нули

На планшете для работы умер кулер. Приплыли.

Ну, не совсем всё умерло - он включается, нагревается и троттлит 100% времени.
Windows-планшет, на котором жила вся моя рабочая среда: куб контексты, профили AWS и Azure, скрипты, IDEs. Да всё. Всё было там.

Надо быстро восстановить рабочее место.

Причём в изолированное окружение, чтобы ничего, связанного с работой не перемешалось с личным и не было лишних утечек.
Поднял виртуальную машину с Windows на Mac (да, я привык к Windows-среде, не осуждайте 🙃), начал по памяти восстанавливать инструменты.


Благо есть asdf с .tool-versions - всё задокументировано, стоишь раз за разом одни и те же версии. Скопировал файл, запустил инсталляцию.

Поставилось автоматически:

  • kubectl, helm, helmfile, terragrunt, terraform
  • awscli, azure-cli, kubelogin
  • jq, yq, k9s, kubectx

Дохера всего, около 45 утилит.

Дальше надо запустить скрипты - клонировать весь GitLab компании разом, настроить контексты кластеров всех облаков и профили облаков. Всё это у меня было. Всё это я написал раньше. Надо просто запустить.

Запускаю скрипт клонирования GitLab.
Завис. Минута. Две. Пять.
Окей, Ctrl+C, думаю. Что-то с сетью? Или токен протух?


Иду в GitLab UI - токен живой. Проверяю вручную:

curl -s -H "PRIVATE-TOKEN: $TOKEN" "https://gl.company.com/api/v4/user"

Всё нормально, вернул мой профиль, HTTP 200. Хорошо.
Значит не токен.

Меняю токен на новый - на всякий случай. Запускаю скрипт. Снова завис.

Прошу нейронку помочь.
Та начинает советовать добавить таймаут в curl, добавить проверки пагинации, переписать функции...
Делаю всё это. Всё равно зависает или выдаёт нули:
Хотя вручную curl отдаёт нормальные данные.
Ни один совет нейронки не помог ни в чем.

Добавляю отладочный вывод.
Оборачиваю запрос в файл, читаю из файла, убираю BOM, убираю нулевые байты, убираю CR...
Нули.

Пишу отдельный дебаг-скрипт с пошаговыми запросами.
Каждый шаг - отдельный curl, каждый ответ - в переменную, потом проверяем
jq -e 'type == "array"'...

И тут вижу странное:
API error на /groups page=1:

[{"id":10,"web_url":"https://gl.company.com/groups/all","name":"All"...

Что.

Стоп. Там же чётко написано [{"id":10... - это массив.
Это валидный, сука, JSON. Ты чо, пёс.
Но проверка говорит "не массив".
Проверяю сам:

echo '[]' | jq -e 'type == "array"'; echo "exit: $?"
exit: 1

Чтоооо.

echo '[1,2,3]' | jq -e 'type == "array"'; echo "exit: $?"
exit: 1

ЧТО.

jq возвращает 1 для валидного массива. На вопрос "это массив?" jq отвечает "нет".

Ладно, решаю - пофиг на GitLab, пофиг на jq, склоню вручную потом.
Сначала сделаю рабочую среду.

Иду настраивать kubectl-контексты. Запускаю скрипт - не работает.
Иду в AWS, пробую aws eks update-kubeconfig - зависает.
Пробую вручную aws sts get-caller-identity - зависает на несколько секунд, потом отрабатывает.
Что-то медленное и странное.

Пробую aws s3 ls - работает, но медленно.
Иду в Azure - az account list отрабатывает, но вывод странный. Где-то что-то парсится не так.

Начинаю замечать паттерн:

всё, что связано с обработкой JSON в CLI-инструментах, либо зависает, либо даёт неверный результат. awscli внутри гоняет Python и boto3, там свой парсер.
jq - отдельный бинарник.

Стоп. Какого лешего тут происходит.

Как мне может ВСЁ поломать всего-лишь одна утилита?????

Пойдём смотреть тебя.

which jq
/home/alexk/.asdf/shims/jq
file /home/alexk/.asdf/shims/jq
/home/alexk/.asdf/shims/jq: Bourne-Again shell script, 
ASCII text executable


Шим asdf. Смотрю куда он ведёт:

ls /home/alexk/.asdf/installs/jq/
1.8.1
file /home/alexk/.asdf/installs/jq/1.8.1/bin/jq
ELF 64-bit LSB executable, x86-64

От сука.


А теперь проверяем ещё раз для наглядности:

uname -m
aarch64


Вот оно.
Система - ARM64. Бинарник jq - x86-64.

WSL2 на Windows ARM (в моём случае - Windows виртуалка на Mac с чипом Apple Silicon, WSL2 внутри неё) умеет запускать x86-64 бинарники через Microsoft Prism - встроенный эмулятор.
Запускает. Но нестабильно. Базовые операции типа jq '.field' или jq '.[]' работают.
А вот более сложные выражения, типа jq -e 'type == "array"' или даже jq --version - падают или зависают.

Именно поэтому:

  • скрипт клонирования GitLab зависал на первой же функции, которая проверяла тип ответа через jq
  • echo '[]' | jq -e 'type == "array"' возвращал 1 вместо 0
  • счётчики были нулями - jq тихо ломался при подсчёте длины массива
  • всё, что просто парсило JSON вручную через awscli/azure-cli, работало через собственные парсеры


Короче все скрипты подготовки среды на новом рабочем железе.

Понятно что сломалось.
Непонятно почему asdf поставил неправильный бинарник.

Лезу в плагин:

cat ~/.asdf/plugins/jq/bin/download


Нахожу функцию определения архитектуры:

get_arch(){
  declare arch="$(uname -m)"
  if [ "$arch" == 'x86_64' ]; then
    echo '64'
  elif [ "$arch" == 'aarch64' ]; then
    echo '64'     # <--- вот оно
  elif [ "$arch" == 'arm64' ]; then
    echo '64'     # <--- и вот
  elif [ "$arch" == 'i386' ]; then
    echo '32'
  ...
}


И чуть ниже - как формируется имя файла для скачивания:

guessed_file="jq-linux$arch"


То есть: aarch64 -> get_arch() возвращает '64' -> скачивается jq-linux64 - это x86-64 бинарник.

Самое смешное - в том же файле есть функция guess_download_url(), которая это правильно обрабатывает:

guess_download_url() {
  ...
  if [ "$arch" == 'aarch64' ]; then
    arch="arm64"
  fi
  ...
}

Правильная логика есть. Но эта функция - мёртвый код.
Нигде не вызывается. Кто-то написал, не подключил и забыл.
А в download() используется старая get_arch(), которая для любой 64-битной архитектуры, включая ARM, выдаёт одинаковое '64'.

А на GitHub Releases у jq 1.7.1+ есть отдельный jq-linux-arm64.
Он существует.
Просто плагин про него не знает.


Правлю плагин (PR с фиксом позже сделаю в плагин):

было:

elif [ "$arch" == 'aarch64' ]; then
    echo '64'
elif [ "$arch" == 'arm64' ]; then
    echo '64'

стало:

elif [ "$arch" == 'aarch64' ]; then
    echo 'arm64'
elif [ "$arch" == 'arm64' ]; then
    echo 'arm64'

И имя файла:

было:

guessed_file="jq-linux$arch"

стало:

case "$arch" in
  64)    guessed_file="jq-linux64" ;;
  32)    guessed_file="jq-linux32" ;;
  arm64) guessed_file="jq-linux-arm64" ;;
  *)     guessed_file="jq-linux-$arch" ;;
esac


Переустанавливаю:

asdf uninstall jq 1.8.1
asdf install jq 1.7.1

file ~/.asdf/installs/jq/1.7.1/bin/jq
ELF 64-bit LSB executable, ARM aarch64 ✅

echo '[]' | jq -e 'type == "array"'; echo "exit: $?"
exit: 0 ✅

Запускаю скрипт клонирования и контексты всех куберентисов.
Всё работает. Красота.


Итоги
- планшет умер, виртуалка поднялась, среда восстановлена. Полдня потрачено.
- из них часа два - на отладку того, что казалось сломанным скриптом или токеном.
- причина: jq x86-64 на aarch64-системе работает через эмуляцию, частично. Простые операции - норм. Чуть сложнее - всё, привет нулям и зависаниям.
- баг в asdf-плагине для jq: get_arch() возвращает 64 для любой 64-битной архитектуры, ARM в том числе. В итоге всегда скачивается jq-linux64 (x86-64).
- правильная логика в том же файле есть - в мёртвой функции guess_download_url(), которая никогда не вызывается.

Нули.