Mert Özen Her Satırda Daha İleri
Geliştirici Araçları JSON, YAML ve Configuration Formatları — #02

YAML 1: Syntax, Tipler ve "Norway Problem": Bir YAML Dosyasını Doğru Okumak

Mert Özen 15 Kas 2026 13 dk 5 görüntülenme
YAML 1: Syntax, Tipler ve "Norway Problem": Bir YAML Dosyasını Doğru Okumak

Tırnaksız bir YAML değerinin tipini parser'ın uyguladığı kural seti belirler: YAML 1.1'de NO false, 12:30 sayı, 0755 sekizlik; YAML 1.2'de üçü de farklı. PyYAML, ruamel.yaml, js-yaml, YamlDotNet, yq, Docker Compose ve kubectl'i aynı dosyayla çalıştırıp Bookstore'un compose.yaml ve Kubernetes manifest'indeki üç gerçek tip hatasını gösteriyoruz.

Sürüm notu: Bu yazıdaki örnekler PyYAML 6.0.3, ruamel.yaml 0.19.1, js-yaml 5.5.0, yaml 2.9.1, YamlDotNet 18.1.0 (.NET 10), yq 4.54.3, Docker Compose 5.1, kubectl 1.37 (yerel bir kind cluster'ına karşı) ve kubeconform 0.8.0 ile çalıştırılmıştır. Spec olarak 1.2.2 (2021) ve karşılaştırma için 1.1 (2005) esas alınmıştır.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) kargo ayarlarını YAML (YAML Ain't Markup Language; girintiye dayalı, elle yazılmaya uygun veri formatı) ile yazdık. Hiçbir değer tırnak içinde değil:

# Bookstore kargo ayarları
shipping:
  countries: [TR, DE, NO]
  cutoff: 12:30
  express: yes
  giftWrap: on
  labelFileMode: 0755
  maxWeightGrams: 1e3
  apiVersion: 1.10
  note: ~

Bu dosyayı Python'un en yaygın YAML kütüphanesi PyYAML ile okursan countries listesindeki üçüncü eleman ne olur? cutoff hâlâ "12:30" mu? labelFileMode 755 mi, 493 mü? Aynı dosyayı yq, Docker Compose veya kubectl okuduğunda cevaplar değişir mi? Tahminini not et; cevaplar dördüncü bölümde.

Mapping, Sequence ve Scalar

YAML'ın veri modeli JSON'unkiyle aynı üç yapıdan oluşur: mapping (key-value çiftleri, JSON'daki object), sequence (sıralı liste, JSON'daki array) ve scalar (tek bir değer: string, sayı, boolean, null). Fark yazılışta. Block style'da yapıyı girinti belirler: key: value bir mapping girdisi, satır başındaki - bir sequence item'ı. Flow style'da ise JSON'a benzer şekilde { } ve [ ] kullanılır ve girinti önemsizdir. Bir dosyada ikisini karıştırabilirsin; yukarıdaki countries: [TR, DE, NO] block style bir mapping içinde flow style bir sequence.

02-three-notations-tr.png

Görseldeki üç dosyayı yq ile JSON'a çevirince aynı sonucu alıyoruz:

$ cat basics-flow.yaml
{shipping: {
  countries:
    [TR, DE, "NO"],
  express: true,
  cutoff: "12:30"}}
$ for f in basics.yaml basics-flow.yaml basics.json; do yq -p yaml -o json -I0 "$f"; done
{"shipping":{"countries":["TR","DE","NO"],"express":true,"cutoff":"12:30"}}
{"shipping":{"countries":["TR","DE","NO"],"express":true,"cutoff":"12:30"}}
{"shipping":{"countries":["TR","DE","NO"],"express":true,"cutoff":"12:30"}}

Girinti kuralı katı: kaç boşluk kullandığın serbest ama aynı seviyedeki her şey aynı sütunda başlamalı ve tab karakteri girintide kullanılamaz. Spec bunu taşınabilirlik gerekçesiyle yasaklıyor, çünkü her editör tab'ı farklı genişlikte gösterir. Parser'lar da bunu açıkça söylüyor:

$ printf 'shipping:\n\tcountries: [TR]\n' > tab.yaml
$ python -c 'import yaml; yaml.safe_load(open("tab.yaml"))' 2>&1 | tail -2
found character '\t' that cannot start any token
  in "tab.yaml", line 2, column 1
