YAML
et format til indstillinger et menneske kan læse
Hvad det er
YAML er et tekstformat til strukturerede data, hvor strukturen udtrykkes med
indrykning, koloner og bindestreger i stedet for klammer og anførselstegn, og
derfor er det det sædvanlige valg til konfiguration, som mennesker skriver og
gennemgår i hånden. Navnet er et rekursivt akronym, »YAML Ain’t Markup
Language«. Filerne hedder .yaml eller .yml, og RFC 9512 fra februar 2024
registrerede medietypen application/yaml og gjorde .yaml til den foretrukne
endelse.
Bag den sparsomme tegnsætning ligger en lille datamodel. Der er mappings, som
parrer nøgler med værdier, sekvenser, som er ordnede lister, og skalarer:
strenge, tal, sandhedsværdier og null. Omkring modellen lægger sproget det, en
person med en fil åben i editoren har brug for: kommentarer, der starter med
#, blokskalarer, der bevarer linjeskift med | eller folder dem sammen med
>, flere dokumenter i én fil adskilt af ---, ankre og aliasser (&navn og
*navn), så én værdi kan genbruges andre steder, og tags, der navngiver en type
direkte.
Det første YAML-bibliotek blev skrevet i Perl i 2001. Version 1.1 af specifikationen kom i 2005 og version 1.2 i 2009, hvor hovedformålet var at gøre YAML til en streng overmængde af JSON, og hvor mange af de gamle regler for implicit typning forsvandt. Revision 1.2.2 fra oktober 2021 rettede fejl og præciserede formuleringer uden at ændre selve sproget. Meget software læser stadig efter de ældre regler: Ansibles dokumentation oplyser, at PyYAML og libyaml implementerer YAML 1.1.
Dermed står YAML mellem to naboer. JSON er dets strenge delmængde, kompakt og entydig og lavet til, at programmer udveksler data. TOML er et fladere konfigurationsformat, hvor hver streng står i anførselstegn, så en værdis type aldrig skal gættes. YAML kan mere end begge, og netop den ekstra frihed er kilden til dets problemer.
Hvorfor det betyder noget
Det egentlige valg handler om, hvilket format en fil skal have, når mennesker skal vedligeholde den i årevis: beskrivelser af udrulninger, definitioner af byggepipelines, serverkonfiguration. Ansible-playbooks er YAML, og det samme gælder konfigurationsfilerne i mange andre infrastrukturværktøjer, så meget af det arbejde har valget allerede truffet for en. Tilbage står spørgsmålet om, hvor stramt filerne skal skrives.
Argumentet for YAML er læsbarhed. En reviewer kan læse en diff uden at tælle
klammer, en kommentar kan forklare, hvorfor en værdi er, som den er, og et
script på flere linjer eller et certifikat passer ind i en blokskalar uden
escaping. Over for det står, at mellemrum bærer betydning: en nøgle, der er
rykket to mellemrum for langt ind, kan havne i en anden mapping, mens filen
stadig parser. Tabulatorer er helt forbudt til indrykning. Værdier uden
anførselstegn får deres type ud fra stavemåden, og stavereglerne er forskellige
i 1.1 og 1.2. Skabeloner lægger en ekstra syntaks ovenpå: i Ansible skal en
værdi, der begynder med {{, stå i anførselstegn, ellers tager parseren den for
en mapping.
Det, der afgør sagen, er, hvordan filen bliver tjekket. YAML med en linter og en vane med at sætte strenge i anførselstegn er et behageligt format til konfiguration, som et team redigerer i fællesskab. YAML uden nogen af delene er et format, hvor en gyldig fil kan sige noget andet, end forfatteren mente. Til data, der sendes mellem programmer, er JSON det sikrere udgangspunkt, og til en kort indstillingsfil med få niveauer er TOML enklere.
Sådan ser det ud i praksis
Virksomheden driver sit eget miljø med webtilstedeværelse, git-forge, database, sikkerhedskopier, mailtransport og DNS, og hele miljøet er beskrevet i 12.065 linjer YAML: 19 Ansible-playbooks og 34 roller. Sammensætningen er data frem for flag: hvilke roller en vært kører, står som variabler i YAML for den tier-gruppe, værten hører til, så en provisionering uden argumenter har alt, hvad den skal bruge, til at bringe hver vært i dens erklærede tilstand. Her er YAML sproget for en erklæret tilstand, og resultatet bliver kontrolleret i stedet for antaget, for en konvergeret vært skal rapportere nul ændringer. Siden lægger også prisen frem: hver ændring tager længere tid end at rette en fil direkte på serveren, og fordi en konvergering, der kun gennemføres halvt, er værre end en, der nægter, kører et preflight-play nu foran den.
Tolv tusind linjer YAML skal læses af en maskine, før et menneske ser dem. YAML er et af de sprog, en commit-gate med 22 lintere tjekker, sammen med HTML, CSS, JavaScript, Python, Markdown og shell. Gaten har intet advarselsniveau, så hver diagnostik standser committet, og inline-undertrykkelser er direkte forbudt: en ignorér-kommentar eller et deaktiveringsdirektiv i filen fører ingen steder hen. Det betyder meget for YAML, fordi de fleste af fælderne nedenfor er fuldt gyldig syntaks, og kun en regel, der afviser dem, fanger dem, inden de når en vært.
Hvor det går galt
Fælderne nedenfor har ét træk til fælles: filen parser fint og betyder noget andet.
En landekode bliver til false. Efter YAML 1.1-reglerne er yes, no, on,
off, y og n uden anførselstegn sandhedsværdier, i flere skrivemåder med
store og små bogstaver. Kerneskemaet i YAML 1.2 beholder kun true og false i
tre skrivemåder, og RFC 9512 advarer om, at en parser efter den gamle syntaks
kan læse {insecure: n} som false i stedet for bogstavet n. En liste over
nordiske lande viser problemet:
lande:
- DK
- SE
- NO # YAML 1.1 læser dette som false
Sæt anførselstegn om enhver streng, der kan læses som noget andet, skriv kun
sandhedsværdier som true og false, og lad en linter håndhæve begge dele.
Et versionsnummer mister et ciffer. Tal uden anførselstegn bliver
konverteret, så version: 1.10 ankommer som kommatallet 1.1, og efter
1.1-reglerne markerer et foranstillet nul et oktaltal, så en kode skrevet som
010 bliver til 8. Ansibles dokumentation beder netop derfor brugerne om at
skrive en version som "1.0". Sæt anførselstegn om id’er, koder og versioner,
for de er strenge, der tilfældigvis indeholder cifre.
En nøgle havner under den forkerte forælder. Indrykningen afgør, hvilken mapping en nøgle hører til, så én linje, der er flyttet to mellemrum, flytter en indstilling et andet sted hen uden nogen fejlmeddelelse. Specifikationen kræver desuden, at hver nøgle i en mapping er unik, og en gentaget nøgle langt nede i en lang blok er let at overse i en review, så yamllint har en regel for hver af de to fejl. Kør den, før filen tages i brug.
Indlæsning af en fil afvikler kode. Tags kan navngive sprogspecifikke typer,
og en loader, der følger dem, bygger det objekt, filen beder om. PyYAMLs
dokumentation skriver, at den fulde yaml.load er lige så kraftfuld som
pickle.load og kan kalde enhver Python-funktion, og henviser til
yaml.safe_load, der kun kender standardtags. RFC 9512 beder om, at
kodeafvikling i deserialisering som standard er slået fra. Brug den sikre loader
til hver fil, der kommer udefra.
En kort fil fylder hukommelsen op. Ankre og aliasser gør et YAML-dokument til en graf frem for et træ. Når hvert anker gentager det foregående to gange, fordobler hver ny linje størrelsen, så tredive korte linjer beskriver over en milliard strenge, når et program gennemløber resultatet som et almindeligt træ. RFC 9512 kalder det eksponentiel dataudvidelse, et billion laughs-angreb, og svarer med processorer, der begrænser dybden af ankerrekursion. Tag kun imod YAML udefra gennem sådan en processor, og afprøv grænsen mod den parser, der faktisk er i brug.
Læs mere
- YAML 1.2.2 specification — selve sproget, herunder kerneskemaet, der afgør, hvordan værdier uden anførselstegn får deres type.
- RFC 9512: YAML Media Type —
registreringen af
application/yamlmed sikkerhedsnoter om sandhedsværdier, kodeafvikling og aliasudvidelse. - YAML specification changes — hvad hver revision ændrede, fra 1.2.1 til 1.2.2.
- Ansible: YAML Syntax — den delmængde, Ansible bruger, med faldgruberne omkring anførselstegn.
- yamllint rules — reglerne for sandhedsværdier, dublerede nøgler, oktaltal og indrykning.
- PyYAML documentation —
forskellen på
loadogsafe_loadmed bibliotekets egne ord.