0Pricing
Linux Command Line & Bash Scripting Mastery · Lekcja

Skryptowanie zasobów chmurowych za pomocą CLI i jq

Steruj interfejsami CLI dostawców chmurowych w sposób idempotentny i parsuj odpowiedzi JSON, aby tworzyć oraz usuwać zasoby.

Skryptowanie zasobów chmurowych za pomocą CLI i jq to bezpłatna lekcja Linux Command Line & Bash Scripting Mastery na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Linux Command Line & Bash Scripting Mastery, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.

Dlaczego idempotentne skrypty chmurowe mają znaczenie

Na poziomie C2 skryptowanie w chmurze nie polega na klikaniu przycisków — chodzi o pisanie kodu, który można bezpiecznie uruchamiać wielokrotnie, bez tworzenia zduplikowanych zasobów i bez awarii przy drugim uruchomieniu.

Idempotentny skrypt sprawdza, czy zasób już istnieje, zanim go utworzy. To podstawa niezawodnej automatyzacji infrastruktury.

  • Narzędzia CLI chmur (AWS, GCP, Azure) zwracają JSON — analizowanie tych danych wyjściowych jest niezbędne.
  • jq to standardowe narzędzie Unix do wycinania, filtrowania i przekształcania JSON-u w skryptach powłoki.
  • Połączenie CLI, jq i logiki warunkowej pozwala pisać solidne, powtarzalne skrypty aprowizacji.

W tej lekcji, traktując AWS CLI jako punkt odniesienia, utworzą Państwo buckety S3, instancje EC2 i role IAM, korzystając ze wzorców, które można bezpośrednio przenieść do gcloud i az.

Instalowanie i weryfikowanie narzędzi CLI chmury

Przed rozpoczęciem skryptowania należy potwierdzić obecność odpowiednich narzędzi. W CI należy zawsze przypinać wersje, aby uniknąć rozbieżności między środowiskami.

Poniższy fragment sprawdza obecność AWS CLI v2, jq i GCP SDK, instalując tylko brakujące elementy — to wzorzec przydatny w skryptach inicjalizacyjnych dla nowych maszyn wirtualnych lub kontenerów.

#!/usr/bin/env bash
set -euo pipefail

check_or_install() {
  local cmd="$1"
  local install_cmd="$2"
  if ! command -v "$cmd" &>/dev/null; then
    echo "[INFO] $cmd not found — installing..."
    eval "$install_cmd"
  else
    echo "[OK]   $cmd $("$cmd" --version 2>&1 | head -1)"
  fi
}

# AWS CLI v2
check_or_install aws \
  'curl -fsSL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o /tmp/awscliv2.zip && unzip -q /tmp/awscliv2.zip -d /tmp && sudo /tmp/aws/install'

# jq
check_or_install jq \
  'sudo apt-get install -y jq 2>/dev/null || sudo yum install -y jq'

# gcloud (optional)
check_or_install gcloud \
  'echo "Install gcloud SDK manually from https://cloud.google.com/sdk"'

echo "All prerequisites satisfied."

Odpytywanie istniejących zasobów za pomocą jq

Pierwszym krokiem każdego idempotentnego skryptu jest odczyt — należy zapytać API, czy zasób już istnieje, a następnie odpowiednio rozgałęzić logikę.

AWS CLI zawsze zwraca JSON. jq pozwala wyodrębnić dokładnie potrzebne pole:

  • jq -r '.Buckets[].Name' — dane wyjściowe jako surowy ciąg znaków, po jednej nazwie bucketa w wierszu.
  • jq -e — kończy działanie kodem 1, jeśli wyrażenie zwróci null lub false, dzięki czemu idealnie nadaje się do warunków if.
  • jq '.[] | select(.Name == env.BUCKET)' — filtrowanie za pomocą zmiennej powłoki przez env.

Poniższy fragment wyświetla wszystkie buckety S3 i sprawdza, czy docelowy bucket już istnieje.

#!/usr/bin/env bash
set -euo pipefail

BUCKET="my-devops-artifacts-$(date +%Y%m)"

echo "Fetching existing S3 buckets..."
EXISTING=$(aws s3api list-buckets --output json)

# Extract names as newline-separated list
echo "$EXISTING" | jq -r '.Buckets[].Name'

# Check if target bucket exists
if echo "$EXISTING" | jq -e --arg b "$BUCKET" '.Buckets[] | select(.Name == $b)' > /dev/null 2>&1; then
  echo "[EXISTS] Bucket $BUCKET already present — skipping creation."
else
  echo "[MISSING] Bucket $BUCKET not found — will create."
fi

Idempotentne tworzenie bucketa S3

Po dodaniu sprawdzania istnienia należy opakować tworzenie warunkiem. Dobrze zorganizowana funkcja chmurowa stosuje następujący wzorzec:

  1. Odczytaj bieżący stan z API.
  2. Porównaj stan docelowy ze stanem rzeczywistym.
  3. Działaj tylko w przypadku różnicy.

Zwróć uwagę na flagę --create-bucket-configuration — jest wymagana we wszystkich regionach z wyjątkiem us-east-1. Wpisanie regionu na stałe w skrypcie zapobiega niejawnym awariom, gdy AWS_DEFAULT_REGION nie jest ustawiona.

#!/usr/bin/env bash
set -euo pipefail

REGION="eu-west-1"
BUCKET="my-devops-artifacts-$(date +%Y%m)"

ensure_bucket() {
  local bucket="$1"
  local region="$2"

  local existing
  existing=$(aws s3api list-buckets --query 'Buckets[].Name' --output json)

  if echo "$existing" | jq -e --arg b "$bucket" 'index($b) != null' > /dev/null 2>&1; then
    echo "[SKIP] Bucket $bucket already exists."
    return 0
  fi

  echo "[CREATE] Creating bucket $bucket in $region..."
  aws s3api create-bucket \
    --bucket "$bucket" \
    --region "$region" \
    --create-bucket-configuration LocationConstraint="$region"

  # Enable versioning immediately after creation
  aws s3api put-bucket-versioning \
    --bucket "$bucket" \
    --versioning-configuration Status=Enabled

  echo "[DONE] Bucket $bucket created with versioning enabled."
}

ensure_bucket "$BUCKET" "$REGION"

Analizowanie zagnieżdżonego JSON-u: stan instancji EC2

Odpowiedzi EC2 są głęboko zagnieżdżone. Zarówno przechodzenie po ścieżkach za pomocą jq, jak i --query (JMESPath, natywny dla AWS CLI) działają poprawnie — jednak jq jest bardziej elastyczny w przypadku złożonej logiki.

Najważniejsze wzorce jq dla EC2:

  • .Reservations[].Instances[] — spłaszczenie podwójnej struktury tablicowej.
  • select(.State.Name == "running") — filtrowanie według stanu.
  • .Tags[] | select(.Key == "Name") | .Value — wyodrębnienie wartości tagu.

Poniższy fragment wyszukuje działającą instancję na podstawie jej tagu Name i zwraca jej identyfikator oraz prywatny adres IP.

#!/usr/bin/env bash
set -euo pipefail

INSTANCE_NAME="web-server-prod"

RESULT=$(aws ec2 describe-instances \
  --filters \
    "Name=tag:Name,Values=${INSTANCE_NAME}" \
    "Name=instance-state-name,Values=running" \
  --output json)

# Extract instance ID and private IP using jq
INSTANCE_ID=$(echo "$RESULT" | jq -r \
  '.Reservations[].Instances[] | .InstanceId')

PRIVATE_IP=$(echo "$RESULT" | jq -r \
  '.Reservations[].Instances[] | .PrivateIpAddress')

if [[ -z "$INSTANCE_ID" ]]; then
  echo "[WARN] No running instance named '$INSTANCE_NAME' found."
  exit 1
fi

echo "Instance ID : $INSTANCE_ID"
echo "Private IP  : $PRIVATE_IP"

Idempotentne aprowizowanie roli IAM

Zasoby IAM są globalne i nie należy ich duplikować. AWS zwraca konkretny kod błędu — EntityAlreadyExists — gdy próbują Państwo utworzyć rolę, która już istnieje. Przechwycenie tego kodu jest przejrzystszym wzorcem idempotencji niż wstępne sprawdzanie za pomocą wywołania listującego przy pracy z IAM na dużą skalę.

Poniższy skrypt demonstruje:

  • Przechwytywanie kodu wyjścia CLI za pomocą || true, aby zapobiec przerwaniu działania przez set -e.
  • Analizowanie komunikatu o błędzie w formacie JSON, który AWS zapisuje na stderr, za pomocą podstawienia procesu.
  • Dołączanie polityki tylko wtedy, gdy nie jest jeszcze dołączona.
#!/usr/bin/env bash
set -euo pipefail

ROLE_NAME="DevOpsDeployRole"
POLICY_ARN="arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"

TRUST_POLICY='{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}'

# Attempt creation; ignore EntityAlreadyExists
CREATE_OUTPUT=$(aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document "$TRUST_POLICY" \
  --output json 2>&1) || {
  if echo "$CREATE_OUTPUT" | grep -q 'EntityAlreadyExists'; then
    echo "[SKIP] Role $ROLE_NAME already exists."
  else
    echo "[ERROR] Unexpected error: $CREATE_OUTPUT" >&2
    exit 1
  fi
}

# Attach policy (attach-role-policy is idempotent by default)
aws iam attach-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-arn "$POLICY_ARN"

echo "[OK] Role $ROLE_NAME ready with policy $POLICY_ARN."

Zaawansowane jq: transformacje, mapowania i toentries

Rzeczywiste odpowiedzi infrastruktury zawierają dziesiątki pól. Transformacje jq pozwalają przekształcać dane wyjściowe na potrzeby kolejnych narzędzi, logów lub plików konfiguracyjnych.

Najważniejsze zaawansowane wzorce:

  • map(select(...)) — filtrowanie tablicy bez utraty otaczającej ją tablicy.
  • to_entries | map(select(.value != null)) — usunięcie pól o wartości null przed zapisaniem konfiguracji.
  • [.[] | {id: .InstanceId, ip: .PrivateIpAddress}] — przekształcenie danych do nowej struktury.
  • @csv, @tsv, @base64 — wbudowane konwertery formatów.

Poniższy fragment wyodrębnia wszystkie działające instancje i zapisuje plik inwentaryzacyjny TSV.

#!/usr/bin/env bash
set -euo pipefail

OUTPUT_FILE="/tmp/ec2_inventory.tsv"

aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --output json \
| jq -r '
  ["InstanceId", "Name", "PrivateIp", "Type", "AZ"],
  [
    .Reservations[].Instances[] | [
      .InstanceId,
      (.Tags // [] | map(select(.Key == "Name")) | .[0].Value // "(none)"),
      (.PrivateIpAddress // "N/A"),
      .InstanceType,
      .Placement.AvailabilityZone
    ]
  ][]
| @tsv' > "$OUTPUT_FILE"

echo "Inventory written to $OUTPUT_FILE:"
column -t "$OUTPUT_FILE"

Oczekiwanie na operacje asynchroniczne: odpytywanie za pomocą jq

Operacje chmurowe są asynchroniczne. Utworzenie instancji EC2 natychmiast zwraca stan pending. Niezawodne skrypty muszą odpytywać system aż do osiągnięcia oczekiwanego stanu, zanim będą kontynuować działanie.

Poniższy wzorzec wykorzystuje pętlę until z wykładniczym zwiększaniem opóźnienia między próbami. AWS CLI udostępnia również podkomendy wait (np. aws ec2 wait instance-running), ale ręcznie zaimplementowane odpytywanie zapewnia kontrolę nad niestandardowym limitem czasu i bogatsze logowanie.

#!/usr/bin/env bash
set -euo pipefail

INSTANCE_ID="i-0abcdef1234567890"
MAX_WAIT=300   # seconds
INTERVAL=10
ELAPSED=0

echo "Waiting for instance $INSTANCE_ID to reach 'running' state..."

while true; do
  STATE=$(aws ec2 describe-instances \
    --instance-ids "$INSTANCE_ID" \
    --output json \
  | jq -r '.Reservations[0].Instances[0].State.Name')

  echo "  [$(date +%T)] state = $STATE"

  [[ "$STATE" == "running" ]] && break

  if [[ "$STATE" == "terminated" || "$STATE" == "shutting-down" ]]; then
    echo "[FATAL] Instance entered terminal state: $STATE" >&2
    exit 1
  fi

  if (( ELAPSED >= MAX_WAIT )); then
    echo "[TIMEOUT] Instance did not reach 'running' after ${MAX_WAIT}s." >&2
    exit 1
  fi

  sleep "$INTERVAL"
  (( ELAPSED += INTERVAL ))
done

echo "[OK] Instance $INSTANCE_ID is running."

Wzorzec wielochmurowy: odpowiedniki dla GCP i Azure

Wzorzec „odczytaj, a następnie działaj” związany z idempotencją można bezpośrednio przenieść do innych narzędzi CLI chmury. Zarówno gcloud, jak i az zwracają JSON i obsługują filtrowanie:

  • GCP: gcloud ... --format='json' — przekazuj dane potokiem do jq dokładnie tak jak w przypadku AWS. Użyj gcloud ... --quiet, aby wyłączyć monity w skryptach.
  • Azure: az ... --output json — identyczny wzorzec. az group exists zwraca zwykły ciąg znaków z wartością logiczną (true/false), dzięki czemu w prostych przypadkach nie trzeba używać jq.

Fragment pokazuje obok siebie idempotentne tworzenie grupy zasobów w Azure i bucketa GCS w GCP, z użyciem tego samego wzorca warunkowego.

#!/usr/bin/env bash
set -euo pipefail

# --- Azure: idempotent resource group ---
RG="devops-rg"
LOCATION="westeurope"

if [[ $(az group exists --name "$RG") == "true" ]]; then
  echo "[SKIP] Azure resource group $RG already exists."
else
  echo "[CREATE] Creating Azure resource group $RG..."
  az group create --name "$RG" --location "$LOCATION" --output json \
    | jq '{name: .name, location: .location, provisioningState: .properties.provisioningState}'
fi

# --- GCP: idempotent GCS bucket ---
GCS_BUCKET="gs://devops-artifacts-prod"
PROJECT="my-gcp-project"

if gcloud storage buckets describe "$GCS_BUCKET" \
     --project="$PROJECT" --format='value(name)' &>/dev/null; then
  echo "[SKIP] GCS bucket $GCS_BUCKET already exists."
else
  echo "[CREATE] Creating GCS bucket $GCS_BUCKET..."
  gcloud storage buckets create "$GCS_BUCKET" \
    --project="$PROJECT" \
    --location=EU \
    --uniform-bucket-level-access
fi

Usuwanie zasobów: bezpieczna likwidacja

Skrypty usuwające są równie ważne jak skrypty tworzące zasoby. Bezpieczne usuwanie:

  • Wyświetla zasoby przed usunięciem czegokolwiek i drukuje podsumowanie do weryfikacji przez człowieka.
  • Akceptuje flagę --dry-run, aby operatorzy mogli potwierdzić plan bez wykonywania operacji.
  • Usuwa zasoby we właściwej kolejności zależności (np. kończy instancje przed usunięciem grup zabezpieczeń).

Poniższy fragment kończy działanie wszystkich instancji EC2 oznaczonych tagiem Env=staging, korzystając z zabezpieczenia dry-run.

#!/usr/bin/env bash
set -euo pipefail

DRY_RUN="${1:-}"

echo "Finding staging EC2 instances..."

INSTANCE_IDS=$(aws ec2 describe-instances \
  --filters \
    "Name=tag:Env,Values=staging" \
    "Name=instance-state-name,Values=running,stopped" \
  --output json \
| jq -r '[.Reservations[].Instances[].InstanceId] | @sh')

if [[ -z "$INSTANCE_IDS" ]]; then
  echo "[INFO] No staging instances found. Nothing to do."
  exit 0
fi

echo "Instances to terminate: $INSTANCE_IDS"

if [[ "$DRY_RUN" == "--dry-run" ]]; then
  echo "[DRY-RUN] No changes made."
  exit 0
fi

read -rp "Terminate these instances? [yes/N]: " CONFIRM
[[ "$CONFIRM" != "yes" ]] && { echo "Aborted."; exit 0; }

# shellcheck disable=SC2086
aws ec2 terminate-instances --instance-ids $INSTANCE_IDS --output json \
| jq '.TerminatingInstances[] | {id: .InstanceId, state: .CurrentState.Name}'

echo "[DONE] Termination initiated."

Od początku do końca: idempotentny skrypt inicjalizujący infrastrukturę

Połączmy wszystkie elementy: gotowy do użycia na produkcji skrypt inicjalizujący koordynuje tworzenie wielu zasobów we właściwej kolejności, jest w pełni idempotentny i emituje logi strukturalne, które system CI może analizować.

Zaprezentowane najważniejsze praktyki:

  • Logowanie strukturalne za pomocą pomocniczej funkcji log(), która dodaje prefiksy [INFO], [WARN], [ERROR].
  • Plik stanu — zapisuj identyfikatory utworzonych zasobów w pliku stanu JSON, aby kolejne uruchomienia i skrypty usuwające zasoby korzystały z tych samych odwołań.
  • Pułapka błędów — trap przechwytuje nieoczekiwane zakończenia i zgłasza numer wiersza, w którym wystąpił błąd.
#!/usr/bin/env bash
set -euo pipefail

STATE_FILE="/tmp/infra_state.json"
REGION="eu-west-1"
BUCKET="devops-bootstrap-$(date +%Y%m)"
ROLE="BootstrapRole"

log() { echo "[$(date -u +%T)] [$1] ${*:2}"; }
trap 'log ERROR "Script failed at line $LINENO"' ERR

# Initialize state
[[ -f "$STATE_FILE" ]] || echo '{}' > "$STATE_FILE"

# --- Step 1: S3 bucket ---
EXISTING_BUCKETS=$(aws s3api list-buckets --query 'Buckets[].Name' --output json)
if echo "$EXISTING_BUCKETS" | jq -e --arg b "$BUCKET" 'index($b) != null' > /dev/null; then
  log INFO "Bucket $BUCKET exists — skipping."
else
  aws s3api create-bucket --bucket "$BUCKET" --region "$REGION" \
    --create-bucket-configuration LocationConstraint="$REGION" > /dev/null
  log INFO "Bucket $BUCKET created."
fi

# Update state file
jq --arg b "$BUCKET" '.bucket = $b' "$STATE_FILE" > /tmp/_state_tmp && mv /tmp/_state_tmp "$STATE_FILE"

# --- Step 2: IAM role ---
if aws iam get-role --role-name "$ROLE" &>/dev/null; then
  log INFO "Role $ROLE exists — skipping."
else
  aws iam create-role --role-name "$ROLE" \
    --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}' \
    --output json | jq '{RoleName: .Role.RoleName, Arn: .Role.Arn}'
  log INFO "Role $ROLE created."
fi

jq --arg r "$ROLE" '.role = $r' "$STATE_FILE" > /tmp/_state_tmp && mv /tmp/_state_tmp "$STATE_FILE"

log INFO "Bootstrap complete. State: $(cat "$STATE_FILE" | jq -c .)"

Sprawdzenie wiedzy: strażnik idempotencji jq

Sprawdź swoją znajomość idempotentnego skryptowania w chmurze z użyciem jq.

Podsumowanie: skryptowanie zasobów chmurowych za pomocą CLI i jq

W tej lekcji omówiono pełny cykl życia idempotentnej automatyzacji chmurowej z użyciem skryptów powłoki, interfejsów CLI usług chmurowych i jq.

Najważniejsze zasady:

  • Odczyt przed zapisem — zawsze najpierw sprawdzaj istniejący stan; wykonuj działania tylko na różnicach.
  • jq -e jako strażnik — używaj trybu kodu wyjścia, aby sterować gałęziami if na podstawie odpowiedzi JSON.
  • Znaczenie kodów błędów — przechwytuj kody błędów właściwe dla dostawcy, takie jak EntityAlreadyExists, zamiast wcześniej wyświetlać listę zasobów, gdy takie rozwiązanie jest wydajniejsze.
  • Odpytywanie o stan asynchroniczny — używaj pętli until z limitami czasu; nigdy nie zakładaj, że zasób jest gotowy natychmiast po utworzeniu.
  • Pliki stanu — zapisuj identyfikatory zasobów we wspólnym pliku JSON, aby każda faza skryptu i proces usuwania zasobów korzystały z tych samych odwołań.
  • Flagi dry-run — zawsze obsługuj --dry-run, aby operator mógł bezpiecznie przejrzeć plan działania przed wykonaniem destrukcyjnych operacji.

Wzorce te — w połączeniu ze ścisłym nagłówkiem set -euo pipefail i pułapką ERR — stanowią podstawę gotowego do użycia na produkcji skryptowania infrastruktury na poziomie C2.

Często zadawane pytania

Czy lekcja „Skryptowanie zasobów chmurowych za pomocą CLI i jq” jest bezpłatna?

Tak — pełny tekst „Skryptowanie zasobów chmurowych za pomocą CLI i jq” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Linux Command Line & Bash Scripting Mastery, przejdź na CoddyKit PRO. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.

Co nauczysz się w „Skryptowanie zasobów chmurowych za pomocą CLI i jq”?

Steruj interfejsami CLI dostawców chmurowych w sposób idempotentny i parsuj odpowiedzi JSON, aby tworzyć oraz usuwać zasoby. Ćwiczysz Linux Command Line & Bash Scripting Mastery z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Linux Command Line & Bash Scripting Mastery?

Nie wymagamy żadnego doświadczenia. Linux Command Line & Bash Scripting Mastery w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Skryptowanie zasobów chmurowych za pomocą CLI i jq”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Linux Command Line & Bash Scripting Mastery?

Tak. Każda lekcja Linux Command Line & Bash Scripting Mastery zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Odchudzone Dockerfile i punkty wejścia powłoki
  2. Szablony konfiguracji za pomocą envsubst i heredoc
  3. Skryptowanie zasobów chmurowych za pomocą CLI i jq
  4. sondy kondycji, bramki gotowości i pętle oczekiwania
← Powrót do Linux Command Line & Bash Scripting Mastery