Grep || true или полезное очковтирательство
Столкнулся недавно с неприятным поведением одного из своих Jenkins-пайплайнов. А именно, при пуше docker-образа в реестр, если такой тэг там уже есть, весь пайп вылетает с ошибкой.
Сама по себе, реакция docker понятна и логична - такой тэг в реестре есть, ничего перезаписывать не буду. Другой вопрос, что весь пайп на этом шаге валится в итоге.
Решил я дописать в него проверку - чтобы перед пушем он запрашивал список всех тегов и искал в нем загружаемый. Если не находит - продолжает пуш. В противном случае, пропускает этот шаг и двигается по пайпу дальше.
Сначала, попробовал реализовать задуманное на родном для Jenkins (но не для меня) Groovy. Состряпал функцию. Покрутил ее, повертел.. Вроде даже придумал, каким образов в декларативный свой пайп вкорячить. В итоге, парсер пайпа при запуске даже перестал ругаться. Но при самой проверке все молча падало. Кодер из меня такой себе, откровенно говоря.
Посмотрел я на всю громоздкость этой конструкции и решил переделать все на родном bash+jq. По моим прикидкам, выйти должно было изящнее и проще для понимания. Написал такую конструкцию (в bash я пока что еще умею писать без ИИ-ассистентов): isTagExists=$(curl <ключи-и-адрес-реестра> | jq -r '.tags[]' | grep <искомый-тэг>)
❓Для тех, кому лень разбираться в командной строке поясню логику: когда мы запрашиваем curl'ом лист тегов в реестре, он приходит к нам в виде списка, упакованного в json. Чтобы из этого json достать только значения поля 'tags', мы перенаправлеяем весь вывод на jq, который и вытаскивает, что нам нужно. Но так как нам нужен один единственный тэг, мы только-что полученный список тэгов отправляем, в свою очередь в grep. Он то и отделяет зерна от плевел. На выходе получается два варианта: либо переменная isTagExists остается пустой (если grep ничего не нашел), либо с именем существующего в реестре тэга.
А дальше делаем стандартный if: if [ -n "$isTagExists" ]; then echo "Tag exists! Skipping" else echo "Tag does not exist. Push it!" docker push fi
На первый взгляд, все выглядит красиво. Конструкция элементарная - и все должно работать. И оно даже работает, если тестировать в нативном окружении bash - в каком-нибудь из линуксов.
Однако, пайплайн продолжал валиться, уже на самом операторе if. При этом, не выдавал никаких ошибок, кроме стандартной - "Script exit with code 1". Ну, то есть, по какой-то причине наша конструкция выдавала не 0 (успешное завершение операции), но 1 ( все, что не 0 - то не успех).
Я его и так крутил, и этак. Ну не работает! И разработка уже в затылок дышит - им нужно свежую сборку катить в тестовую среду. А я тут весь конвейер поломал.
Все оказалось до банальности просто - grep, если не находит ничего, возвращает код 1! Это не очевидно - в консоль он ничего при этом не выводит. Поэтому, в линуксе все и отрабатывает, как кажется, успешно. Но Jenkins эту единицу видит и останавливает дальнейшее выполнение.
В нашем случае, вышло так, что если grep находил тэг в реестре, исходное условие в операторе if выполнялось, и все дальше шло как по маслу. Но если нужно было загрузить новый образ, grep ничего не находил и возвращал пустую строку, вместе с кодом 1. На этом все и заканчивалось.
Для решения данной проблемы есть несколько вариантов - от отказа от grep, с перекладыванием сортировки на jq, до радикального "set +e" для игнорирования ошибок. Я выбрал компромиссный и, в данном случае, самый простой вариант - попросил grep всегда возвращать "успех", так как в нашей ситуации, нам эти коды выхода не важны. Выглядит эта просьба в добавлении после grep команды "|| true": isTagExists=$(curl <ключи-и-адрес-реестра> | jq -r '.tags[]' | grep <искомый-тэг> || true)
✅Теперь, либо grep находит что-то и докладывает об успехе. Либо ничего не находит (что нас тоже устраивает) и тоже докладывает об успехе. Вот такое очковтирательство, но с пользой для дела.