Automatic PDF Generation with Google Chrome

Many developers get, sooner or later, the task to generate PDF documents automatically. Though, less developers put their experiences and insights then into a blog-post to save others some hassle. So let me do you a favor by explaining how we utilize Google Chrome in headless mode to generate pretty PDF files from HTML.

I won’t go into the details why Google Chrome. If you found this blog entry I suppose you already tried other options without success or satisfying results. We sure have tried several other options (e.g. wkhtmltopdf, dompdf, tcpdf) but only Google Chrome provided us with the results we wanted.

Another advantage of Google Chrome is the possibility to instrument a remotely running instance. And that’s exactly how we instruct it to generate a PDF file for us. Though, not with Puppeteer but with plain chrome devtools protocol (CDP) communication over a websocket.

I’ll avoid any programming language specific examples. You can take a look here at our implementation in PHP. So, let’s start with it.

The Process to Print HTML to PDF

First you’ll need to connect with the browser by use of a websocket connection at: ws://

This is printed on stderr right at launch of the process and also available on the JSON api:

The important bit is the browser id (79744167-25f0-41a8-9226-382fa2eb4d66) at the end of the URI. This changes every time the process is launched and can’t be configured.

Communicating with the browser now takes place over this socket by transmitting and receiving JSON messages. They are divided into four types:


These primarily originate from ourselves. They contain an id, a method and parameters:

{ “id”: <mixed>, “method”: <string>, “params”: <object> }

The id is chosen by us and should be different for each call. It’s sent back with the response so it’s possible to later associate it with the call we made. Though this is mostly relevant if you are not communicating synchronously, which we do. So this may just as well be an integer incremented by 1 each time.


These are sent by the browser in response to an API call.

{ “id”: <mixed>, “result”: <mixed> }


If it’s not a response, it’s an error.

{ “id”: <mixed>, “error”: { “code”: <int>, “message”: <string> } }


These may be sent by the browser at any time.

{ “method”: <string>, “params”: <object> }

Some of these are of interest to us. Some of them are not and can be ignored.

Operating the Browser As Usual

In order to let the browser print us a web page (or HTML) to PDF we need to instrument it the same as when we do it manually on the desktop.

First we need to open a new tab:

Call: { “id”: 1, “method”: “Target.createTarget”, “params”: { “url”: “about:blank” } }

Result: { “id”: 1, “result”: { “targetId”: “418546565-d4f9-4d9f-8569-9ad8f9db7f9de” } }

Now we have to communicate with the tab, which requires a new websocket connection to: ws://

Before we can print anything we have to tell the browser where to load the content to print from. This may either be a URI (which then needs to be accessible for the browser) or raw HTML. I’ll stick to raw HTML here, since it’s the most flexible option anyway:

Call: { “id”: 2, “method”: “Page.setDocumentContent”, “params”: { “frameId”: “418546565-d4f9-4d9f-8569-9ad8f9db7f9de”, “html”: <html> } }

Result: { “id”: 2, “result”: { } }

The next step is the instruction to print the page’s content to PDF:

Call: { “id”: 3, “method”: “Page.printToPDF”, “params”: <object> }

Result: { “id: 3, “result”: { “data”: <string> } }

What parameters you can use to influence the generation (e.g. the layout) are outlined in the official documentation.

The string you will get back is probably encoded in Base64, so decode it and store it where you want. The PDF file has been successfully generated.

If you are planning to use a single process to generate multiple PDFs with, remember to clean up afterwards. (i.e. closing the tab) Otherwise you will soon have a memory usage issue.

Call: { “id”: 4, “method”: “Target.closeTarget”, “params”: { “targetId”: “418546565-d4f9-4d9f-8569-9ad8f9db7f9de” } }

Result: { “id”: 4, “result”: { “success”: <bool> } }

I hope this proves useful to you or convinces you to give Google Chrome a try to generate pretty PDFs. 🙂

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

Icinga Web 2 – More Goodies for Developers

Tuesday version 2.7 of Icinga Web 2 has been released and introduced some interesting new functionality for module developers. Now I’d like to tell you, my fellow colleague, some more details about this.


jQuery v3 – Migration Required

First a friendly warning. We’ve upgraded the jQuery version we ship to v3.4.1. This has previously been v2.1.0 so now with this major upgrade some deprecated methods and interfaces are gone.

Though, don’t worry, you don’t need to hurry to avoid everyone complaining your module is incompatible with v2.7. We also ship jQuery migrate now which ensures that the usage of removed methods/interfaces still works. It also emits console warnings if it detects such a usage. The warnings are not active by default. They only appear when using the non-minimized javascript code. Put _dev in your address bar to instruct Icinga Web 2 to serve the non-minimized version. (e.g. /icingaweb2/dashboard?_dev)

