Programujeme letenky

Vývojáři GOL IBE v C.E.E. Group Travelport
Programátorský web
Background image - Futurama office park

Kompilovaná konfigurace

Tomáš Srb, 29. 6. 2020

K čemu to může být?

Máme projekt. Dost starý, leč stále udržovaný projekt. Některé části jsou z výkonostních důvodů v c++, jiné pro snazší zpracování http požadavků v php, rúzné nástroje a pomůcky v bashi či pythonu a konečně nejmodernější dílky celé skládačky v javascriptu. Každá komponenta má svou zodpovědnost a každá má i svou konfiguraci ve formátu sobě vlastním. Tím u nás postupně vzniklo na 230 konfiguračních souborů nejrůznějšího druhu. Většina ve stylu zdrojového kódu php:

<?php

$HYPER_DUPER_SETTING = "foo";

$DB_ACCESS = [
    'host' => 'databazator.example.com',
    'user' => 'guest',
    'password' => 'hEsL0',
...

něco v nápodobě yaml použité dávno před vznikem jsonu, natož yamlu:

DebugLevel: 10
LogFilename: logfile.log
...

a došlo i na .ini, plnotučný yaml i json.

Udržet na uzdě takovou konfiguraci, to vyžaduje celého člověka. Zvlášť když se podstatné části většiny konfiguračních souborů podobali - skoro všechny komponenty používají stejnou databázi, navzájem spolu komunikují pomocí url o stejném základu. Mohli jsme na to sednout a během pár... týdnů... nastavení všech součástí sjednotit, vytvořit centrální konfiguraci se sdílenou sekcí obsahující společné parametry a cca 230 drobnějších individuálních sekcí. Mohli, jenže místo toho jsme začali konfiguraci generovat. Tedy, my jsme ji svým způsobem generovali už předtím. Prostě, když někdo nějakou část potřeboval zprovoznit, tak prostě šel do jejího adresáře, tam našel nějaký config.php.SAMPLE, nebo neco.conf.SAMPLE, ten vzor zkopíroval na verzi bez přípony .SAMPLE a kopii ručně upravil ve svém oblíbeném textovém editoru. Takže teď jsme to jen zautomatizovali.

Jak se z toho stal takový... šikovný nástroj?

První krok spočíval v nahrazení ručně zadávaných parametrů značkou {~???}. Asi takto:

  $PORT = {~???};

Na což instalační script zareagoval asi takto:

Generuji pokus.php z pokus.php.SAMPLE ...  Chybejici informace pro parametr "  $PORT = {~???};" na radce 4.
Zadejte kompletni radek (napr.   $PORT = superheslo;):

Po zadání $PORT = 1234; vygenereoval soubor pokus.php v němž nahradil řádek se značkou řádkem zadaným. Aby se příště na označený parametr už neptal, vložil si do nového souboru komentář:

//C:  $PORT = {~???};
  $PORT = 1234;

Abychom si udrželi přehled, které konfiguráky už máme "automatizované" a které ještě ne, přidáváme do zpracovaných komentář,

// compilable conf

bez kterého generátor vypíše červenou hlášku a skončí. Pokud vše proběhne správně, vloží se do výsledného souboru na místo výše uvedené hlášky následující:

// tento soubor vznikl generovanim pomoci compile_conf z pokus.php.SAMPLE

Pokud by měl kompilátor přepsat soubor bez téhle poznámky, tak radši zrudne a skončí. (Malá odbočka: později se také ukázala potřeba generovat konfiguráky v jsonu... který nemá komentář! Takže pokud se v samplu najde

// compilable conf without comments support

je výstup bez milosti přepsán a informace o generování se v něm nehledá, ani se do něj nepřidává. Tolik komentář k datovému formátu bez komentáře.)

Pak jsme zjistili, že heslo k databázi stačí zadávat jen na instancích "v serverovně", vývojářské instalace si vystačí s jednotným. A že by vlastně vůbec mohlo být pěkné, kdyby programátoři mohli pomocí kódu zajistit, jak bude aplikace nastavená na produkci.

V samplech se objevily takovéhle konstrukce:

//PTD:    'password' => '{~???}',
//I:    'password' => 'hEsL0',
...
//PTD:  $PORT = 1234;
//I:  $PORT = {~???};

Význam písmen je jistě zřejmý - měli jsme produkční, testovací, devel a interní instalace.

Jenže kdo chce při instalaci nějaké aplikace stokrát zadávat různé podoby řádku

  $PORT = 1234;
...
  AppPort: 1234
...
  ControlTcpPort="1234"
...

Takže vznikl instalátor, který se na port zeptal jednou, nastavil ho do proměnné prostředí a generátor konfigurace se naučil znát konstrukci

  $PORT = {~port};

A tím bylo prakticky hotovo - nyní se při instalaci aplikace instalační script zeptá na pár parametrů a frrr, sviští záplava modrých zpráv o úspěšně vygenerovaných souborech. A co je ještě lepší, v pipeline gitlab-ci se instalační script neptá vůbec na nic a vše nakonfiguruje podle přednastavených parametrů.

Když už je tu zmínka o CI, nešlo by to nějak upravit pro integraci s gitlab-ci?

Málem bych zapomněl, ty proměnné, které se dají nastavovat třeba v .gitlab-ci, nebo ve formuláři Schedule a new pipeline, tak to jsou přesně ty, které generátor konfigurace použije pro tvorbu konfiguračních souborů - čili nastavení běhu testu v pipeline nemusí být nadrátované v projektu, ale lze ho doladit naklikanými hodnotami v prostředí vašeho oblíbeného správce kódu.

A co tajné parametry, ty mám taky dát do proměnných prostředí?

Posledním vylepšením bylo přidání šifrování. Konfigurační soubory přece mohou obsahovat i tajné informace. Generátor tedy umí i hodnoty zašifrované buď symetricky (AES), nebo asymetricky (RSA) - inspirováno ansiblem. Vygenerovat klíč i zašifrovat hodnotu proměnné umí script compile_conf_encrypt.

Použili jsme to na produkčním serveru zhruba takto:

Veřejný klíč je možno commitnout do repozitáře s projektem, každý vývojář pak může přidávat důvěrné parametry do konfigurace. Abychom předešli průsaku kódu, který neprošel code review, generátor nepřipustí v hodnotě šifrované proměnné apostrof ani uvozovky.

Kde je ten zázrak k mání?

Zdrojový kód je k nalezení na https://github.com/Travelport-Czech/compile_conf