Aus Linux-Magazin 06/2023

Basis-Kurs Ansible: Eigene Module schreiben

© Mark Bowden / 123RF.com

Benötigt man eine Funktion, die kein existierendes Ansible-Modul bereitstellt, heißt es selbst Hand anlegen. Mit Python-Grundkenntnissen ist das zum Glück nicht schwer.

Unter den auf dem Markt verfügbaren Automatisierern ist Ansible nach einhelliger Meinung derjenige, der sich am leichtesten erlernen lässt. Die ersten beiden Teile unseres Ansible-Workshops haben das bewiesen: Nach der Installation der benötigten Pakete erzielt man in wenigen Minuten einen Zustand, aus dem heraus man bereits die Hosts als Automatisierungsziele erreicht. Wer dann noch die für den eigenen Einsatzzweck passenden Ansible-Rollen aus dem Netz bezieht, hat im besten Fall binnen eines Tags eine funktionierende Automation, die sich gut nachvollziehen lässt und sich dank der Ansible-Syntax praktisch auch noch selbst dokumentiert.

Hinzu kommt, dass Ansible schon ab Werk eine Vielzahl sogenannter Module mitbringt. Sie bieten sämtliche Funktionen an, die die Ansible-Rollen später aufrufen, zum Beispiel die Funktionen »lineinfile« und »systemd«. Zudem ist Ansible nicht auf diese Standardfunktionen beschränkt. Im Gegenteil: Über die Jahre haben sich in seiner Funktionsbibliothek Tausende Funktionen angesammelt, mit denen sich die allermeisten Standardprogramme zuverlässig steuern lassen.

In der Praxis kommt es trotzdem vor, dass Ansible Dinge erledigen soll, für die die Community keine passenden Module anbietet. Das kommt besonders bei lokaler Software vor, die den Weg in die Community gar nicht erst gefunden hat. Neben Eigenentwicklungen umfasst das auch proprietäre Programme, die Ansible steuern soll, die aber selbst keine Community haben.

Verschiedene Ansätze

Hat man es mit solch einem Stück Software zu tun, ergeben sich verschiedene Möglichkeiten. Die offensichtlichste Option besteht darin, dem Programm mit den Standardfunktionen von Ansible zu Leibe zu rücken, die das Ausführen von Befehlen auf der Kommandozeile ermöglichen.

Ein Mittel der Wahl wäre das selbst als Modul implementierte »command«. Sonderlich elegant erscheint dieser Ansatz aber nicht: Einerseits steht in »command« nur das zur Verfügung, was auf dem jeweiligen Zielsystem als Binary irgendwo in »PATH« liegt. Um so mit einem Werkzeug zu interagieren, muss es also bereits eine CLI-Implementierung für die Steuerung geben. Das ist bei modernen Diensten oft aber nicht der Fall, denn die wollen per API gesteuert werden.

Selbst wenn für einen API-basierten Dienst ein CLI-Werkzeug zur Verfügung steht, liegt das nicht automatisch bereits auf allen Zielsystemen vor. Man müsste sich dann behelfen, indem man es zunächst installiert. Den meisten Admins widerstrebt es jedoch, für singuläre Aufrufe einzelner Programme Pakete mit etlichen Abhängigkeiten zu installieren.

Die Lösung über CLI-Aufrufe hat noch andere unangenehme Nebenwirkungen. So ist es in diesem Kontext etwa verhältnismäßig komplex, die Ausgabe von CLI-Werkzeugen aufzufangen und adäquat auszuwerten. Das gilt insbesondere für Fehlermeldungen: Um sie müsste man in der jeweiligen Ansible-Rolle einen komplexen Wrapper bauen, der die Meldung entsprechend ausgibt, statt sie einfach verschwinden zu lassen.

Viel eleganter erscheint es, jene Mittel zu nutzen, die Ansible ab Werk bietet, um ein eigenes Modul für die Steuerung des jeweiligen Diensts zu schaffen. Das klingt recht komplex, ist im Grunde aber nicht schwierig. Dazu muss man lediglich ein paar Brocken Python beherrschen, denn Ansible erwartet seine Module grundsätzlich in dieser Sprache.