$ yq . tab.yaml
Error: bad file 'tab.yaml': go-yaml load error in scanner (while scanning for the next token) at L2.C1: found character that cannot start any token

Üçüncü nokta, YAML 1.2'nin ana hedeflerinden birinin JSON'un üst kümesi olmaktı. Pratikte her geçerli JSON dosyası bir YAML 1.2 dokümanıdır, bu yüzden appsettings.json'ı yq ile okuyabilirsin. Ama "geçerli" kelimesi önemli: serinin ilk yazısındaki, aynı key'i iki kez içeren order.json'ı verdiğimizde üç YAML aracı üç farklı şey yaptı:

$ python -c 'import yaml; print(yaml.safe_load(open("../01-json/order.json")))'
{'orderId': 9007199254740993, 'status': 'Cancelled', 'total': 24.9}
$ yq -o json -I0 ../01-json/order.json
{"orderId":9007199254740992,"status":"Paid","total":24.9,"status":"Cancelled"}
$ python -c 'from ruamel.yaml import YAML; YAML(typ="safe", pure=True).load(open("../01-json/order.json"))' 2>&1 | grep -A3 DuplicateKeyError
    raise DuplicateKeyError(*args)
ruamel.yaml.constructor.DuplicateKeyError: while constructing a mapping
  in "../01-json/order.json", line 1, column 1
found duplicate key "status" with value "Cancelled" (original value: "Paid")
  in "../01-json/order.json", line 5, column 3

YAML spec'i mapping key'lerinin benzersiz olmasını şart koşuyor; ruamel.yaml bu yüzden hata veriyor. PyYAML JSON parser'ları gibi sonuncuyu alıyor. yq ise iki key'i de koruyup çıktıya geri yazıyor, ayrıca büyük ID'yi YAML sayısı olarak okurken yuvarlıyor. JSON'u YAML aracıyla işlemek çoğu zaman sorunsuzdur ama bu tür uç durumlarda araçların davranışı aynı değil.

Implicit Typing: YAML 1.1 ile 1.2 Arasındaki Fark

JSON'da tip, değerin yazılışından bellidir: tırnaklıysa string, değilse sayı, boolean veya null. YAML'da tırnaksız bir scalar'ın tipini parser, bir tag resolution tablosuna bakarak tahmin eder. Sorun, bu tablonun YAML 1.1 ve 1.2'de farklı olması. 2005 tarihli YAML 1.1'de y, yes, on, no, off ve bunların büyük harfli hâlleri boolean; 0 ile başlayan sayılar sekizlik tabanda; 12:30 gibi değerler 60 tabanında (sexagesimal) sayı. 2009'da yayımlanan YAML 1.2'nin core schema'sı ise boolean olarak yalnızca true ve false'u (ve büyük harfli yazımlarını) tanıyor, sekizlik sayı için 0o öneki istiyor ve sexagesimal'i tamamen kaldırıyor. Ekim 2021'de yayımlanan 1.2.2 yalnızca bir düzeltme revizyonu; spec'in kendisine göre 1.2'ye göre normatif bir değişiklik yok.

Tırnaksız değerYAML 1.1YAML 1.2 core schema
NO, no, offboolean falsestring
yes, on, yboolean truestring
12:30sayı: 12 × 60 + 30 = 750string
0755sekizlik sayı: 493onluk sayı: 755
1e3string (1.1'de float için nokta şart)float: 1000
1.10float: 1.1float: 1.1
~, boş değernullnull

Son iki satır önemli: 1.10 iki sürümde de string değil, float. Bir sürüm numarasını tırnaksız yazdığında her parser onu 1.1'e çevirir. "Norway problem" adı ilk satırdan geliyor: ülke listesine Norveç'in ISO (Uluslararası Standardizasyon Örgütü) ülke kodu NO eklendiğinde YAML 1.1 parser'ı onu false olarak okuyor.

Hangi Araç Hangi YAML'ı Konuşuyor?

Teori bir yana, asıl soru elindeki aracın hangi tabloyu kullandığı. Tahmindeki dosyayı PyYAML ve ruamel.yaml ile okuyan script (paketleri bir virtual environment'a pip install -r requirements.txt ile kur; dosya repo'nun kökünde):

import json
import sys

import yaml
from ruamel.yaml import YAML

path = sys.argv[1]
with open(path, encoding="utf-8") as f:
    text = f.read()

# PyYAML, YAML 1.1'i uygular
print("PyYAML 6.0.3   ", json.dumps(yaml.safe_load(text), default=str))
# ruamel.yaml varsayılan olarak YAML 1.2'yi uygular
print("ruamel.yaml    ", json.dumps(YAML(typ="safe", pure=True).load(text), default=str))

Aynı dosyayı JavaScript'in iki yaygın kütüphanesiyle (parse.mjs), .NET'te YamlDotNet ile (parse.cs) ve yq ile de okuyoruz. Node script'i için klasörde önce npm install çalıştır:

$ python parse.py shipping.yaml
PyYAML 6.0.3    {"shipping": {"countries": ["TR", "DE", false], "cutoff": 750, "express": true, "giftWrap": true, "labelFileMode": 493, "maxWeightGrams": "1e3", "apiVersion": 1.1, "note": null}}
ruamel.yaml     {"shipping": {"countries": ["TR", "DE", "NO"], "cutoff": "12:30", "express": "yes", "giftWrap": "on", "labelFileMode": 755, "maxWeightGrams": 1000.0, "apiVersion": 1.1, "note": null}}
$ node parse.mjs shipping.yaml
js-yaml 5.5.0   {"shipping":{"countries":["TR","DE","NO"],"cutoff":"12:30","express":"yes","giftWrap":"on","labelFileMode":755,"maxWeightGrams":1000,"apiVersion":1.1,"note":null}}
yaml 2.9.1      {"shipping":{"countries":["TR","DE","NO"],"cutoff":"12:30","express":"yes","giftWrap":"on","labelFileMode":755,"maxWeightGrams":1000,"apiVersion":1.1,"note":null}}
yaml (1.1)      {"shipping":{"countries":["TR","DE",false],"cutoff":750,"express":true,"giftWrap":true,"labelFileMode":493,"maxWeightGrams":1000,"apiVersion":1.1,"note":null}}
$ dotnet run parse.cs -- shipping.yaml
untyped: {"shipping":{"countries":["TR","DE","NO"],"cutoff":"12:30","express":"yes","giftWrap":"on","labelFileMode":"0755","maxWeightGrams":"1e3","apiVersion":"1.10","note":null}}
typed:   {"Countries":["TR","DE","NO"],"Cutoff":"12:30","Express":true,"GiftWrap":true,"LabelFileMode":493,"MaxWeightGrams":1000,"ApiVersion":"1.10","Note":null}
$ yq -o json -I0 shipping.yaml
{"shipping":{"countries":["TR","DE","NO"],"cutoff":"12:30","express":"yes","giftWrap":"on","labelFileMode":755,"maxWeightGrams":1e3,"apiVersion":1.10,"note":null}}

Tahminin cevabı: PyYAML'da Norveç false oldu, kargo kesim saati 750 dakikaya, dosya modu 493'e döndü, 1e3 ise string kaldı. ruamel.yaml, js-yaml ve yaml paketi YAML 1.2 kurallarını uyguladı. yaml paketini version: "1.1" ile çalıştırınca PyYAML'a çok yakın sonuç verdi; tek fark 1e3'ü yine sayı olarak okuması.

YamlDotNet ilginç bir ara yol izliyor. Tipsiz okumada (Deserialize<object>) her scalar'ı string olarak bırakıyor, yorumu sana bırakıyor. Tipli okumada ise hedef property belirleyici: bool bir property'ye yes ve on true olarak giriyor, int property'ye 0755 sekizlik okunup 493 oluyor. Ama string tanımladığın ApiVersion "1.10" olarak kalıyor. Yani .NET'te ayarları tipli bir class'a okumak, tip tahminini parser'dan alıp senin modeline veriyor. yq (Go'da go.yaml.in/yaml v4 kütüphanesi) çıktıda orijinal yazılışı korudu ve 1.2 kurallarını uyguladı.

Araç (sürüm)YAML kütüphanesiNO / 12:30 / 0755
PyYAML 6.0.3kendisi (YAML 1.1)false / 750 / 493
ruamel.yaml 0.19.1kendisi (varsayılan 1.2)string / string / 755
js-yaml 5.5.0, yaml 2.9.1kendileri (varsayılan 1.2)string / string / 755
YamlDotNet 18.1.0kendisi; tip, hedef property'denstring / string / 493 (int property'de)
yq 4.54.3go.yaml.in/yaml v4string / string / 755
Docker Compose 5.1go.yaml.in/yaml v4 ve gopkg.in/yaml.v3string / string / "493"
kubectl 1.37, kubeconform 0.8sigs.k8s.io/yaml, altında go.yaml.in/yaml v2false / string / 493

Son iki satır, aynı makinedeki iki DevOps aracının farklı kurallarla çalıştığını gösteriyor. kubectl'in kullandığı kütüphane YAML 1.1'in boolean ve sekizlik sayı kurallarını uyguluyor ama sexagesimal'i uygulamıyor: replicas: 12:30 kubectl'de string kaldı, kubeconform da integer beklediği için reddetti:

$ kubectl apply --dry-run=client -f sexagesimal.yaml -o json | jq -c '{replicas: .spec.replicas, grace: .spec.template.spec.terminationGracePeriodSeconds}'
{"replicas":"12:30","grace":493}
$ kubeconform -summary sexagesimal.yaml
sexagesimal.yaml - Deployment api is invalid: problem validating schema. Check JSON formatting: jsonschema validation failed with 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master-standalone/deployment-apps-v1.json#' - at '/spec/replicas': got string, want null or integer
Summary: 1 resource found in 1 file - Valid: 0, Invalid: 1, Errors: 0, Skipped: 0

Yani "YAML 1.1 mi 1.2 mi" sorusunun cevabı bile çoğu zaman "ikisinin karışımı". Kütüphane bilgisini binary'lerin içine gömülü Go module adlarından okudum; araçların sürümü değiştikçe bu tablo da değişebilir, kendi sürümünü yukarıdaki gibi küçük bir dosyayla dene.

Bookstore'da Üç Gerçek Vaka

Birinci vaka Compose. Bookstore.Api'nin environment variable'larını Compose dosyasına ilk yazdığımız hâli:

# Bookstore compose dosyasının ilk hâli: değerler tırnaksız
name: bookstore

services:
  api:
    image: bookstore-api:dev
    environment:
      Shipping__DefaultCountry: NO
      Shipping__Express: yes
      Shipping__ApiVersion: 1.10
      Shipping__LabelFileMode: 0755
      Catalog__PageSize: 20
$ docker compose config | yq '.services.api.environment'
Catalog__PageSize: "20"
Shipping__ApiVersion: "1.1"
Shipping__DefaultCountry: "NO"
Shipping__Express: "yes"
Shipping__LabelFileMode: "493"

Compose environment değerlerini her zaman string'e çeviriyor; Norveç ve yes doğru kaldı. Ama çeviri, YAML değeri sayı olarak okuduktan sonra yapılıyor: 1.10 önce float 1.1 oldu, sonra "1.1" string'ine dönüştü. 0755 ise sekizlik okunup "493" oldu. Uygulama Shipping:ApiVersion için "1.1" görüyor ve hiçbir yerde hata yok. Compose dokümanı da boolean benzeri değerlerin tırnak içine alınmasını öneriyor; kuralı genişlet: sayıya benzeyen her environment değerini tırnakla yaz.

İkinci ve üçüncü vaka Kubernetes. Aynı ayarlar bir Deployment manifest'inde:

# api Deployment'ının ilk hâli: değerler tırnaksız
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app.kubernetes.io/name: bookstore-api
    app.kubernetes.io/version: 1.10
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: bookstore-api
  template:
    metadata:
      labels:
        app.kubernetes.io/name: bookstore-api
    spec:
      containers:
        - name: api
          image: bookstore-api:1.10.0
          env:
            - name: Shipping__DefaultCountry
              value: NO
            - name: Catalog__PageSize
              value: 20
$ kubeconform -summary deploy-broken.yaml
deploy-broken.yaml - Deployment api is invalid: problem validating schema. Check JSON formatting: jsonschema validation failed with 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master-standalone/deployment-apps-v1.json#' - at '/metadata/labels/app.kubernetes.io~1version': got number, want null or string - at '/spec/template/spec/containers/0/env': validation failed   - at '/spec/template/spec/containers/0/env/0/value': got boolean, want null or string   - at '/spec/template/spec/containers/0/env/1/value': got number, want null or string
Summary: 1 resource found in 1 file - Valid: 0, Invalid: 1, Errors: 0, Skipped: 0
$ kubectl apply --dry-run=server -f deploy-broken.yaml
error: unable to decode "deploy-broken.yaml": json: cannot unmarshal number into Go struct field ObjectMeta.metadata.labels of type string

kubeconform, Kubernetes'in OpenAPI şemasından üretilmiş JSON Schema'larla üç hatayı birden buldu: label değeri 1.10 sayı olmuş, NO boolean olmuş, 20 sayı olmuş. Üçü de string olmak zorunda; label değerleri ve environment variable değerleri Kubernetes API'sinde her zaman string. kubectl ise ilk hatada durdu ve mesajı JSON dilinde veriyor, çünkü YAML'ı önce JSON'a çevirip sonra Go struct'ına okuyor. Hata mesajında YAML satır numarası yok; büyük bir manifest'te "hangi label?" diye aramak zorunda kalırsın. Düzeltilmiş hâl:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app.kubernetes.io/name: bookstore-api
    app.kubernetes.io/version: "1.10"
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: bookstore-api
  template:
    metadata:
      labels:
        app.kubernetes.io/name: bookstore-api
    spec:
      containers:
        - name: api
          image: bookstore-api:1.10.0
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: Shipping__DefaultCountry
              value: "NO"
            - name: Catalog__PageSize
              value: "20"
          resources:
            limits:
              memory: 512Mi
$ kubeconform -summary deploy/k8s/api.yaml
Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0, Skipped: 0
$ kubectl apply --dry-run=server -f deploy/k8s/api.yaml
deployment.apps/api created (server dry run)
$ docker compose config | yq '.services.api.environment'
ASPNETCORE_ENVIRONMENT: Development
Catalog__PageSize: "20"
Shipping__ApiVersion: "1.10"
Shipping__DefaultCountry: "NO"

Bookstore'un düzeltilmiş compose.yaml'ı da aynı kuralla yazıldı: sürüm, ülke kodu ve sayısal ayarlar tırnak içinde. Ne zaman tırnak kullanacağının kısa kuralı: değer string olmalıysa ve true/false/null dışında bir kelime, bir sayı, bir saat, bir sürüm numarası, 0 ile başlayan bir kod ya da :, #, {, [, *, & gibi özel bir karakterle başlıyorsa tırnakla. Kubernetes'te port adı, label, annotation ve env değeri; Compose'da environment, label ve sürüm alanları bu kuralın en sık uygulandığı yerler.

GitHub Actions'taki on: Anahtarı

Implicit typing yalnızca değerleri değil key'leri de etkiler. Her GitHub Actions workflow'unun ikinci satırındaki on:, YAML 1.1'de boolean true. Bookstore'un CI workflow'unu bir Python script'iyle okuyalım:

import sys

import yaml

with open(sys.argv[1], encoding="utf-8") as f:
    workflow = yaml.safe_load(f)

print(list(workflow.keys()))
print(workflow.get("on"))
$ python read_workflow.py ../../bookstore/.github/workflows/ci.yml
['name', True, 'permissions', 'jobs']
None
$ yq -o json -I0 'keys' ../../bookstore/.github/workflows/ci.yml
["name","on","permissions","jobs"]

PyYAML'da key'in adı "on" değil True; workflow.get("on") None döndürüyor. GitHub'ın kendi parser'ı on'u string key olarak okuyor, actionlint de dosyayı geçerli buluyor, bu yüzden workflow GitHub'da sorunsuz çalışır. Sorun, workflow dosyalarını okuyan kendi script'lerinde çıkar: hangi job'ın hangi event'le tetiklendiğini raporlayan bir araç on key'ini hiç bulamaz. Ya YAML 1.2 kullanan bir kütüphane seç ya da key'i True olarak da ara. Workflow syntax'ının kendisi GitHub Actions serisinde; bir sonraki yazıda göreceğimiz yamllint'in truthy kuralı da bu satırı işaretler.

Üretimde Dikkat

Bir YAML dosyasının "ne anlama geldiği", dosyanın kendisine değil onu okuyan araca bağlı. Bookstore'da aynı manifest'i kubectl, kubeconform, yq ve bir Python script'i okuyor; bu dördünün iki farklı kural seti kullandığını gördük. Bu yüzden CI'da, YAML'ı production'da okuyacak araçla doğrula: Kubernetes manifest'leri için kubectl apply --dry-run=server ya da kubeconform, Compose için docker compose config. config çıktısı Compose'un dosyayı nasıl anladığını gösterir; bir değerin tipinden şüphelendiğinde ilk bakacağın yer orası.

İkinci kural, string olması gereken değerleri her zaman tırnakla yazmak. Bu, okunabilirlikten biraz feda etmek demek ama her araçta aynı sonucu veren tek yöntem. Helm gibi template araçlarında bu daha da önemli, çünkü değer template'e girmeden önce bir kez, render edildikten sonra bir kez daha YAML olarak parse ediliyor (Helm serisinde ayrıntısıyla). Üçüncüsü, .NET'te YAML okuyorsan tipli bir model kullan; tip kararı modelinde olsun, parser'ın tahmininde değil.

Sık Düşülen Tuzaklar

Kopyala-yapıştırda girinti kayması sessizce yapı değiştirebilir. Bir örnekten aldığın resources bloğunu iki boşluk solda yapıştırdığını düşün:

# resources bir örnekten yapıştırıldı, iki boşluk fazla solda
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: bookstore-api
  template:
    metadata:
      labels:
        app.kubernetes.io/name: bookstore-api
    spec:
      containers:
        - name: api
          image: bookstore-api:1.10.0
      resources:
        limits:
          memory: 512Mi
$ kubectl apply --dry-run=server -f deploy-indent.yaml -o yaml | yq '{"pod": .spec.template.spec.resources, "container": .spec.template.spec.containers[0].resources}'
pod:
  limits:
    memory: 512Mi
container: {}

YAML geçerli, Kubernetes API'si de kabul etti, çünkü 1.34'ten beri varsayılan olarak açık olan pod-level resources özelliği sayesinde pod spec'inin de bir resources alanı var. Limit container'a değil pod'a uygulandı; container'ın kendi limiti boş. Hata vermeyen ama anlamı değişen bu tür kaymaları ancak çıktıya bakarak yakalarsın.

- name: ile name: arasındaki fark da aynı türden. Tire, sequence item'ı açar; tireyi unutursan env bir liste değil mapping olur:

$ kubectl apply --dry-run=server -f pod-env-map.yaml
Error from server (BadRequest): error when creating "pod-env-map.yaml": Pod in version "v1" cannot be handled as a Pod: json: cannot unmarshal object into Go struct field Container.spec.containers.env of type []v1.EnvVar

Son tuzak boş değer. key: yazıp değeri boş bırakmak boş string değil null demek:

$ cat empty.yaml
ConnectionStrings:
  BookstoreDb:
Catalog:
  PageSize: ""
$ yq -o json -I0 empty.yaml
{"ConnectionStrings":{"BookstoreDb":null},"Catalog":{"PageSize":""}}

.NET configuration'da null bir değer ile boş string farklı davranabilir; bir connection string'in "boş" olduğunu kontrol eden kod null'ı beklemeyebilir. Boş string istiyorsan "" yaz.

Küçük Bir Deneme

★☆☆ shipping.yaml'a %YAML 1.1 satırını ve altına --- ekle, ardından parse.py'yi tekrar çalıştır. ruamel.yaml'ın sonucu nasıl değişti?

★★☆ Bookstore'un compose.yaml'ına Shipping__Cutoff: 12:30 ve Shipping__Express: on satırlarını tırnaksız ekle. docker compose config çıktısında ne gördüğünü tahmin et, sonra çalıştırıp karşılaştır.

★★★ Bir CI adımı yaz: repodaki bütün *.yaml dosyalarını hem PyYAML hem ruamel.yaml ile oku ve JSON çıktıları farklı olan dosyaları listele. Bookstore'un manifest'lerinde kaç dosya iki parser'da farklı sonuç veriyor?

Bu yazıda YAML'ın tek satırlık değerlerine odaklandık. Bir sonraki yazıda çok satırlı string'leri (| ve >), anchor ve merge key ile tekrarı azaltmayı, --- ile çoklu dokümanı, KYAML'ı (Kubernetes'in tanımladığı, daha katı bir YAML alt kümesi) ve yamllint'i göreceğiz; merge key'in Compose, GitHub Actions ve kubectl'deki durumunu da tek tek deneyeceğiz.