Konzeptionelle Gedanken Text-Template-Engine rtemple
====================================================

Start: 25.04.2005
Stand: 26.04.2005
Autor: Winfried Mueller, www.reintechnisch.de


Oft besteht die Aufgabe, eine Sammlung von Textdateien für einen
speziellen Anwendungsfall anzupassen. Typische Bereiche, wo dies nötig
ist, sind textbasierte Konfigurationsdateien, wie sie unter Linux üblich
sind, oder Web-Projekte, wo HTML, CSS, Steuerungs oder
Konfigurations-Dateien angepasst werden müssen.

Oft ist es dabei so, dass große Teile der Textdateien unverändert
bleiben und nur ein paar wenige Bereiche ausgetauscht werden. Bei
dutzenden von Dateien kann dieser Job sehr mühselig und fehleranfällig
sein.

Dies ist eine zentrale Aufgabe von System-Administratoren: Eine Menge
von Systemen einzurichten und zu pflegen, die alle ähnlich und doch 
nicht gleich sind. Und dies beansprucht oft jede Menge Zeit, weil
keine optimalen Werkzeuge für diese Anforderung zur Verfügung stehen.

Ein paar grundsätzliche Strategien, wie man diese Problematik
in den Griff bekommt:

* Zentralisierung der Konfiguration: Anstatt überall verstreut 
Konfigurationsdateien zu haben, ist es besser, einen zentralen Ort
für die gesamte Konfiguration eines bestimmten Systembereiches zu
haben.

* Hierarchische Konfigurationsstruktur: Um bei einer Vielzahl von
Variablen den Überblick zu behalten und schnell konfigurieren zu
können, bedarf es eines hierarchischen Systems der 
Konfigurationsvariablen. Darüber bekommt man schnell einen Überblick
und kann Bereiche ausblenden, die man im Augenblick nicht 
konfigurieren will. 

* Verbergung von Implementierungsdetails: Obwohl man eigentlich
nur konfigurieren möchte, muss man sich in technische Details von
Komponenten (Programmpaketen, Skripten) einarbeiten. Jedes Programm
bringt z.B. seine eigene Konfigurationssprache (Syntax) mit, manche
nutzen gar eine Programmiersprache zur Konfiguration, die man 
verstehen muss. Das bringt alles oft unnötige Komplexität in den Job 
der Systemkonfiguration. Solche Details müssen verborgen werden.

* Verbergung von unnötigen Konfigurationsparametern: Manche 
Konfigurationsdatei kommt mit hunderten von Parametern daher, die
eingestellt werden können. Ein Großteil davon wird man in seinem
konkreten Anwendungsfeld nie brauchen. Jedesmal, wenn man die
Konfigdatei jedoch bearbeitet, wird man wieder mit unzähligen
Einstellungen belastet und muss erneut abschätzen, was man anpassen
muss und was konstant bleibt. Es ist eine gute Idee, all das zu 
verbergen, was man für das eigene Anwendungsfeld nie verändern wird.



Konzept Text-Template-Engine
----------------------------

In einem Template-Verzeichnisbaum werden alle zu konfigurierenden Text-
Dateien abgelegt. Und zwar in der gleichen hierarchischen-Struktur und
mit den gleichen Namen, wie sie später im Zielverzeichnis existieren
sollen.

    Template Verzeichnis:
      etc/
        hosts
        resolv.conf
        apache2/
          apache2.conf


    Zielverzeichnis:
      etc/
        hosts
        resolv.conf
        apache2/
          apache2.conf


Jede dieser Textdateien wird nun präperiert. In der Art, dass überall
dort, wo veränderliche Bereiche sind, Variablen eingesetzt werden. Diese
Variablen werden später durch die konkrete Ausprägung ersetzt.

