Короткий ответ
Нужно заменить безусловное создание ресурса проверкой его текущего состояния ("создать, только если ресурс отсутствует" или "привести к желаемой конфигурации, если он уже есть") и не считать ошибкой ситуацию, когда ресурс уже существует в нужном виде; дополнительно стоит явно обработать гонку, если два запуска скрипта стартуют почти одновременно.
Как это работает подробнее
большинство CLI облачных провайдеров и API возвращают специфичный код ошибки при попытке создать уже существующий ресурс (например, BucketAlreadyOwnedByYou или HTTP 409 Conflict); идемпотентный скрипт либо сначала проверяет наличие ресурса отдельным вызовом (describe/get), либо перехватывает именно этот код ошибки и трактует его как успех, а не как сбой.
Пример кода
example-482.sh
#!/usr/bin/env bash
set -euo pipefail
BUCKET_NAME="myco-reports-prod"
if aws s3api head-bucket --bucket "$BUCKET_NAME" 2>/dev/null; then
echo "Бакет $BUCKET_NAME уже существует, пропускаем создание"
else
echo "Создаём бакет $BUCKET_NAME"
aws s3api create-bucket --bucket "$BUCKET_NAME" --region us-east-1
fiЧто использовать на практике
- Рекомендация. для сложных ресурсов (не только "существует/не существует", но и с настройками) правильнее декларативно описать желаемое состояние и использовать инструменты, которые сами реализуют идемпотентность (Terraform, Ansible, облачные SDK с
create_or_update), а не писать логику сверки состояния вручную в bash. Если вручную — важно отличать "ресурс уже существует и он правильный" от "ресурс существует, но с другими настройками", и решить, нужно ли приводить его к нужному состоянию.
Подводные камни
- Ошибка. просто оборачивать команду в
|| true, подавляя вообще любую ошибку, включая реальные проблемы (неверные права доступа, исчерпанная квота); проверять существование ресурса одной командой, а создавать другой, не обрабатывая гонку между двумя параллельными запусками скрипта (TOCTOU — time-of-check to time-of-use).