Schreiben der IP-Konfiguration mittels Template

Dieses Beispiel zeigt, wie man die IP-Konfiguration aus Ausgabe des Kommandos "ip a s" (IP Address Show) auslesen und mit diesen Daten direkt die Dateien /etc/sysconfig/network/ifcfg-eth0 (bei SLES) bzw. (bei Redhat) /etc/sysconfig/networking-scripts/ifcfg-eth0 schreiben kann.

Im folgenden agent bundle „interfaces“ wird mittels der fest in CFEngine vorhandenen Variable „sys.interfaces“ eine Liste mit allen vorhandenen Netzwerk-Interfaces in unsere eigens Erstellte Variable „my_interface“ geschrieben. Das @-Zeichen vor (sys.interfaces) sorgt für Einen Schleifenlauf über alle vorhandenen Interfaces. Dieses Feature nennt sich „implizierte Schleife/implicit Looping“ und stellt ein mächtige Werkzeug von CFEngine dar. Diese „Lauf-Verhalten“ machen wir uns im Methods-Aufruf zunutze: das folgende writeIPcfg Bundle wird mit jedem vorhandenen Interface einmal aufgerufen. Gibt es nun bspw. eth0 und eth1, so wird writeIPcfg zweimal aufgerufen, einmal mit writeIPcfg(eth0) und ein weiteres Mal mit writeIPcfg(eth1).

bundle agent interfaces
{
    vars:
            "my_interface" slist => { @(sys.interfaces) };

    methods:
            "any" usebundle => writeIPcfg("$(my_interface)");
}

Im folgenden agent-Bundle definieren wir zunächst drei Variablen: In $my_ip schreiben wir die gesamt Ausgabe des ip a s (ip address show) Befehls auf Das Interface des aktuellen Schleifenlaufs (im ersten Lauf wird das meistens eth0 sein). Aus dieser Gesamtausgabe der IP-Konfiguration picken wir uns im nächsten Schritt die IP-Adresse und die Subnetmask heraus. „execresult“ führt das in den Klammern folgende Kommando aus und das ohne eine Shell zu öffnen, daher das „noshell“. Eine Shell, was Mit „useshell“ angegeben wird, braucht man nur für Kommandos mit Pipes. In die Variable $my_route schreiben wir die Zeile der Ausgabe der Routingtabelle, die Das Default Gateway (0.0.0.0) enthält. Aus dieser Ausgabe picken wir uns gleich den Default Gateway. In $nic schreiben wir unser dem Schleifendurchlauf entsprechendes Interface.

bundle agent writeIPcfg(interface)
{
    vars:
            "my_ip" string => execresult("/sbin/ip a s $(interface)","noshell");
            "my_route" string => execresult("/sbin/ip route l match 0.0.0.0 dev $(interface)","noshell");
            "nic" string => execresult("/bin/echo $(interface)","noshell");

Es folgt nun die Klassen-Definition; die classes werden üblicherweise als Fallunterscheider verwendet. Hier verwenden wir die Definition allerdings, um im files-Promise Zugriff auf die Variableninhalte zu haben. Die Klassen selbst verwenden wir überhaupt nicht ;-)

classes:
            "IP_MASK" EXPRESSION => REGEXTRACT(".*?INET (.*?)/(.*?) .*","$(MY_IP)","MASK");
            "default_gw" expression => regextract(".* via ([^ ]+)\s*.*","$(my_route)","defroute");
            "broadcast" expression => regextract(".* brd ([^ ]+)\s*.*","$(my_ip)","broadcast");

Zum files: (-Promise):

"/etc/sysconfig/network/ifcfg-$(nic)" soll erstellt werden; im ersten Schleifendurchlauf erstellen wir damit die Interface-Konfigurationsdatei: /etc/sysconfig/network/ifcfg-eth0. Den Inhalt füllen wir mit Hilfe eines Templates: getIPcfg.mustache. CFEngine unterstützt mehrere Template-Formate; mustache ist das Flexibelste, weshalb es hier Verwendung findet. Die Template(-Datei) sieht so aus:

DEVICE={{DEVICE}}
STARTMODE={{STARTMODE}}
IPADDR={{IPADDR}}
NETMASK={{NETMASK}}
BROADCAST={{BROADCAST}}
BOOTPROTO={{BOOTPROTO}}

Mit "template_data => parsejson" füllen wir die Template-Schlüsselfelder DEVICE, STARTMODE, IPADDR, NETMASK, BROADCAST und BOOTPROTO mit den Werten unserer Variablen bzw. den via in der classes-Definition via regular expressions extrahierten Informationen. Der reguläre Ausdruck bei „ip_mask“ bspw. sucht aus der Ausgabe von „ip a s“, die ja in $my_ip steht, nur die Zeichen von nach „inet“ bis zur Subnetmask heraus.

Ip a s eth0 zeigt:

cfetst1:/var/cfengine/masterfiles/R4Promises # ip a s eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 00:50:56:b7:3c:6d brd ff:ff:ff:ff:ff:ff
inet 10.8.1.30/24 brd 10.8.1.255 scope global eth0
inet6 fe80::250:56ff:feb7:3c6d/64 scope link
   valid_lft forever preferred_lft forever

und der reguläre Ausdruck pickt hier nur 10.8.1.30/24 heraus. Das „mask“ unterscheidet, dass es sich hier um zwei Werte handelt. Daher können wir unten IPADDR mit dem ersten Wert, mask[1] ist die IP-Adresse und NETMASK mit dem zweiten Wert, mask[2] ist die Subnetmask, füllen. Die Kompliziertheit liegt hier im Umgang mit den regulären Ausdrücken und nicht mit CFEngine. CFEngine bietet hier Funktionen wie mask, mit denen wir aus den Matches der regulären Ausdrücke die benötigten Informationen filtern können. a

Den Wert für den Schlüssel BROADCAST wird ähnlich ermittelt. STARTMODE und BOOTPROTO wird fix eingetragen.

    files:
            "/etc/sysconfig/network/ifcfg-$(nic)"
            create => "true",
            template_method => "mustache",
            edit_template => "${sys.workdir}/masterfiles/templates/getIPcfg.mustache",
            template_data => parsejson('{
               "DEVICE": "$(nic)",
               "STARTMODE": "onboot",
               "IPADDR": "$(mask[1])",
               "NETMASK": "$(mask[2])",
               "BROADCAST": "$(broadcast[1])",
               "BOOTPROTO": "static",
              }');

Der Report sorgt für die Info im syslog (/var/log/messages), dass für das entsprechende Interface die Datei mit den entsprechenden Werten erstellt wurde.

reports:
            "ifcfg-$(nic) file was created with ip address: $(mask[1])/$(mask[2])";
}