Damit Variablen eindeutig erkannt werden können, erhalten sie ein
bestimmtes Textmuster am Anfang und am Ende, werden sozusagen dort
eingebettet. Bsp:

    -- Bsp TextKonfigurationsdatei
    # Konfigurationsdatei
    MailTo     = @@_mailto_@@
    MailFrom   = @@_mailfrom_@@
    SmtpServer = @@_mailsmtp_@@

    -- Bsp HTMLDatei
    <html>
    <body>
    Dies ist der Server @@_server_name_@@. Nutzen sie
    bitte folgende Weiterleitung: @@_forward_@@
    </body>
    </html>

Hier wird @@_ als Anfangsmuster und _@@ als Endmuster verwendet.
Grundsätzlich ist dies frei konfigurierbar, man sollte sich jedoch nach
Möglichkeit eines auswählen, was für alle möglichen Projekte passt. Es
darf nicht in Konflikt mit dem restlichen Text kommen. Der ursprüngliche
Text darf also das Muster nicht enthalten.

Für das komplette Projekt gibt es nun eine Konfigurationsdatei in Yaml-
Syntax. Dort können wohl strukturiert Konfiguriationsparameter
eingetragen werden. Man wählt hier eine Struktur, die aus dem Blickwinkel
des Konfigurators sinnvoll ist, anstatt sich an technischen Details der
dahinterliegenden Komponenten zu orientieren. Hier ein Beispiel:

    ---
    Config:
      General:
        LogoName:     'logo4.gif'
        WikiTitle:    'reintechnisch.de Wiki'
        BaseUrl:      'http://www.wikidorf.de/reintechnisch/'

      Color:
        HeadLine:   '#0099FF'
        BGHead:     '#ffcc33'
        Background: '#fffff2'
        A:          '#003399'
        AVisited:   '#663399'
        Left:       '#000000'
        BGLeft:     '#ffcc33'
        Pre:        '#efefef'

      Password:
        Admin:           'StrengGeheim'
        Edit:            'Geheim'
        Read:            ''
        Upload:          ''
        Attr:            ''


Oft wird man nun direkt die Variablen in den Template-Dateien mit dem
angegebenen Wert in der Config-Datei ersetzen können. In diesem Fall
kann man in den Templates direkt auf die Variable verweisen. Die
hierarchische Struktur der Variablen wird syntaktisch durch einen
"/" abgebildet. Bsp:

    <html>
    <body>
    Das Wiki ist zu erreichen unter @@_General/BaseUrl_@@.
    </body>
    </html>

Im einfachsten Fall baut man also zuerst einmal die Configdatei auf und
ersetzt dann in allen Text-Template-Dateien die entsprechenden Bereiche
durch die Variablen. Damit ist alles vorbereitet, damit rtemple die
Zieldateien nach Vorgabe der Config-Datei erzeugen kann.


Die Welt ist jedoch oft nicht so einfach. Nehmen wir einmal an, wir
wollen eine Reihe von Rechnern in eine etc/hosts Datei eintragen. Eine
Config-Datei könnte so aussehen:

    Config:
      Hosts:
        - Name:   'workstation1'
          Domain: 'intra.net'
          IP:     '192.168.0.10'
        - Name:   'workstation2'
          Domain: 'intra.net'
          IP:     '192.168.0.11'


Dies ist für den Konfigurator gut lesbar und technische Details über den
Aufbau einer Hosts Datei werden verborgen.

Die Template Datei könnte nun so aussehen:


    127.0.0.1   localhost

    # Begin local hosts
    @@_hosts_@@
    # End local hosts

    ::1     ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    ff00::0 ip6-mcastprefix
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    ff02::3 ip6-allhosts


Wir können hier keine direkte Variablensubstitution machen. Wir brauchen
vielmehr ein Stück Programmcode, welches aus den Informationen der
Config-Datei die entsprechenden Hosts-Zeilen bastelt.

