Puppet Logo

Eine der größten Neuerungen mit Puppet 4 waren wohl die Typisierung von Variablen, so dass nun direkt bei der Parametrisierung einer Klasse oder definierten Resource ein Datentyp für die Parameter festgelegt werden kann. Da der Wert dann automatisch gegen diese Typisierung geprüft, entfallen damit auch die Validierungsfunktionen aus der Stdlibs. Neben den einfachen Datentypen wie Integer und String können hier auch komplexere Vorgaben gemacht werden. Das einfachste Beispiel wäre die Länge eines Strings einzuschränken indem Mindest- und Maximallänge angegeben werden wie String[1,10] für einen String mit mindestens einem, maximal zehn Zeichen Länge. Etwas komplexer wird eine Werteliste wie Enum['running','stopped'], wenn ein Wert auch undefiniert sein darf also Optional[String] oder ein Pattern[] um einen Vergleich mit einem regulären Ausdruck zu machen, aber auch dies stößt irgendwann an die Grenzen und man landet bei den abstrakten Datentypen.

Der abstrakteste Datentyp ist Struct, welcher es erlaubt eine komplett eigene Struktur als Hash festzulegen. Und hier kommt nun eine Neuerung von Puppet 4.8 ins Spiel, die es erlaubt Custom Datatypes als Aliasses zu erstellen. Diese landen im Unterordner types des Modules und müssen sich wie gewohnt an eine Namenskonvention halten damit das Autoloading funktioniert. Ein sehr gutes Beispiel findet sich im Systemd-Module von Camptocamp auf welches mich Ewoud Kohl van Wijngaarden dankenswerterweise aufmerksam gemacht hat.

Da dieses Beispiel allerdings auch etwas umfangreicher ist, möchte ich zum Erklären auf ein Beispiel zurückgreifen, dass ich im Rahmen des Puppet-Modules für Foreman erstellt habe. Ziel war es das Column_View-Plugin einzubinden, welches für jede zusätzlich eingeblendete Column 3 Pflicht- und 5 optionale Konfigurationswerte benötigt. Diese wollte ich als Hash übergeben um es dann in einer For-Each-Schleife im Template umzusetzen. Damit dies sauber funktioniert dürfen die Werte keine Leerstrings sein, somit sieht meine Definition des Custom Datatypes in der Datei types/column_view_column.pp folgendermaßen aus.

type Foreman::Column_view_column = Struct[
  {
    title => String[1],
    after => String[1],
    content => String[1],
    Optional['conditional'] => String[1],
    Optional['eval_content'] => String[1],
    Optional['view'] => String[1],
    Optional['width'] => String[1],
    Optional['custom_method'] => String[1],
  }
]

Verwendung findet dies dann einfach als Datentyp für den Klassenparameter. In meinem Fall allerdings nochmal innnerhalb eines Hashes, der als Key einen String entgegennimmt und als Wert die definierte Struktur.

class foreman::plugin::column_view (
  Hash[String, Foreman::Column_view_column] $columns = {},
){
  foreman::plugin { 'column_view':
    config => template('foreman/foreman_column_view.yaml.erb'),
  }
}

Damit kann dann ein Hash von einzublendenden Columns übergeben werden.

class { 'foreman::plugin::column_view':
  columns => {
    'architecture' => {
      'title' => 'Architecture',
      'after' => 'last_report',
      'content' => 'facts_hash["architecture"]'
    },
    'console' => {
      'title' => 'Console',
      'after' => '0',
      'content' => 'link_to(_("Console"), "https://#{host.interfaces.first.name}.domainname", { :class => "btn btn-info" } )',
      'conditional' => ':bmc_available?',
      'eval_content' => 'true',
      'view' => ':hosts_properties',
    }
  }
}

Es bietet sich natürlich an für so ein Konstrukt entsprechende Tests zu schreiben, sowohl für den Positiv- als auch Negativ-Fall. Insbesondere da eventuell davon abhängige Software noch nicht sauber mit umgehen kann, da dieses Konstrukt noch relativ neu ist. Leider hatte ich genau so einen Fall getroffen, weshalb ich für denjenigen, der das gesamte Beispiel sehen will nur auf den Pull-Request und noch nicht auf das Modul verweisen kann.

Dirk Götz

Autor: Dirk Götz

Dirk ist Red Hat Spezialist und arbeitet bei NETWAYS im Bereich Consulting für Icinga, Nagios, Puppet und andere Systems Management Lösungen. Früher war er bei einem Träger der gesetzlichen Rentenversicherung als Senior Administrator beschäftigt und auch für die Ausbildung der Azubis verantwortlich.