Wer diese Bedingung erfüllt, kommt schnell ans Ziel. Ansible macht überhaupt keine Vorgaben hinsichtlich der Frage, wo das jeweilige Zusatzmodul liegt. Der Administrator kann es in den Bibliotheksordner seines lokalen Ansible-Repositorys packen, statt es wie bei anderen Automatisierern mühsam irgendwo im Dateisystem an der richtigen Stelle abzulegen. Das spart Zeit, Aufwand und hält die Systeme sauber, auf denen der Administrator Kommandos wie »ansible-playbook« aufruft. Wichtig ist dabei nur, dass der Name der Datei dem Kommando entspricht, das es in Ansible später aufzurufen gilt. In den folgenden Beispielen heißt das Modul »github_repo«, sodass die Datei letztlich in »library/github_repo.py« landen soll. Der Funktionsaufruf aus Ansible heraus lautet später »github_repo«.

Dieser Artikel zeigt, wie Sie ein Modul erstellen, das die Existenz eines Repositorys in Github erzwingt oder es löscht. Unser Exempel basiert auf einem Beispiel von Christo Crampton, der selbst etliche Module und Rollen für Ansible verfasst hat. Es nutzt den API-Dienst von Github (Abbildung 1) unter https://api.github.com. Die Details zu den benötigten API-Aufrufen entstammen unmittelbar der API-Dokumentation des Diensts. Im Kern funktioniert das Modul folgendermaßen: Aus verschiedenen Angaben wie dem zu nutzenden Account, dem Namen des Github-Verzeichnisses sowie diversen Parametern, die die Github-API beim Anlegen von Repos akzeptiert, konstruiert es die jeweilige URL. Die sendet es an die Github-API, deren Antwort es dann ausgibt.

Abbildung 1: Die exzellent dokumentierte Github-API lässt sich maschinell ansprechen. Damit ist sie das perfekte Beispiel für das Erstellen eines eigenen Ansible-Moduls, das Verzeichnisse in Github anlegen oder löschen kann.

Abbildung 1: Die exzellent dokumentierte Github-API lässt sich maschinell ansprechen. Damit ist sie das perfekte Beispiel für das Erstellen eines eigenen Ansible-Moduls, das Verzeichnisse in Github anlegen oder löschen kann.

Aller Anfang

Dass Ansible einen Codeschnipsel überhaupt als Modul für die eigene Verwendung erkennt, erfordert einen Header. Neben dem üblichen Python-Shebang muss er zumindest das Ansible-Modul »ansible.module_utils.basic« importieren und eine »main()«-Funktion haben. Die sollte das Ansible-Modul über die Funktion »AnsibleModule()« definieren und dabei sicherstellen, dass das Modul einen adäquaten Ausgabewert liefert. Listing 1 zeigt, wie ein solches Grundmodul (»library/github_repo.py«) aussehen kann.

Listing 1

Ansible-Grundmodul

#!/usr/bin/python
from ansible.module_utils.basic import *
def main():
  module = AnsibleModule(argument_spec={})
  response = {"hello": "world"}
  module.exit_json(changed=False, meta=response)
if __name__ == '__main__':
  main()

Wichtig: Die letzten zwei Zeilen des Listings gehören nicht zu »main()«, das wir im Lauf des Artikels noch mehrmals verändern werden. Die letzten beiden Zeilen der Datei bleiben aber stets unverändert. Der gezeigte Code enthält auch eine Art Marker, um die Funktionalität des Moduls zu testen. Mit dem Playbook »play.yml« aus Listing 2 lässt sich das Modul nämlich bereits aufrufen.

Listing 2

Test des Moduls

- hosts: localhost
  tasks:
    - name: Test that my module works
      github_repo:
      register: result
    - debug: var=result

Der Aufruf »ansible-playbook play.yml« nimmt noch keinen Kontakt zu Github auf, sollte in der Ausgabe aber in den Metadaten den Wert »world« für das Schlüsselwort »hello« enthalten (Abbildung 2). Ist das der Fall, haben Sie den Eintrittspunkt für Ansible in Ihrem eigenen Modul bereits erfolgreich angelegt. Damit beweist Ansible einmal mehr, dass sich selbst zentrale Funktionen mit relativ wenig Code nutzen lassen.

Abbildung 2: F&uuml;hrt man den Code aus <a href="#artRef-l1">Listing&nbsp;1</a> auf einem System aus, l&auml;sst sich damit demonstrieren, wie Ansible Module integriert und aufruft. Die Ausgabe von &raquo;"hello": "world"&laquo; ist der relevante Teil.