Then start using the front-end as usual. Interact with all widgets you’ve written your own Javascript for and look for console entries starting with JQMIGRATE. Any of these messages will only appear once, repeated usages are not reported. If you’ve got a warning then, consult jQuery migrate’s in order to get hints how to solve it.

jQuery migrate will be removed with Icinga Web 2 version 2.8.0. While this is still some time ahead, this (and the note in the upgrade documentation) is probably the only warning.


Persistence and Collapsible Containers

While we’re at it, let’s stay with the topic of Javascript. If you don’t already know about the localStorage and sessionStorage, it’s now time to inform yourself. (That’s an entire blogpost if described thoroughly)

There’s now an abstraction layer for this shipped with Icinga Web 2. It hides all the handling of complex datatypes and conflicts with other apps using the storage from you. That’s the object Icinga.Storage which utilizes the localStorage by default but also supports the sessionStorage. Take a look here to see how this is used for Icinga Web 2’s sidebar.

Though, this is only the basic stuff. If you need to store more complex data and want to benefit from a storage’s event processing, take a look at the object Icinga.Storage.StorageAwareMap. This is a proxy for Map and allows to subscribe to change events of particular keys in the map. It also keeps track of a key’s age and removes it automatically if it hasn’t been accessed for 90 days.

Another new addition are collapsible HTML containers. This is provided by a behavior which makes use of the StorageAwareMap, a perfect example use-case.

Making a container collapsible is as easy as possible. Just apply the CSS class collapsible and you’re done. If you’re not satisfied with the default height, apply the data attribute data-visible-height and give it the desired height in pixels. (For table‘s and ul‘s or ol‘s there is also data-visible-rows.) Then, if you fancy a custom control by which users expand or collapse the container you can pass a CSS selector to data-toggle-element which (if a direct descendant of the container) then acts as the toggle.


Custom XHR Without Dirty Hacks

Have you ever wanted/tried to process link clicks or form submissions by yourself? Well, I have and it was a nightmare every single time. Most of Icinga Web 2’s processing is fine. But of course there ever is this single behavior or side-effect which keeps getting in the way. This has now come to an end.

Meet data attribute data-no-icinga-ajax which does exactly what it’s name suggests. Applied to an element it causes Icinga Web 2 to ignore click and submit events triggered by the element itself and all descendants.

Couldn’t be more simple, can it?


Hooks For Everyone

Previously hooks were only processed for logged-in users with the permission to access the module providing the hook. This for example prevented the audit module to register logins from users without the permission module/audit and also didn’t allow to log failed logins.

When providing a hook it is now possible to have it run always. ($this->provideHook($name, $implementation = null, $alwaysRun = false);)

Another case of hooks not being processed was the issue that, unlike in the web front-end, enabled modules were not loaded automatically on the CLI. Thus also their hooks were not registered. Now this has changed and also on the CLI all enabled modules are automatically loaded. If you’ve previously loaded the modules explicitly this is not required anymore. If you don’t need any other modules and want to avoid the overhead of loading them, you can disable this of course.

If you still don’t have enough of this, there’s also an entirely new hook available: ConfigFormEventsHook This hook enables you to influence every configuration form of Icinga Web 2. Extending a form’s validation or doing additional work once submission succeeded is now on the table.


That’s it. I hope these things are as useful to you as they were to us. And remember, we don’t mind any suggestions to further improve the integration of modules. You are the developer, you’ll know best what’s… best.

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

Verschachtelte Listen mit Sortable.js

Nachdem ich vor einiger Zeit die Ehre hatte Drag & Drop im Business Process Modul für Icinga Web 2 zu integrieren, dachte ich mir ich plaudere heute mal ein wenig aus dem Nähkästchen welche Stolpersteine ich dabei überwinden musste.

Aber erst einmal eine kleine Vorstellung der Bibliothek die eingesetzt wurde: Sortable.js 

Die Entscheidung hierfür fiel erst nicht leicht, da das Angebot jener Bibliotheken die sich Drag & Drop verschrieben haben, nicht besonders klein ist. Aber wo wir gerade von klein reden, ist das bereits der wichtigste Punkt der Sortable.js von anderen abhebt. Weil sie nur das nötigste an Funktionalität abdeckt und keine fancy Animationen nutzt die nicht nur Leistung fressen sondern auch Dateigröße ist diese Bibliothek mit 25kB (minimized) eines der Leichtgewichte unter ihren Vertretern. Außerdem existiert keine Abhängigkeit zu jQueryUI, das alleine hat bereits überzeugt.