Rtemple handhabt das so, dass generell alle Variablen, die kein
hierarchischen Trenner "/" enthalten, als lokale Variablen angesehen
werden. Und lokale Variablen werden durch Programmcode erzeugt, der in
die Template Datei eingefügt wird. Und zwar vor dem eigentlichen
Template-Text, mit einem __END_RTEMPLE__ abgeschlossen:

    localvar('hosts') {
      r = ""
      $Config['Hosts'].each do |host|
        r << host['IP'] + "\t" +
             host['Name'] + "." + host['Domain'] + "\t" +
             host['Name'] + "\n"
      end
      r
    }
    __END_RTEMPLE__

    127.0.0.1   localhost

    # Begin local hosts
    @@_hosts_@@
    # End local hosts

    ::1     ip6-localhost ip6-loopback
    fe00::0 ip6-localnet
    ff00::0 ip6-mcastprefix
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    ff02::3 ip6-allhosts


Bei der Generierung der Zieldateien schaut rtemple also zuerst einmal,
ob es ein __END_RTEMPLE__ findet. Wenn ja, dann wird die Datei in zwei
Teile gesplittet. Alles davor ist Generierungs-Code, alles
danach ist der eigentliche Template-Teil.

Wichtig ist, dass man im Generierungs-Code alles generisch hält und
alles, was für die Erzeugung anpassbar sein muss, in der Config-Datei
landet. Nur die Config-Datei ist der zentrale Ort aller Einstellungen
für die zu erzeugende Instanz (Entität).

Die Definition der lokalen Variablen folgt immer dem Schema
localvar('hosts') { codeblock }, wobei das Ergebnis des Codeblocks der
Wert der Variablen entspricht.

Dieses Konzept ist recht flexibel, kann man doch so während der
Erzeugung beliebigen Code ausführen. Man könnte z.B. ein Time.new
aufrufen, um die aktuelle Uhrzeit irgendwo einzufügen. Oder das
Environment nach bestimmten Werten befragen. 

    localvar( "Test1" ) {
      "Hello World"
    }

    localvar( "Test2" ) {
      Time.new.to_s
    }

    __END_RTEMPLE__
    Das ist ein kleiner Text, und ich sage mal
    @@_Test1_@@. 

    Wenn dieser Text erzeugt wird, 
    ist es @@_Test2_@@

    Bla Bla Bla


Generierung
-----------

Nachdem die Template-Files und die Config-Datei entsprechend vorbereitet
sind, kann man rtemple aufrufen. In der Config-Datei befinden sich
weitere Angaben, die für die Erzeugung des aktuellen Projekts wichtig
sind:

    TmplPath:       './tmpl'
    DstPath:        './dst'
    VarPrePattern:  '@@__'
    VarPostPattern: '__@@'
    TmplEndMark:    '__END_RTEMPLE'

In diesem Beispiel würde die Konfigurationsdatei im aktuellen
Verzeichnis liegen und unterhalb dieses Verzeichnisses gibt es ./tmpl
und ./dst für die Vorlagen und das zu erzeugende Ziel. Ein Aufruf
erzeugt dann alles:

  # rtemple rtemple.conf





Diskussion unter: http://www.rubyforen.de/topic,713.html

Weblog

21.05.2005 Konfigurationsmanagment

Vernünftiges Konfigurationsmanagment wird heutzutage immer wichtiger. Systeme werden immer komplexer und damit steigen auch die Möglichkeiten der Konfiguration und auch der Fehlkonfiguration.

Wenn ich Systeme wie Samba oder Apache konfiguriere, komme ich mir manchmal vor, als müsste ich eine Steuererklärung machen oder bei der Stadt ein kompliziertes Formular ausfüllen. Tausende von Fragen, die gar nicht auf mich zutreffen, die aber auf irgendjemanden in diesem Land zutreffen könnten. Und all diese Felder muss ich erstmal verstehen. Diese Aufgabe ist nervig. Wenn man so ein Formular dann zum zweiten mal ausfüllen muss, ist man froh, eine Vorlage zu haben, wo man fix abschreiben kann. Wehe dem, das Formular hat sich irgendwo kaum sichtbar verändert, dass übersehe ich dann bestimmt.