Abbildung 2: Führt man den Code aus Listing 1 auf einem System aus, lässt sich damit demonstrieren, wie Ansible Module integriert und aufruft. Die Ausgabe von »”hello”: “world”« ist der relevante Teil.

Nun geht es noch darum, dem bisher nur generischen Modul Leben einzuhauchen. Das geschieht in mehreren Stufen.

Eingaben handhaben

So gut wie jedes Ansible-Modul benötigt auf irgendeine Weise Input von außen, damit es sinnvolle Dinge anstellen kann. Freilich ließen sich Parameter auch statisch direkt in das Modul integrieren. Damit bringt man sich aber um die Macht der Variablen und hält den Nutzbarkeitsradius des Moduls sehr klein.

Sinnvoller ist es, dem Modul den Umgang mit Variablen beizubringen. Dazu erhält die zuvor schon angelegte »main()«-Definition ein Array »fields«, auf das später in Rollen und Playbooks verwiesen wird (Listing 3). Die Definition von »fields« gehört noch vor dem Aufruf »module =« in die Definition der »main()«-Funktion.

Listing 3

Eingabewerte handhaben

fields = {
  "github_auth_key": {"required": True, "type": "str"},
  "github_account": {"required": False, "type": "str"},
  "name": {"required": True, "type": "str"},
  "description": {"required": False, "type": "str"},
  "private": {"default": False, "type": "bool"},
  "has_issues": {"default": True, "type": "bool"},
  "has_wiki": {"default": True, "type": "bool"},
  "has_downloads": {"default": True, "type": "bool"},
  "state": {
    "default": "present",
    "choices": ['present', 'absent'],
    "type": 'str'
  },
}
choice_map = {
  "present": github_repo_present,
  "absent": github_repo_absent,
}

Die »choice_map«-Angabe wird später noch von Interesse sein: Sie verweist auf die zwei noch zu definierenden Funktionen »github_repo_present()« und »github_repo_absent()«. Sie wird später aus »main()« heraus über eine »get«-Direktive aufgerufen, praktisch also ausgelesen. Damit das funktioniert, müssen Sie den Codeblock aus Listing 1 so abwandeln, dass die Funktion »AnsibleModule()« das »fields«-Array mit den übergebenen Parametern befüllt. Danach ruft sie die gewählte Funktion aus »choice_map« auf und gibt je nach deren Rückgabewert entweder das erfolgreiche Löschen oder das erfolgreiche Anlegen eines Repositorys als Rückgabewert aus. Den Code der »main()«-Funktion aus Listing 1 ersetzen Sie entsprechend durch den aus Listing 3 und Listing 4.

Listing 4

Funktionsaufruf im Modul

module = AnsibleModule(argument_spec=fields)
is_error, has_changed, result = choice_map.get(module.params['state'])(module.params)
  if not is_error:
    module.exit_json(changed=has_changed, meta=result)
  else:
   module.fail_json(msg="Error deleting repo", meta=result)

Die beiden Anweisungen am Ende des Codeblocks, also das If-Else-Konstrukt, dienen der Fehlerbehandlung: Kommt es zu einer Fehlermeldung, gibt Ansible sie wie gewohnt aus. Dabei beachtet es etwaige Konfigurationen wie die Debugging-Einstellung und die Direktive, die Log-Output aus Compliance-Gründen vollständig unterdrückt.

Aktionen definieren

Bis hierhin ist das Modul noch immer ein zahnloser Tiger. Es kann zwar Parameter aus den übergebenen Variablen einer Rolle oder eines Playbooks übernehmen, doch verweist die »main()«-Funktion auf die beiden Funktionen »github_repo_absent()« und »github_repo_present()«, die es noch nicht gibt. Würde man das Modul nun also aus Ansible heraus aufrufen, wäre eine Fehlermeldung der Mühe Lohn.

An diesem Beispiel zeigt sich besonders gut, dass gerade lokale Funktionen die eigentliche Magie sind, die das Modul ausmachen. Beim bis hierhin gezeigten Code handelt es sich im Wesentlichen um ein Skelett, in dessen Rahmen sich (möglicherweise mit wenigen Anpassungen) auch jede andere Funktion in Ansible umsetzen ließe. Sonderlich viel echtes Python war bis hierhin obendrein noch nicht im Spiel. Das ändert sich allerdings im nächsten Schritt.

Verzeichnisse anlegen