Nun aber zum eigentlichen Thema. Während Sortable.js wunderbar mit einfachen Listen arbeiten kann, wird es etwas anspruchsvoller wenn es um verschachtelte Listen geht. Glücklicherweise wurde in diesem Segment in den letzen Wochen einiges getan und die Unterstützung erheblich verbessert. Dennoch gibt es etwas das immer noch existiert und mir einige ruhelose Nächte bereitet hat. Gut, so schlimm war es nun auch wieder nicht, dennoch, es könnte ja einigen genauso wie mir nicht sofort wie Schuppen von den Augen fallen.

Aber erst einmal die Demo die das Problem darstellt. Versucht einmal alles aus der roten Liste zu entfernen und dann wieder Elemente aus der blauen hineinzuschieben. Demo

Klappt nicht so ganz? Tja, das liegt daran, dass die rote Liste zu klein ist sobald keine Elemente mehr enthalten sind. Der findige Leser denkt jetzt eventuell daran der Liste eine Mindesthöhe zu geben. Gar nicht so falsch. Demo

Klappt dennoch nicht? Hehe, willkommen im Club. Ein Blick in die von Sortable.js und man findet emptyInsertThreshold. Leider führt der Name dieser Option eher in die Irre, denn die Lösung ist sie nicht. Euer Blick sollte eher auf invertSwap fallen. Demo

Warum das nun funktioniert? Arr, das geht etwas über das Ziel des Beitrags hinaus. Ich kann euch aber folgendes ans Herz legen.

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

Was mein ist, bleibt mein

Auch wenn mich das als Egoist darstellt, sollte das jeder in Betracht ziehen. Jedenfalls wenn es um potentiell sensible Daten geht.

Ein Beispiel

Jeder der häufig im Internet unterwegs ist, sei es nun privat oder geschäftlich, kennt das. Eine Anmelde-Maske.

Wer auf Sicherheit wert legt, nutzt hoffentlich überall ein anderes Passwort. Selbstverständlich sind die auch in einem Passwort-Manager wie Keepass oder Enpass hinterlegt und mit einem sicheren Master-Passwort gesichert.

Aber mal ganz ehrlich, wer klickt im Browser bei der Frage “Soll dieses Passwort gespeichert werden?” nicht gerne auf Ja? Nun, ich hab es eine Zeit lang vermieden war aber jedes Mal traurig nicht auf Ja geklickt zu haben.

Warum? Weil ich eine üble Angewohnheit, wie viele andere wohl auch, habe.


Ich nutze ein Passwort niemals ein zweites Mal. Ich habe alle meine Passwörter in Keepass gespeichert. Diese Datenbank ist mit einem relativ komplexen jedoch noch leicht zu merkendem Master-Passwort versehen und mit meinem Yubikey gekoppelt. 2-Faktor Authentifizierung wie aus dem Buche.

Aber ich bin bequemlich. Jedes Mal Keepass aufzumachen und diese Prozedur durchzuführen, für jede Anmelde-Maske die ich im Laufe des Tages benutzen muss? Ein Graus. Selbstverständlich kann ich Keepass im Hintergrund offen lassen, aber das würde dem Gedanken der 2-Faktor Authenfizierung widersprechen. Sicher, Keepass schützt die Passwörter vor unerlaubten Speicherzugriffen und derlei Späßen, jedoch hab ich dennoch kein gutes Gefühl dabei.

Aber warum speichere ich dann nicht einfach die Passwörter mit Hilfe des Browsers? Werden die dort nicht auch sicher gespeichert? Na, selbstverständlich werden sie das. Doch das Problem ist ein ganz anderes.

Das schwache Glied

Die Frage ist nämlich nicht wie die Passwörter vom Browser gespeichert werden, sondern wie erneut auf sie zugegriffen wird.

Ich setze Ubuntu 18 ein. Hier werden derart gespeicherte Passwörter im GnuPG-Schlüsselbund hinterlegt. Dieser wird bei jedem Login auf dem System entsperrt. (Oder beim entsperren des Systems.)

Nun, der aufmerksame Leser wird sich nun denken können weshalb ich das als schwaches Glied in der Kette ansehe. Ich bin bequemlich, welch Überraschung. Wenn ich das System entsperren muss, möchte ich nicht erst ein super sicheres Passwort eintippen müssen. Erst recht nicht während jemand daneben steht/sitzt. Je komplexer das Passwort nämlich, desto langsamer tippe ich es. Je langsamer ich tippe, desto eher steigt die Gefahr mir schaut jemand dabei zu. (Kollegen vertraue ich selbstverständlich, aber man weiß ja nie wo man sonst ist.) Deshalb: Es ist ein einfaches Passwort das super schnell getippt ist.

Falls aber doch jemand, oder etwas, Kenntnis von diesem Passwort erlangt war alles für die Katz. Sofort sind alle Passwörter aus dem GnuPG-Schlüsselbund gefährdet. Da hilft nur eines.

Die Lücke schließen

Zum Glück hat meine Bequemlichkeit doch ihre Grenzen. Denn ich fahre mein DELL XPS 13 grundsätzlich immer herunter wenn ich es länger aus den Augen lasse.

Somit ist diese Lücke auf einen Schlag verschlossen sobald der gesamte Festplatten-Inhalt verschlüsselt ist. Und das ist er inzwischen. LUKS sei dank.

Auch hier kommt es auf die Qualität der gewählten Passwörter an, schließlich muss vor dem Start des Systems erst einmal alles entschlüsselt werden können. Aber Achtung: Ein zu schwaches Passwort ist erneut das schwache Glied.

Hier habe ich einen Kompromiss mit meiner Bequemlichkeit geschlossen. Ich habe zwei Möglichkeiten meine Festplatte zu entschlüsseln. Zum einen ein super sicheres Passwort (das nicht im Wörterbuch zu finden ist), zum anderen aber auch ein leicht zu merkendes. (Das aber auch nicht im Wörterbuch zu finden ist, jedenfalls nicht 1:1) Der Clou jedoch ist, das zweite (einfache) Passwort ist mit dem Yubikey gekoppelt.

Ein Hoch auf die 2-Faktor Authentifizerung

Man merkt es vielleicht. Ich bin ein Fan meines Yubikeys. Besser gesagt, meiner zwei Yubikeys. (Es könnte ja einer abhanden kommen) Auf die technischen Details gehe ich jetzt nicht mehr ein, das macht zum Teil bereits Marius. Doch kurz auflisten wofür ich ihn noch einsetze möchte ich:

  • GPG (Private subkeys auf dem Yubikey)
  • SSH (Dank GPG, Gunnar hatte hierzu bereits etwas geschrieben)
  • Github (FIDO U2F)

Außerdem ist mein zweiter (privater) Yubikey NFC fähig, ich kann ihn also super easy mit der Keepass App auf dem Smartphone nutzen.

Was mein ist, bleibt mein!

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

DELL XPS13 – Ein anständiges Fliegengewicht mit kleinerer Klappe

Dies ist die Fortsetzung zum zickigen Leichtgewicht mit großer Klappe.
Nun war es mal wieder soweit. Nach Jahren als einiger der wenigen die bei Meetings kein Macbook vor sich stehen hatten, habe ich nun erneut ein DELL XPS13 erhalten. Diesmal ist es das Modell 9370 (vormals 9343) in der FHD Ausführung. Oh ja, was vorher ein QHD+ war ist nun kleiner. Aber dafür viel angenehmer. Seit Ubuntu 14.04 hat sich zwar einiges getan hinsichtlich HiDPI Unterstützung, jedoch scheitert es immer noch meist an einzelnen Applikationen. Aus diesem Grund habe ich seit langem schon nicht die native Auflösung von 3200×1600 Bildpunkten betrieben, sondern wie auch mein zweiter Bildschirm mit 1920×1080 Bildpunkten. Allerdings war eine gewisse Unschärfe nicht zu verhindern.
Nunja, das neue Modell hat nun FHD als native Auflösung und jegliche Probleme mit Unschärfe, zu kleiner Schrift oder schrägen Skalierungs-Artefakten sind nun Geschichte. Geschichte ist außerdem der Touchscreen, aber den hab ich eh nie gebraucht. Was hingegen vollkommen neu ist:

  • Es wirkt leichter. Ich habs nicht nachgewogen, aber es wirkt eindeutig leichter.
  • 3 (!) USB-C Ports (Das waren vorher 2 USB-A Ports)
  • 4 statt 2 CPU-Kerne. Power satt. (Aber auch Hitze, dazu später mehr)
  • Ganze 16 GB RAM. (Vorher mit 8GB kam ich schon hin und wieder an meine Grenzen)
  • Mit 512 GB SSD doppelter Speicherplatz als vorher. (Jetzt werd ich wohl weniger oft VMs löschen)
  • Eine Infrarot Kamera. (Ist wohl ein Überbleibsel aus der Windows Variante, könnte noch nützlich werden)
  • Oh, und das Tastatur Layout. Ich nutze gerne Home, End, PageUp und PageDown. Jetzt muss ich dafür keine akrobatischen Kunststücke mit dem Function-key mehr vollziehen!

