Continuous Integration mit Drone und Lua
Schon lange her das ich hier mal was geschrieben hab, aber irgendwie fehlte mir immer ein Thema um was zu schreiben oder wenn es eins gab, die Lust. Jetzt habe ich aber mal eins, mit dem ich mich die letzten Tage ein wenig auseinander gesetzt hab und überwinde hiermit meine Faulheit und schreibe etwas dazu. :-)
Einleitung
Seit ungefähr Anfang des Jahres bring ich mir selbst programmieren bei und nutze dafür meine Lieblingssprache Lua. Warum Lua? Naja, ich habe schon mal einen Versuch gestartet programmieren zu lernen und zwar mit Python, aber aus irgendeinem Grund langweilte es mich schnell. Versteht mich nicht falsch, Python ist ne coole Sprache, aber irgendwie sprang bei mir da der Funke nicht über. Ganz anders sah das bei Lua aus, auf die ich über den XMPP Server Prosody aufmerksam wurde, wo ich direkt Spaß am schreiben und lesen hatte und hab. Außerdem gefällt mir die minimalistische Natur von Lua, die natürlich nicht nur Vorteile hat. Leider ist sie nicht so weit verbreitet, was den Umfang an vorhanderen Modulen (Libraries) sehr überschaubar macht und ein guter Teil davon auch etwas alt ist.
Da ich jemand bin der am besten was versteht und behält indem er es einfach tut, suchte ich, nachdem ich ein paar Tage in Programming in Lua geschmökert hatte, eine Möglichkeit zu üben. Ein Bekannter empfohl mir exercism.io (das ich an dieser Stelle nur wärmstens empfehlen kann), wodurch ich auch direkt mit Test Driven Development in Berührung kam. Seitdem versuche ich immer neben einer guten Dokumentation auch meine „Software“ mit Tests auszustatten. So kam mir dann auch mal langsam der Gedanke sich mit Continuous Integration auseinander zu setzen.
Btw. hier mein bisherigen „Projekte“:
- lua-netio – ein API Client Modul zum steuern von Netzwerksteckdosen der Firma NETIO products a.s.
- lua-ssllabs – ein API Client Modul für Qualys SSL Labs Server Test API (v3)
- emoji-downloader – ein CLI Tool zum downloaden von Custom Emojis von Mastodon oder Pleroma Instanzen
- whats-my-ip-address – Ein kleiner Webservice der die WAN IP anzeigt. Eine Demo läuft hier: wmi.kokolor.es
Ich bin immernoch sehr viel am experimentieren und lernen, also ja, der Code sieht mit Sicherheit für den geübten Programmierer an vielen Stellen ziemlich hässlich aus. Für Hinweise bin ich dankbar btw. ;-)
Die Umgebung
Derzeit sind die verbreitesten Lua Versionen Lua 5.1, 5.2 und 5.3. Daneben gibt es zwar noch LuaJIT, aber das lass ich meist außen vor, da wenn es mit Lua 5.1 funktioniert in der Regel auch mit LuaJIT funzt. Gegen diese Versionen möchte ich also auch meine Module testen, was bedeutet das ich diese dann auch installieren muss. Außerdem wird der Paketmanager LuaRocks benötigt, um Abhängkeiten zu installieren. Außerdem wäre es auch möglich das getestete Modul direkt auf luarocks.org zu publishen, das hab ich aber noch nicht gemacht und ist deshalb nicht Part von dem Artikel.
Da ich primär mein eigenes Gitea nutze, entschied ich mich auch einen CI Service zu nutzen der mit Gitea zusammenarbeitet und dies ist Drone. Mit Drone habe ich schon einmal herumgespielt, bevor ich mein Gitea aufsetzte und es gefiel mir schon damals recht gut. Nur hatte ich noch keinen Einsatzzweck und so warf ich den Container irgendwann wieder weg.
Drone baut auf Docker auf, wie so fast jedes CI System glaub ich, was man auch sehr an den Konfigurationen sehen kann, welche docker-compose Konfigurationen ähneln. Somit werden auch alle Sprachen unterstützt für die es einen entsprechenden Container gibt und wenn es keinen gibt, macht man sich einen. Für Lua würde also eine .drone.yml
in etwa so aussehene:
pipeline:
build:
image: some/lua${LUA_VERSION}:image
commands:
- luraocks install busted
- busted spec/
matrix:
LUA_VERSION:
- 5.1
- 5.2
- 5.3
Diese Konfiguration würde nun z.B. sagen: „Nimm für den Job build, das Docker Image some/lua${LUAVERSION}:image, installier das Unit Testing Framwork busted mit LuaRocks und führe die Tests anschließend aus. Achja und dies tust du bitte für alle drei Lua Versionen.“
Docker Images für Tests
Leider gibt es nicht all zu viele fertige Images mit Lua und LuaRocks und die wenigen die es gibt, basieren dann entweder auf Debian oder Ubuntu (Größe so ~300MB), werden nicht mehr gepflegt oder sind irgendwie buggy. Am vielversprechendsten waren die Images von akorn, welcher allerdings die Lua Versionen mit Platform posix, statt linux kompiliert, was dazu führt das ein paar in C geschriebene Module, wie z.B. luafilesystem nicht nutzbar sind. Dazu habe ich auch schon ein Issue bei ihm erstellt. Sollte er das anpassen oder mich auf einen Fehler meinerseits hinweisen, werde ich vermutlich seine Images nutzen. Bis dahin allerdings habe ich die Dockerfiles angepasst und local die Images erstellt, sodass sie Drone nutzen kann. Das Image das aus meinen LuaRocks Dockerfiles erstellt wird ist mit 187MB zwar dennoch recht groß, allerdings sind auch schon eine Reihe von Build Tools enthalten, womit sich in C geschriebenen Abhängigkeiten direkt installieren lassen sollten. Das cleane Image von akorn ist hingegen ca. 12MB groß.
Alternativ kann man auch das offizielle Python Image nehmen und darin das Tool hererocks installieren. Mit diesem kann man dann Lua und LuaRocks in ein Projektverzeichnis installieren und anschließend nutzen. Wenn man sich die Lua Docker Image Situation anschaut, ist dies sicher auf den ersten Blick die einfachste Lösung. Allerdings komme ich persönlich nicht damit klar, ein 1GB großes Image zum testen eines paar KB großen Modules zu verwenden. Wenn man sich natürlich keine Sorge um Ressourcen machen muss, dann kann man das natürlich tun, bzw. soweit ich das bisher gelesen hab, hat man, wenn man Travis CI nutzt, nicht wirklich viele Auswahlmöglichkeiten mit Lua.
Tests bzw. Specs
Wie weiter oben in dem .drone.yml
Beispiel zu sehen ist, nutze ich das Unit Testing Framwork busted von OlivineLabs. Neben busted
gibt es noch wesentlich schmalere Unit Testing Framworks, da ich aber durch exercism.io bei busted
gelandet bin, nutze ich dieses auch seitdem. Hier mal ein kurzes Beispiel wie ein Test, bzw. in busted
Spec genannt, aussehen kann:
Ein simples Modul:
-- hello.lua
return {
say = function(str) return 'Hello ' .. str end
}
Die Spec dazu:
-- spec/hello_spec.lua
local hello = require('hello')
describe('hello lib', function()
it('says hello to you', function()
assert.equal('Hello you', hello.say('you'))
end)
end)
Kurz zur Erklärung: Mit describe
definiert man eine Gruppe von Specs und mit it
definiert man eine Spec. assert
ist hier die monkeypatched Version von Lua’s assert
und wird hier für assertions
(übersetzt man das in Behauptungen?) genutzt. Wenn assertations
zutreffen, gilt der Spec als bestanden und es wird ein grüner Punkt zurück gegeben, wenn nicht gibt es einen roten Punkt und der Spec gilt als nicht bestanden. Die Ausgabe für das obige Beispiel würde dann wie folgt aussehen:
Für das obige Beispiel, habe ich meine .drone.yml
wie folgt angepasst:
pipeline:
test:
image: imo/luarocks${LUA_VERSION}:latest
commands:
- luarocks install busted
- busted -o TAP .
matrix:
LUA_VERSION:
- 5.1
- 5.2
- 5.3
Mit -o TAP
sage ich das busted
als Output Handler das TAP
(Test Anything Protocoll) benutzen soll.
Sobald gepusht, fängt Drone auch direkt mit den Tests an:
Ist der Test durchgelaufen, sieht man dann den Status hinter dem Commit:
Statische Code-Analyse
Obwohl es schon mein IDE macht, lasse ich in der Regel dennoch gern, vor den eigentlichen Tests, eine Linter über meinen Code drüber laufen. Dafür nutze ich luacheck, welches verbreitete Fehler, wie z.B. deklarierte aber unbenutzte Variablen, globale Variablen, uvm. erkennt. luacheck wird ebenfalls über LuaRocks installiert und kann dann als CLI Tool benutzt werden:
Die Werte der --std
Option sagen folgendes aus:
max
– akzeptiere alle globale Variablen welche in allen Lua Versionen genutzt werden+busted
– akzeptiert globale Variablen die vonbusted
deklariert werden, also z.B.describe
oderit
(dies brauch man natürlich nur wenn man auch seine Spec Dateien mit analysieren lässt)
Eine .drone.yml
für luacheck
würde dann wie folgt aussehen:
pipeline:
lint:
image: imo/luarocks${LUA_VERSION}:latest
commands:
- luarocks install luacheck
- luacheck --std max+busted hello.lua hello_spec.lua
matrix:
LUA_VERSION:
- 5.1
- 5.2
- 5.3
Die fertige .drone.yml
Die fertige .drone.yml
würde nun so aussehen:
pipeline:
lint:
image: imo/luarocks${LUA_VERSION}:latest
commands:
- luarocks install luacheck
- luacheck --std max+busted hello.lua hello_spec.lua
test:
image: imo/luarocks${LUA_VERSION}:latest
commands:
- luarocks install busted
- busted -o TAP .
when:
status: success
matrix:
LUA_VERSION:
- 5.1
- 5.2
- 5.3
Und wie definiert wird das ganze dann auch von Drone ausgeführt:
So das soll der Bericht über meine ersten CI Gehversuche sein. Neben Testing und Linting werde ich wohl zukünftig auch noch Coverage Analysen mit LuaCov einbauen, aber da habe ich bisher noch keine Lust gehabt mich mit auseinander zu setzen. Außerdem habe ich gemerkt das ich mich nun doch langsam mal ein bisschen ausführlicher mit Docker beschäftigen muss. Bisher habe ich das Thema immer umschifft, da ich es nie wirklich gebraucht habe, bzw. LXC mir vollkommen ausreichte.
Vielen Dank fürs lesen und fals es Anmerkungen gibt, freue ich mich sehr davon zu hören/lesen. :-)