Im Fokus steht zunächst die Funktion »github_repo_present()«, über die das Modul Verzeichnisse in Github anlegt. Wer sattelfest in Python ist, kann die Instruktionen aus Listing 5 schnell nachvollziehen.

Listing 5

Github-Verzeichnisse anlegen

def github_repo_present(data):
  api_key = data['github_auth_key']
  del data['state']
  del data['github_auth_key']
  headers = {
    "Authorization": "token {}" . format(api_key)
  }
  url = "{}{}" . format(api_url, '/user/repos')
  result = requests.post(url, json.dumps(data), headers=headers)
  if result.status_code == 201:
    return False, True, result.json()
  if result.status_code == 422:
    return False, False, result.json()
  # default: something went wrong
  meta = {"status": result.status_code, 'response': result.json()}
  return True, False, meta

Für alle anderen seien sie kurz erläutert: Die Funktion bekommt in Form von »data« aus dem Aufruf in »main()« heraus alle Parameter mit, die Sie dem Modul übergeben haben. Danach definiert sie die Variable »headers«, in der der Github-Authentifizierungscode für den jeweiligen Benutzer hinterlegt sein muss. Den erhalten Sie aus den Einstellungen Ihres Github-Accounts.

Über das Python-Modul »requests« generiert das Ansible-Modul danach einen kompletten HTTP-Aufruf für Githubs ReST-API. Er enthält sowohl sämtliche Inhalte aus dem »data«-Array als auch die Header, die Sie zuvor mittels der »headers«-Anweisung hinterlegt haben. Der Rest des Moduls widmet sich bloß noch der Behandlung von Fehlern: Ein Rückgabewert von »201« zeugt vom erfolgreichen Anlegen des Moduls. Geht unterwegs etwas schief, gibt das Modul die entsprechenden Fehlermeldungen aus.

Geübten Augen entgeht nicht, dass im Modul selbst – also außerhalb aller Funktionen – noch zwei Dinge zu erledigen bleiben. Einerseits nutzt die Funktion eine bis dato noch nicht definierte Variable namens »api_url«, andererseits kommt das schon erwähnte Python-Modul »requests« zum Einsatz. Bis dato lädt das Ansible-Modul dieses Python-Modul aber noch nicht, exportiert seine Funktionen also nicht. Die Lösung des Problems besteht darin, in Listing 1 unmittelbar unter der From-Zeile die Zeilen aus Listing 6 einzufügen. Danach lässt sich die Funktion verwenden.

Listing 6

Modul laden

<pre>
import requests
api_url = "https://api.github.com"
<pre>

Verzeichnisse löschen

Langsam biegt unser Ansible-Modul damit bereits auf die Zielgerade ein. Noch fehlt allerdings die zweite Funktion »github_repo_absent()«, die Verzeichnisse löschen kann. Die plagt jedoch eine unangenehme Eigenschaft der Github-API: Die erkennt den eingehenden Benutzerzugang anhand seines API-Keys und legt Verzeichnisse automatisch in dem Github-Account an, der zu dem API-Schlüssel passt. Deshalb ist es in Listing 5 auch nicht nötig, im Pfad der Github-URL den Benutzernamen anzugeben. Stattdessen kommt hier gemäß der Github-API »/user/repos« zum Einsatz.

Dieser Automatismus funktioniert beim Löschen von Verzeichnissen jedoch nicht. Hier müssen Sie den Github-Account, innerhalb dessen Sie ein Repository löschen möchten, explizit angeben (Abbildung 3). Das liegt daran, dass ein Github-Account ja durchaus Zugriff auf zahlreiche Verzeichnisse haben kann. Die zu generierende URL gerät im Beispiel der Löschfunktion deshalb etwas komplexer. Listing 7 zeigt den kompletten Code.

Abbildung 3: Die Funktion zum Anlegen von Verzeichnissen in Github bezieht den Namen des Accounts aus dem API-Schl&uuml;ssel. Die Funktion zum L&ouml;schen von Verzeichnissen kann das nicht, hier ist ein zus&auml;tzlicher Parameter n&ouml;tig.

Abbildung 3: Die Funktion zum Anlegen von Verzeichnissen in Github bezieht den Namen des Accounts aus dem API-Schlüssel. Die Funktion zum Löschen von Verzeichnissen kann das nicht, hier ist ein zusätzlicher Parameter nötig.

Listing 7

Github-Verzeichnisse löschen