Wieder einmal war auch Ubuntu vorinstalliert. Da ich allerdings diesmal FDE (Full Disk Encryption) einsetzen wollte musste das runter. Zuerst hatte ich versucht mit Dell Recovery neu zu installieren. Schließlich hat Dell einen eigenen Kernel mit Plattform spezifischen Verbesserungen entwickelt. Dummerweise jedoch ist scheinbar genau jener Kernel (oder irgendwas anderes in diesem Paket) inkompatibel mit LUKS (Quelle), denn egal welches Passwort ich gewählt hatte (zuletzt “test”), nach abgeschlossener Installation wurde keines von LUKS als richtig erkannt.
Gut, also hieß es nun das normale Ubuntu 18.04 mit dem generic Kernel zu installieren. Und siehe da, es lief perfekt. Und so läuft es auch jetzt noch. Kaum zu glauben, ist aber wahr. Okay, vielleicht nicht perfekt, aber immerhin gut genug für mich. Bisher sind mir keine Fehler aufgefallen. All die Probleme die ich initial mit dem vorherigen Modell (9343) hatte, traten nicht auf. Kein Tastatur-Lag. Kein Touchpad-Ghosting. Sound ging sofort. Nichts. Nicht einmal mit dmesg sind grobe Fehler oder Warnungen zu entdecken. Ja sogar der Philips Monitor mit USB-C Dock-Funktionalität wird mitsamt der an ihn angeschlossenen Peripherie anstandslos erkannt.
Der einzige Wermutstropfen, wie eingangs schon erwähnt, ist die Hitze-Entwicklung. Ich habe noch nicht nachgesehen ob ich im UEFI die Lüfter konfigurieren kann, aber im Werkszustand drehen die leider bereits bei knapp über 55° lautstark auf. Wie laut kann ich nicht messen, aber es übertönt die sonst üblichen Geräusche im Büro. (Tastatur Klackern, knarrende Stühle, etc) Und hab ich mal PhpStorm und eine Centos-7 VM mit Icinga 2 und Icinga Web 2 laufen, werden die 55° schon recht oft überschritten. Dann blasen die Lüfter erst einmal für einige Minuten, bis ~43° erreicht sind.
Zu guter letzt habe ich heute mal nachgesehen was ich mit dieser ominösen Infrarot Kamera machen kann. Dabei erfahre ich, hätte ich Windows könnte ich diese mit Windows Hello koppeln. Hm, hab ich aber nicht, ich habe Ubuntu. Gut, gibt es Windows Hello Alternativen für Linux? Ja! Howdy! Auch ich musste schmunzeln bei diesem Namen. Erste Versuche führten auch recht schnell zum Erfolg. Jetzt kann ich einfach in die Kamera grinsen wenn ich im Login-Screen oder Lock-Screen bin. Oder mit sudo Kommandos ausführe. Oder im Ubuntu Software-Center etwas installiere. Kurz, dank PAM geht das einfach überall.

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

RT Extensions Made Easy

Heute geht es darum, wie man auf einfache Art und Weise Erweiterungen für Request Tracker vorbereitet. Einen kleinen Ausblick darauf wie man sie dann auch schreibt, gibt es am Ende auch, aber mehr würde den Rahmen dieses Posts sprengen.
Bevor wir beginnen, müssen jedoch erst einige Vorbereitungen erledigt werden:
# cpanm Module::Install::RTx Dist::Zilla::MintingProfile::RTx
Dies installiert einige Werkzeuge die wir für die folgenden Beispiele benötigen.
Außerdem bietet es sich an, ein paar grundlegende Informationen über die eigene Person zu konfigurieren. Das erspart uns später einige Angaben:
$ dzil setup

Wer sich fragt was man alles mögliche an Lizenzen angeben kann, darf hier einen Blick riskieren.
Schon kann es losgehen. Zuerst erstellen wir mit dem sogenannten “profile provider” RTx ein blankes Skelett.
Dies erstellt dort wo wir uns gerade befinden ein neues Verzeichnis mit dem Namen “RT-Extension-Netways”:
$ dzil new -P RTx RT-Extension-Netways

Die darin enthaltene Datei “” nun öffnen und entsprechend anpassen bzw. erweitern. Darunter fallen i.d.R. der Name, die Beschreibung, die RT Versions-Voraussetzungen und die Autoren Angabe. Der Rest sollte bereits größtenteils vorausgefüllt sein, wie schon zuvor erwähnt.
Außerdem ist es ratsam sich die Datei “Makefile.PL” einmal genauer anzusehen. Denn dort sind ebenfalls einige wichtige Angaben zu finden über deren Korrektheit man sich vergewissern sollte.
Hat man dies getan, kann man bereits mit der eigentlichen Entwicklung der Erweiterung beginnen.
Hierzu sei jedem die offizielle Dokumentation von Best Practical und HTML::Mason ans Herz gelegt.
Ist man letztendlich fertig mit der Entwicklung oder möchte schon einmal testen was man da tolles fabriziert hat, ist es an der Zeit die Verteilung seiner neuen Erweiterung vorzubereiten:
$ perl Makefile.PL

Da dies die erste Ausführung von “Makefile.PL” war, wurden einige für die Installation notwendige Bibliotheken in die Struktur integriert. Diese sind notwendig, damit Nutzer die Erweiterung zumindest installieren können, ohne zusätzlich notwendige Abhängigkeiten.
Außerdem wurden einige zusätzliche Dateien und Verzeichnisse angelegt. Diese jedoch sind nur ein Nebenprodukt und nicht notwendig für die Verteilung. (Darunter das “Makefile” und die “MYMETA.*” Dateien.) Was alles genau nicht notwendig ist, kann in der Datei “gitignore” nachgelesen werden. (Wer sowieso mit Git arbeitet, kann diese Datei auch zu “.gitignore” umbenennen.)
Nun folgt man nur noch den üblichen Schritten und schon kann die Erweiterung konfiguriert und genutzt/getestet werden:
$ make
# make install

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

Trainings mit Style

Wer bereits die Gelegenheit hatte an einem unserer Trainings teilzunehmen, dürfte bereits festgestellt haben, dass unsere Trainer keine schnöden Powerpoint Präsentationen verwenden.
Nein, sie verwenden showoff!
Markus hat bereits schon einmal darüber berichtet und showoff kurz vorgestellt, daher hier nur noch einmal eine kleine Zusammenfassung: showoff erlaubt es den Teilnehmern die Präsentation direkt in deren Browser nachzuverfolgen und auf weitere Inhalte zuzugreifen.
Bisher waren unsere Trainingsunterlagen jedoch von der showoff Version abhängig. Diese ist mittlerweile mehr als zwei Jahre alt und die neuere Version bietet einiges an neuen Funktionen und natürlich Lösungen für bisherige Probleme. Nachdem ich mich letzte Woche damit beschäftigte diese Versionsabhängigkeit zu neutralisieren bzw. zumindest auf die aktuelle (v0.19.3) anzuheben, sind mir ein paar neue Features aufgefallen von denen unsere Trainer und natürlich Teilnehmer profitieren.

Live Code Execution
Live Slide Anmerkungen

Wer von was profitiert, lass ich mal offen. 😀

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

Github Topics – Ein kleiner Blick in die Zukunft

Wir sind seit einiger Zeit damit beschäftigt, Icinga Exchange in neuem Licht erstrahlen zu lassen. Neben einem neuen Design wird es allerdings auch einige andere Änderungen und Neuerungen geben.
Eine dieser Neuerungen wird eine stärkere Github Integration sein. Wir werden unter anderem die bisher notwendige yaml Datei ablösen, indem wir Details über von Github synchronisierte Projekte über die API von Github abrufen. Dabei ist uns eine neue Funktion von Github ins Auge gefallen: Topics.
Diese ist seit Anfang des Jahres verfügbar und erlaubt es einem Repository bestimmte Begriffe zuzuordnen, ganz ähnlich den auf Icinga Exchange bekannten Tags. Mit der üblichen URL um Details zu einem Repository abzurufen wird man jedoch nicht fündig:<owner>/<repository>

Aktuell fand diese Funktion noch keinen Einzug in die offizielle API. Stattdessen muss man explizit angeben, eine bestimmte preview Version der API benutzen zu wollen, damit zugeordnete Topics in den Details ebenfalls erscheinen.
Dies erreicht man bei Github mit bestimmten media types im Accept header:


(Alle preview Versionen gibt es hier)
Um also über die API auf die Topics zuzugreifen, wird folgender media type benötigt:


In der Antwort der API erscheint daraufhin ein neuer Eintrag:

  "topics": [

Weil diese Funktion aber noch nicht Bestandteil der offiziellen API ist, werden wir vermutlich vorerst davon absehen diese in Icinga Exchange zu benutzen. Wir hoffen jedoch, dass sie bald Bestandteil der offiziellen wird oder absehbar ist, dass sie es sicher werden wird.
Ach, bevor Ihr fragt: Nein, wir haben noch keinen Release Zeitpunkt für die neue Version von Icinga Exchange. Aber vermutlich noch dieses Jahr. 😛

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

Icinga Web 2 – Das gefällt jedem

Und wenn doch nicht, kann man es ganz einfach seinen eigenen Design-Vorstellungen entsprechend anpassen, denn darum geht es heute:


Wer es bis jetzt noch nicht entdeckt hat, darf nun erst einmal im Menü auf seinen Benutzernamen klicken und die Konto-Einstellungen öffnen. Dort versteckt sich seit v2.1.1 eine Möglichkeit das Theme, in dem Icinga Web 2 erstrahlt, zu ändern. (Sofern diese Möglichkeit nicht vom Administrator deaktiviert wurde.) Diese Themes werden teilweise von Icinga Web 2, teilweise von Modulen bereit gestellt können aber auch vom System-Administrator eingepflegt werden.
Von Haus aus stellt aktuell nur Icinga Web 2 zwei Themes bereit: High-Contrast und Winter.
Das High-Contrast Theme, wie der Name schon suggeriert, erhöht den Kontrast der Farben in Icinga Web 2. Es ist dafür gedacht die Barrierefreiheit zu erhöhen. Das Winter Theme hingegen ist einfach nur eine Spielerei für die kalten Winter-Tage.
Würde es aber heute nur um die einfache Nutzung der Themes gehen, wären wir nun schon wieder am Ende angelangt. Aber das wäre ja langweilig und für viele sicher auch nichts neues, nicht? Deshalb:

Eigene Themes erstellen

Erst einmal ein paar Grundsätze:

  • Globale Themes liegen hier: icingaweb2-installation/public/css/themes/
  • Modul Themes liegen hier: modul-installation/public/css/themes/
  • Der Name eines Themes wird direkt vom Dateinamen abgeleitet
  • Themes müssen die Datei-Endung “less” haben
  • Geschrieben wird ein Theme mit CSS oder LESS

Auf CSS oder LESS gehen wir nun nicht näher ein, das wäre zu viel des Guten. Interessant allerdings dürfte sein, wie Stylesheets in Icinga Web 2 behandelt werden und was in den einzelnen bereits mitgelieferten Dateien enthalten ist.

Stylesheets in Icinga Web 2

Alle Stylesheets in den oben erwähnten Verzeichnissen, werden automatisch von Icinga Web 2 in eine einzelne Datei zusammengefasst und an den Browser ausgeliefert:

  • Mit Originaler (lesbarer) Formatierung: /icingaweb2/css/icinga.css
  • Keine Formatierung (Komprimiert): /icingaweb2/css/icinga.min.css

Da die Stylesheets von Icinga Web 2 immer zuerst kommen und danach die aller Module, können Themes (da sie als letztes kommen) alles andere beeinflussen.
Prinzipiell solltet ihr euch einfach einmal alle mitgelieferten Stylesheets ansehen: icingaweb2-installation/public/css/icinga/
Aber um den Überblick und die Orientierung etwas zu vereinfachen, ist hier eine grobe Zusammenfassung zu den wichtigsten Dateien:

  • base.less
    Allgemeine Regeln und alle globalen Farb-Variablen
  • login.less
    Regeln für den Login, inklusive welches Logo verwendet wird
  • layout.less
    Regeln für das globale Layout (Header, Container, Footer)
  • menu.less
    Regeln für das Haupt-Menü
  • forms.less
    Regeln für alle Formulare
  • tabs.less
    Regeln für die Tabs
  • controls.less
    Speziellere Regeln für Form-Elemente bzw. “Widgets”

Jetzt könnt ihr bereits loslegen. Einfach eine neue Datei in einem der oben genannten Pfade anlegen (je nachdem ob es sich um ein Modul handelt, oder nicht) und fröhlich drauf los stylen. Treten Probleme auf oder ihr möchtet mit den Developer-Tools des Browsers die Regeln inspizieren, kann es hilfreich sein den “_dev” URL-Parameter an die URL anzuhängen. (z.B. /icingaweb2/dashboard?_dev) Dies führt dazu, dass das Stylesheet nicht komprimiert ausgeliefert wird.
Das wars dann nun aber. Falls noch Fragen aufkommen, verweise ich auf das Forum. Frohes schaffen!

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.

atexit, oder wie man Python-Dienste nicht beenden sollte

Wer schon einmal einen Dienst mit Python realisiert hat, wird bereits vor der Aufgabe gestanden haben die Aufgaben die er verrichtet sauber und geordnet zu beenden. Python bietet einem hier vielerlei Lösungen an, darunter auch das atexit Modul. Für schnelle und simple Aufräum-Arbeiten ist dieses Modul wunderbar geeignet, nicht jedoch wenn Thread-Synchronisation und externe Kommunikation im Spiel ist. Warum das so ist und was die saubere Alternative ist, darum geht es heute in diesem Blogpost.
Wie der Name des Moduls und dessen Dokumentation bereits sagt, werden registrierte Routinen ausgeführt kurz bevor der Interpreter angehalten wird. Klingt erst einmal nicht besonders problematisch, ist es doch gerade was man haben möchte. Und es funktioniert sogar, solange keine Threads laufen. Dann werden die registrierten Routinen nicht aufgerufen. Das liegt daran, dass “normale” Threads explizit beendet werden müssen, sonst verhindern diese den Stopp des Interpreters. Damit das nicht passiert, kommt man möglicherweise auf folgende Idee:

t = threading.Thread(target=func)
t.daemon = True  # Avoids that python hangs during shutdown

Ganz schlecht. Das mag möglicherweise den gewünschten Effekt haben und die Aufräum-Routinen können ihre Arbeit verrichten. Aber tatsächlich ist dies nur eine Lösung für ein Symptom, das eigentliche Problem ist noch immer nicht gelöst. Das wird einem spätestens klar, wenn es nicht mehr nur darum geht Threads zu beenden, sondern auch noch mit anderen über das Netzwerk verbundenen Diensten/Klienten eine saubere Unterbrechung der Kommunikation einzuleiten.
Denn was genau passiert wenn der Interpreter angehalten wird?

  1. Der MainThread wird sofort angehalten
  2. Offene File-Handles werden geschlossen
  3. Es wird gewartet dass alle nicht-Daemon Threads beendet wurden
  4. Die atexit-Routinen werden ausgeführt
  5. Garbage Collection
  6. Der Prozess wird beendet

Der zweite Punkt lässt einige vielleicht bereits aufhorchen, denn sockets sind nichts anderes als File-Handles. Wer nun immer noch denkt er könne in einem daemon-Thread die Netzwerk-Kommunikation mit seinem Gegenüber sauber beenden, ist auf dem Holzweg. Zum Zeitpunkt zu dem die atexit-Routinen laufen, sind bereits alle sockets unwiderruflich geschlossen.
Angenommen der Stopp des Dienstes wird ganz normal über SIGTERM initiiert, so könnte man nun alle atexit-Routinen in einen Signal-Handler verlagern. Dadurch würden sie laufen noch bevor der Interpreter angehalten wird, allerdings kommt man so sehr schnell wieder in die Bredouille, je nachdem welche Art von Arbeit der MainThread verrichtet. Denn da Signal-Handler asynchron im MainThread ausgeführt werden, ist das Risiko für ein Deadlock sehr groß. Kommen wir also zur erwähnten, sauberen Alternative: Feuer mit Feuer bekämpfen.
Was vormals in etwa so aussah:

class SomeDaemon:
    def start():
        # ...
        signal.signal(signal.SIGTERM, self._sigterm)
        # ...
    def _sigterm(self, signum, frame):
    def _atexit(self):
        # Alle Aufräum-Arbeiten

Kann ganz einfach, mit durchschlagendem Erfolg, so umgebaut werden:

class SomeDaemon:
    def start():
        # ...
        signal.signal(signal.SIGTERM, self._sigterm)
        # ...
    def _sigterm(self, signum, frame):
        threading.Thread(target=self._cleanup, name='CleanupThread').start()
    def _cleanup(self):
        # Komplexe Aufräum-Arbeiten
    def _atexit(self):
        # Einfache Aufräum-Arbeiten

Der “CleanupThread” sollte selbstverständlich keine Arbeit verrichten die unkontrolliert blockt. Denn dann sieht das ganze am Ende wieder so aus:

    def _sigterm(self, signum, frame):
        t = threading.Thread(target=self._cleanup, name='CleanupThread')
        t.daemon = True

Und der ganze Spaß geht von vorne los..

Johannes Meyer
Johannes Meyer

Johannes ist seit 2011 bei uns und hilft bei der Entwicklung zukünftiger Knüller (Icinga2, Icinga Web 2, ...) aus dem Hause NETWAYS.