So in etwa ist das mit der Konfiguration von komplizierten Systemen. Wohin muss gutes Konfigurationsmanagment gehen? Ein paar Ideen:

  • Mir sollte als Konfigurator immer nur das zur Konfiguration angeboten werden, was ich in meinem Anwendungskontext brauche. Als Systeme nur ein dutzend Konfigparameter hatten, konnte man da schnell über alles drüberschauen. Heutzutage haben Systeme aber hunderte oder gar tausende von Konfigurationsparametern, besonders wenn sie stark skalierbar sind. Viele sind in meinem Umfeld, in dem ich agiere niemals nötig. Es muss also Mechanismen geben, womit ich alles ausblenden kann, was für meine Anwendungsdomäne nicht erforderlich ist. Ein schönes Praxisbeispiel ist der Browser Firefox, wo man möglichst viel Konfiguration verbirgt. Man hat sozusagen ein Anfängerprofil an Konfigmöglichkeiten als Standard definiert. Manche Programme nutzen auch "Standard" und "Experte", um zwei Ebenen anzubieten. Modernes Konfigmanagment muss oft noch viel weiter gehen, in dem man eigene Konfigprofile anlegt, wo man genau definiert, welche Parameter in meiner Domäne variabel sein müssen und welche fix sind und damit ausgeblendet werden können.
  • Konfigurationsobjekte - es gibt oft Teilbereiche einer Konfiguration, die definierte zusammenhängende Konfiguration benötigt. Im Steuerformular wäre das die Anschrift. Name, Adresse, Telefon ist sozusagen eine Aggregation, ein zusammenhängendes Etwas, was in dieser Form öfters gebraucht wird. Solche Konfigurationsobjekte begegnen einem immer wieder - Teilbereiche einer Gesamtkonfiguration, die in einer definierten Art belegt werden müssen. "Wenn wir einen Internetserver für diese Anwendung aufsetzen, muss dieser Abschnitt so konfiguriert werden." oder "Für die Dateifreigabe X braucht es diese Konfiguration in diesem Bereich, für Freigabe Y dagegen diese." Konfigurationswerkzeuge müssen solche Konfigurations-Subsets irgendwie unterstützen.
  • Konfiguration muss hierarchisch sein. Das Steuerformular ist wieder ein gutes Beispiel. Wenn ein Formular "Einnahmen aus Vermietung" heißt und ich weiß, dass ich nichts vermiete, dann weiß ich auch, dass ich mir die hundert Fragen dazu nicht im Einzelnen durchlesen brauche. Ich kann gleich alles ausblenden. Mit einer guten Hierarchie ist das ähnlich, ich kann ganze Teilbereiche ausblenden und mich auf das Wesentliche konzentrieren.
  • Konfiguration und Programmierung strikt trennen. Mitunter wird Programmierung und Konfiguration vermischt. Programmcode und Teile einer Skriptsprache in Konfigurationsdateien. Das Hauptproblem dabei ist, dass man so in der Rolle des Konfigurators das Wissen eines Programmierers haben muss. Programme sollen aber oft durch Menschen konfiguriert werden, die kein Einblick in die dahinterliegende Programmiersprache haben. Selbst Programmierer können nicht Einblick in sämtliche gängigen Skriptsprachen haben. Auch wird das konfigurieren eines Systems so oft unnötig kompliziert. Durch die Mächtigkeit einer Programmiersprache in der Konfiguration kann man auch mächtigere Konfigurationsfehler machen. Ein Programm kann solche Konfigurationsfehler auch nicht abfangen. Man kann sehr gut ein paar Parameter auf Gültigkeit überprüfen, nicht aber, ob ein Stück Programmcode auch Sinn macht. Ein Programm was ein Konfiguration über Programmiersprache anbietet, wird bald dahin tendieren, dass dort auch komplexe Skripte für bestimmte Funktionalitäten entstehen. Man programmiert sozusagen seine Anwendung im Konfigskript weiter. (PmWiki ist ein gutes Beispiel dafür). Ziel sollte es aber sein, dass wirklich die Anwendung weiter entwickelt wird, um dann eine einfache Konfigurationsschnittstelle anzubieten.