def github_repo_absent(data=None):
  headers = {
    "Authorization": "token {}" . format(data['github_auth_key'])
  }
  url = "{}/repos/{}/{}" . format(api_url, data['github_account'], data['name'])
  result = requests.delete(url, headers=headers)
  if result.status_code == 204:
    return False, True, {"status": "SUCCESS"}
  if result.status_code == 404:
    result = {"status": result.status_code, "data": result.json()}
    return False, False, result
  else:
    result = {"status": result.status_code, "data": result.json()}
    return True, False, result

Praktisch unterscheiden sich die beiden Funktionen nur in Details voneinander. Die gesamte Fehlerbehandlung funktioniert weitgehend identisch. Allerdings liefert Github bei Löschoperationen unter Umständen andere Rückgabewerte als beim Anlegen. Entsprechend horcht die Löschfunktion auf die HTTP-Rückgabewerte »204« und »404«.

Viel wichtiger ist allerdings die »url«-Zeile, die anhand der API-Vorgaben von Github eine passende URL zusammenzimmert. Dabei übernimmt sie auch den Namen des Repos aus dem Playbook sowie den Namen des Github-Accounts, zu dem das zu löschende Repository gehört.

Die Klammerkonstruktion bei »url« mutet etwas seltsam an, ist aber leicht erklärt: In die drei geschweiften Klammernpaare fügt Python automatisch die Inhalte ein, die die Funktion »format()« aus ihren drei Parametern generiert. Hier setzt Python also in der ersten geschweiften Klammer den Inhalt der Variablen »api_url« ein. Im zweiten Klammernpaar landet der Inhalt von »github_account« aus den Parametern, mit denen Sie das Modul aufgerufen haben. In die letzte geschweifte Klammer kommt der Inhalt von »name« als Parameter für das Ansible-Modul.

Dokumentation

Damit haben Sie den auszuführenden Code des Moduls fertiggestellt. Nun fehlt nur noch eine gerade innerhalb der Ansible-Community gern gesehene Dokumentation. Ansible hält dafür sogar ein eigenes Format bereit: Geben Module die Variablen »EXAMPLES« und »DOCUMENTATION« aus, kann Ansible deren Inhalte beispielsweise auf der Kommandozeile darstellen.

Es empfiehlt sich, in den Beispielen zumindest den kompletten Aufruf als Exempel zu integrieren. »DOCUMENTATION« sollte wenigstens den Namen des Moduls sowie eine Kurzbeschreibung enthalten. In Summe könnte das so aussehen wie in Listing 8. Der entsprechende Code gehört unmittelbar hinter den Python-Aufruf in der ersten Zeile des Moduls, also noch vor die From-Zeile, die das Ansible-Basics-Modul importiert.

Listing 8

Moduldokumentation

DOCUMENTATION = '''
---
module: github_repo
short_description: Create or delete GitHub repositories
'''
EXAMPLES = '''
- name: Create a GitHub Repo
 github_repo:
  github_auth_key: "..."
  github_account: "..."
  name: "linux-magazin-beispiel"
  description: "Beispiel-Repository für das Linux-Magazin"
  private: yes
  has_issues: no
  has_wiki: no
  has_downloads: no
 register: result
- name: Delete a GitHub repo
 github_repo:
  github_auth_key: "..."
  name: "linux-magazin-beispiel"
  state: absent
 register: result
'''

Damit ist das Modul fertig. Legen Sie nun noch ein Playbook oder eine Rolle in Ansible an, können Sie aus ihr heraus wie beschrieben »github_repo()« mit den benötigten Parametern aufrufen.

Im Modul gibt es noch an ein paar Stellen Optimierungsoptionen, die den Rahmen dieses Artikels allerdings sprengen würden. So wäre es beispielsweise sinnvoll, sämtliche Rückgabewerte der Github-API abzufangen und zu verarbeiten. Auf diese Weise ließen sich deutlich mehr Fehler erkennen als mit dem hier vorgestellten Code (Abbildung 4).

Daneben gibt es auch inhaltliche Verbesserungsmöglichkeiten. Bei einem Aufruf zum Anlegen eines Repositorys könnte »github_repo()« zunächst prüfen, ob das Repository bereits existiert. Statt in einen Fehler zu laufen, könnte es dann die Einstellungen dieses Repos an die Vorgaben aus dem Ansible-Aufruf anpassen. Das wäre sowohl über eine eigene Option »update_repo()« möglich als auch unmittelbar aus »github_create_repo()« heraus.

