DevOps-bootcamp · leksjon

Skripting av skyressurser med CLI og jq

Styr CLI-er for skyleverandører idempotent, og analyser JSON-svar for å opprette og fjerne ressurser.

Leksjon 3 av 413 trinn

Skripting av skyressurser med CLI og jq er en gratis leksjon i DevOps-bootcamp på CoddyKit. Dette er leksjon 3 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i DevOps-bootcamp, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i DevOps-bootcamp inneholder totalt 4 leksjoner.

Hvorfor idempotent skripting i skyen er viktig

På C2-nivå handler skripting i skyen ikke om å klikke på knapper — det handler om å skrive kode som kan kjøres trygt flere ganger uten å opprette dupliserte ressurser eller mislykkes ved andre kjøring.

Et idempotent skript kontrollerer om en ressurs allerede finnes, før den opprettes. Dette er grunnlaget for pålitelig infrastrukturautomatisering.

  • Cloud CLI-er (AWS, GCP, Azure) returnerer JSON — det er avgjørende å analysere disse utdataene.
  • jq er standardverktøyet i Unix for å hente ut, filtrere og transformere JSON fra skallskript.
  • Ved å kombinere CLI, jq og betinget logikk kan De skrive robuste og repeterbare klargjøringsskript.

Gjennom denne leksjonen skal De klargjøre S3-buckets, EC2-instanser og IAM-roller ved hjelp av AWS CLI som referanse, med mønstre som kan overføres direkte til gcloud og az.

Installere og kontrollere cloud-CLI-er

Før skripting må De kontrollere at de riktige verktøyene er tilgjengelige. Bruk alltid fastlåste versjoner i CI for å unngå avvik mellom miljøer.

Kodeutdraget nedenfor kontrollerer AWS CLI v2, jq og GCP SDK, og installerer bare det som mangler — et mønster som er nyttig i bootstrap-skript for nye VM-er eller containere.

#!/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."

Spørre etter eksisterende ressurser med jq

Det første steget i ethvert idempotent skript er en lesing — spør API-et om ressursen allerede finnes, og velg deretter riktig gren.

AWS CLI returnerer alltid JSON. jq lar Dem hente ut nøyaktig feltet De trenger:

  • jq -r '.Buckets[].Name' — rå strengutdata, ett bucket-navn per linje.
  • jq -e — avslutter med kode 1 hvis uttrykket gir null eller false, noe som gjør det ideelt for if-betingelser.
  • jq '.[] | select(.Name == env.BUCKET)' — filtrer ved hjelp av en skallvariabel gjennom env.

Kodeutdraget nedenfor viser alle S3-buckets og kontrollerer om en mål-bucket allerede finnes.

#!/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

Idempotent opprettelse av S3-bucket

Når kontrollen av om ressursen finnes, er på plass, pakker De opprettelsen inn i en betingelse. En godt strukturert cloud-funksjon følger dette mønsteret:

  1. Les gjeldende tilstand fra API-et.
  2. Sammenlign ønsket tilstand med faktisk tilstand.
  3. Utfør bare endringer som følger av avviket.

Legg merke til flagget --create-bucket-configuration — det er nødvendig i alle regioner unntatt us-east-1. Ved å hardkode regionen i skriptet unngås stille feil når AWS_DEFAULT_REGION ikke er satt.

#!/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"

Analysere nestet JSON: EC2-instansens tilstand

EC2-svar er dypt nestet. Både sti-traversering med jq og --query (JMESPath, innebygd i AWS CLI) fungerer — men jq er kraftigere for kompleks logikk.

Viktige jq-mønstre for EC2:

  • .Reservations[].Instances[] — flat ut den doble array-strukturen.
  • select(.State.Name == "running") — filtrer etter tilstand.
  • .Tags[] | select(.Key == "Name") | .Value — hent ut en tag-verdi.

Kodeutdraget finner en kjørende instans ut fra Name-taggen og returnerer ID-en og den private IP-adressen.

#!/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"

Idempotent klargjøring av IAM-roller

IAM-ressurser er globale og må ikke dupliseres. AWS returnerer en bestemt feilkode — EntityAlreadyExists — når De prøver å opprette en rolle som allerede finnes. Å håndtere denne koden er et ryddigere idempotensmønster enn et forhåndskall for å liste ressurser når IAM brukes i stor skala.

Skriptet nedenfor viser:

  • Hvordan CLI-ens avslutningskode fanges opp med || true, slik at set -e ikke avbryter skriptet.
  • Hvordan feilmeldingen i JSON som AWS skriver til stderr, analyseres ved hjelp av prosessubstitusjon.
  • Hvordan en policy bare knyttes til rollen hvis den ikke allerede er knyttet til den.
#!/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."

Avansert jq: Transformasjoner, map og toentries

Reelle infrastruktursvar inneholder dusinvis av felt. Transformasjoner med jq lar Dem omforme utdata for etterfølgende verktøy, logger eller konfigurasjonsfiler.

Viktige avanserte mønstre:

  • map(select(...)) — filtrer en array uten å miste array-innpakningen.
  • to_entries | map(select(.value != null)) — fjern null-felt før skriving til en konfigurasjon.
  • [.[] | {id: .InstanceId, ip: .PrivateIpAddress}] — projiser til en ny struktur.
  • @csv, @tsv, @base64 — innebygde formatkonverterere.

Kodeutdraget nedenfor henter ut alle kjørende instanser og skriver en TSV-inventarfil.

#!/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"

Vente på asynkrone operasjoner: Polling med jq

Skyoperasjoner er asynkrone. Når en EC2-instans opprettes, returneres resultatet umiddelbart med tilstanden pending. Pålitelige skript må polle til ønsket tilstand er nådd før de fortsetter.

Mønsteret nedenfor bruker en until-løkke med eksponentiell backoff. AWS CLI tilbyr også wait-underkommandoer (for eksempel aws ec2 wait instance-running), men egen polling gir Dem kontroll over tidsavbrudd og mer detaljert logging.

#!/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."

Multi-cloud-mønster: Tilsvarende løsninger for GCP og Azure

Les-og-handle-mønsteret for idempotens kan overføres direkte til andre cloud-CLI-er. Både gcloud og az returnerer JSON og støtter filtrering:

  • GCP: gcloud ... --format='json' — send utdataene til jq på nøyaktig samme måte som med AWS. Bruk gcloud ... --quiet for å undertrykke spørsmål i skript.
  • Azure: az ... --output json — samme mønster. az group exists returnerer en ren boolsk streng (true/false), slik at jq ikke trengs i enkle tilfeller.

Kodeutdraget viser idempotent opprettelse av ressursgrupper i Azure og GCS-buckets i GCP side om side, ved hjelp av det samme betingelsesmønsteret.

#!/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

Nedmontering: Sikker destruksjon av ressurser

Skript for destruksjon er like viktige som skript for opprettelse. En sikker nedmontering:

  • Viser ressurser før noe slettes og skriver ut et sammendrag for menneskelig gjennomgang.
  • Godtar flagget --dry-run, slik at operatører kan bekrefte planen uten å utføre den.
  • Sletter i riktig avhengighetsrekkefølge (for eksempel avslutte instanser før sikkerhetsgrupper slettes).

Kodeutdraget nedenfor avslutter alle EC2-instanser med taggen Env=staging med en dry-run-betingelse.

#!/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."

Ende-til-ende: Idempotent oppstartsskript for infrastruktur

Når alt settes sammen: Et oppstartsskript for infrastruktur på produksjonsnivå orkestrerer flere ressurser i riktig rekkefølge, er fullstendig idempotent og genererer strukturerte logger som et CI-system kan analysere.

Viktige fremgangsmåter som demonstreres:

  • Strukturert logging via en log()-hjelpefunksjon som setter [INFO], [WARN] eller [ERROR] foran hver melding.
  • Tilstandsfil — skriv ID-ene til opprettede ressurser til en JSON-tilstandsfil, slik at etterfølgende kjøringer og nedmonteringsskript kan dele de samme referansene.
  • Feilfanging — trap fanger opp uventede avslutninger og rapporterer linjenummeret der feilen oppsto.
#!/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 .)"

Kunnskapssjekk: jq-vakt for idempotens

Test forståelsen Deres av idempotent skripting i skyen med jq.

Oppsummering: Skripting av skyressurser med CLI og jq

Denne leksjonen dekket hele livssyklusen for idempotent skyautomatisering ved hjelp av shell-skript, sky-CLI-er og jq.

Grunnprinsipper:

  • Les før skriving — spør alltid etter eksisterende tilstand først, og utfør bare handlinger for differansen.
  • jq -e for vakter — bruk modus for avslutningsstatus til å styre if-grener basert på JSON-svar.
  • Semantikk for feilkoder — fang leverandørspesifikke feilkoder, for eksempel EntityAlreadyExists, i stedet for å liste opp ressurser på forhånd når det er mer effektivt.
  • Avvent asynkron tilstand — bruk until-løkker med tidsavbrudd, og anta aldri at en ressurs er klar umiddelbart etter opprettelse.
  • Tilstandsfiler — skriv ressurs-ID-er til en delt JSON-fil, slik at hver fase av skriptet og nedmonteringen deler de samme referansene.
  • Flagg for prøvekjøring — støtt alltid --dry-run, slik at operatøren trygt kan gjennomgå handlingene før destruktive operasjoner utføres.

Disse mønstrene — kombinert med en streng set -euo pipefail-header og en ERR-felle — danner grunnlaget for skripting av infrastruktur på produksjonsnivå på C2-nivå.

Gratis å komme i gang

Lær deg DevOps-bootcamp med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
142
Leksjoner
568

Ofte stilte spørsmål

Er leksjonen «Skripting av skyressurser med CLI og jq» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien DevOps-bootcamp, inkludert «Skripting av skyressurser med CLI og jq», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i DevOps-bootcamp inneholder totalt 4 leksjoner.

Hva lærer jeg i «Skripting av skyressurser med CLI og jq»?

Styr CLI-er for skyleverandører idempotent, og analyser JSON-svar for å opprette og fjerne ressurser. Du øver på DevOps-bootcamp med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med DevOps-bootcamp?

Ingen tidligere erfaring er nødvendig. DevOps-bootcamp på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.

Hvor lang tid tar leksjonen «Skripting av skyressurser med CLI og jq»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne DevOps-bootcamp-leksjonen?

Ja. Alle DevOps-bootcamp-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Slanke Dockerfiles og shell-entrypoint-er
  2. Maler for konfigurasjoner med envsubst og heredoc-er
  3. Skripting av skyressurser med CLI og jq
  4. Helsetester, readiness-sperrer og venteløkker
← Tilbake til DevOps-bootcamp