07.05.2005 Erste Experimentierversion

So langsam stabilisiert sich der Code für eine erste Version, mit der ich in den nächsten Tagen einige Praxistests machen werde. Es ist einiges hinzugekommen und einige Schnittstellen haben sich geändert, aber das Grundkonzept ist geblieben. Ein erster Testlauf für eine Wiki-Konfiguration verlief prächtig. Beim Wiki werden dabei ca. 5 Dateien nach Vorgabe der rtemple-Config-Datei angepasst. Hier mal die Beispiel Config:

 
#PmWiki 1.0 Config 
---
TmplPath: './tmpl'
DstPath:  './'

TmplPrePattern: '@@-'
TmplPostPattern: '-@@'

FileDefaultProp:
  Owner: 'wm:wm'
  Perm:  '640'

DirDefaultProp:
  Owner: 'wm:wm'
  Perm:  '750'

Files:
  - Name: 'pub/skins/wm/wm.css'
    Perm: '0440'
  - Name: 'pub/skins/xp/xp.css'
    Exclude: true


Config:
  General:
    LogoName:    "logo4.gif"
    WikiTitle:   "Test Wiki"
    BaseUrl:     "http://local.intra.net/reintechnisch"
    RewritePath: "/reintechnisch"

  Color:
    HeadLine:     '#0099FF'
    Background:   '#fffff2'
    A:            '#003399'
    AVisited:     '#663399'
    Left:         '#000000'
    BGLeft:       '#ffcc33'
    BGTop:        '#ffcc33'
    BGPre:        '#efefef'
    PreBorder:    '#ff6600'

  Decoration:
    A:            'none'
    AVisited:     'none'
    AHover:       'underline'
    PreBorder:    '1px dashed'

  Position:
    LogoMarginTop:  '5px'
    LogoMarginLeft: '10px'
    LogoHeight:     '100px'

  Password:
    Admin:           '$1$Pes.MzH9$HPbkNCSxzbwim7q6z5P735'
    Edit:            '$1$Pes.MzH9$HPbkNCSxzbwim7q6z5P735'
    Read:            '$1$Pes.MzH9$HPbkNCSxzbwim7q6z5P735'
    Upload:          '$1$Pes.MzH9$HPbkNCSxzbwim7q6z5P735'
    Attr:            '$1$Pes.MzH9$HPbkNCSxzbwim7q6z5P735'


Permissions können jetzt für jede Datei einzeln in der Config angegeben werden. Verzeichnisse, die angelegt werden müssen, können ebenfalls mit definierten Permissions und Owner belegt werden. Auch kann man Files einzeln über die Config excluden.

Eine neue Idee ist hinzugekommen: Manchmal gibt es von bestimmten Konfigurationsdateien völlig unterschiedliche Varianten und man möchte eine Variante auswählen. Möglich wäre dies, in dem das Template mehrere Abschnitte hätte, die Varianten widerspiegeln. Also z.B. so:

 
#template code
#...


__END_RTEMPLE__
# Template Variante 1
#...


__END_RTEMPLE_VARIANT__
# Template Variante 2
#...


__END_RTEMPLE_VARIANT__
# Template Variante 3
#...



26.04.2005 Erster Prototyp

Hier ist ein erster Prototyp mit ein paar Beispieldateien einer Linux-Konfiguration. Einfach auspacken und starten. Getestet unter Windows und Linux mit Ruby 1.8.

Attach:rtemple-example1.tgz