Abbildung 4: Die Github-Dokumentation listet f&uuml;r das Anlegen von Verzeichnissen etliche m&ouml;gliche R&uuml;ckgabewerte auf, von denen das Modul aktuell aber nur wenige abf&auml;ngt. Hier g&auml;be es also Verbesserungspotenzial.

Abbildung 4: Die Github-Dokumentation listet für das Anlegen von Verzeichnissen etliche mögliche Rückgabewerte auf, von denen das Modul aktuell aber nur wenige abfängt. Hier gäbe es also Verbesserungspotenzial.

In der Praxis

Tatsächlich müssen Sie für Ansible keineswegs ein eigenes Modul für das Handling von Github-Verzeichnissen schreiben. Der Automatisierer bringt bereits eines mit, das ausgezeichnet funktioniert. Es ist allerdings – wie die meisten fertigen Ansible-Module – lang und komplex und deshalb als Exempel nur bedingt zu gebrauchen.

Dennoch eignet sich das Github-Beispiel hervorragend als Studienobjekt für das Erstellen eigener Ansible-Module. Das liegt daran, dass die Github-API gut und vollständig dokumentiert ist. Daher bereitet es keinerlei Schwierigkeiten, in Python per »requests« URL-Requests zusammenzustellen. Das gilt jedoch nicht für alle APIs.

Innerhalb eines Python-Moduls können Sie in Ansible praktisch alles ausführen, was auch Ansible selbst auszuführen vermag. Sie sind also keineswegs auf die Kommunikation mit API-Schnittstellen beschränkt. Ein Beispiel aus der echten Welt demonstriert das eindrucksvoll: Um PowerDNS mit Ansible zu steuern, benötigen Sie etliche CLI-Befehle aus der PowerDNS-Welt.

Jan-Piet Mens, selbst überzeugter PowerDNS-Nutzer, hat der Ansible-Rolle für PowerDNS aus diesem Grund ein komplettes Modul [1] spendiert (Abbildung 5). Es überträgt einen großen Teil der bestehenden CLI-Befehle so, dass sie sich in Ansible als Funktion nutzen lassen. Der Mühe Lohn sind eine umfassende Fehlerbehandlung, eine Plausibilitätsprüfung für Eingabewerte und das gute Gefühl, in Ansible selbst nicht mit »command« und anderen vorzeitlichen Methoden agieren zu müssen.

Abbildung 5: Wie komplex Ansible-Module werden k&ouml;nnen, zeigt das Beispiel von Jan-Piet Mens f&uuml;r PowerDNS. Es steuert PowerDNS &uuml;ber dessen eigene APIs mit einer Vielzahl von Optionen.

Abbildung 5: Wie komplex Ansible-Module werden können, zeigt das Beispiel von Jan-Piet Mens für PowerDNS. Es steuert PowerDNS über dessen eigene APIs mit einer Vielzahl von Optionen.

Die Kehrseite der Medaille ist freilich, dass das pDNS-Modul für Ansible schon deutlich komplizierter ausfällt als das aus unserem Beispiel. Neben API-Anfragen kommen hier auch Requests über das PowerDNS-Protokoll an die Dienste vor, die schwieriger zu konstruieren sind. Obendrein benötigt das PowerDNS-Modul eine wesentlich ausgefeiltere Fehlerbehandlung: Im operativen Alltag ist es eben nicht egal, wenn eine DNS-Änderung schiefgeht.

Fazit

Ein paar grundlegende Python-Kenntnisse vorausgesetzt, ist das Hinzufügen eigener Funktionen in Ansible absolut kein Hexenwerk. Dass der Automatisierer eine native Schnittstelle zu Python bietet, schafft eine einfache Option für lokale Module.

Allerdings kann Ansible nicht verhindern, dass solche lokalen Module je nach anfallender Aufgabe recht komplex ausfallen. Verglichen mit dem, was sich an fertigen Modulen in den Untiefen etlicher Github-Verzeichnisse findet, erscheint das Beispiel aus diesem Artikel jedenfalls geradezu trivial.

Potenzielle Modulautoren sollten das aber eher als Ansporn denn als Warnung verstehen. Gerade, weil es im Netz etliche gute Beispiele für fertige Module gibt, lassen sich Verständnisschwierigkeiten meist relativ schnell durch die Lektüre vergleichbarer Module auflösen. (jcb/jlu)

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben