Spring til indhold

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

Ordliste