Jenkins Pipeline Plugin – Scripts not permitted to use staticMethod hudson.model.Hudson getInstance

Vor kurzem wollte ich ein wenig das Jenkins Plugin Pipeline etwas besser kennen lernen. Dazu habe ich das Plugin installiert und anschließen einen neuen Pipeline Job erstellt. Mein Pipeline Skript, das ich anschließend starten wollte, sah so aus:

import hudson.model.*;
 
node {
    echo "Hallo :)";
 
    // get all jobs
    jobs = Hudson.getInstance().getAllItems(FreeStyleProject)
    for (job in jobs) {
        println(job.getFullName());
   }  
}

Doch beim Starten des neuen Jobs hatte ich folgende Fehlermeldung:

org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use staticMethod hudson.model.Hudson getInstance

Lösung

Um die Fehlermeldung verschwinden zu lassen, bestand bei mir die Lösung daraus, dass ich den Haken bei Use Groovy Sandbox weggemacht habe:

img_013

Jenkins absichern mit dem Benutzer-/Gruppenverzeichnis

Heute habe ich ein wenig mit dem der Absicherung von Jenkins mit Hilfe von einem Linux Benutzer- / Gruppenverzeichnis gespielt. Ich habe das getan, um noch ein bisschen vertrauer mit Jenkins zu werden. Diese Absicherung mit Hilfe von einem Benutzer- / Gruppenverzeichnis basiert auf den Benutzern des Betriebssystems, auf dem Jenkins gerade läuft. D.h. wenn ich auf dem master-System (also auf dem Betriebssystem, auf dem Jenkins läuft) einen Benutzer hinzufüge, kann ich diesem Benutzer später diverse Rechte geben oder er hat schon alle nötigen Rechte (je nachdem, was man in Jenkins eingestellt hat). Ich werde also im folgenden Beschreiben, was ich getan habe, um Jenkins auf diese Weise abzusichern.

Dazu habe ich Jenkins erst einmal frisch installiert. Das Betriebssystem war ein CentOS 7. Danach bin ich auf die Seite http://jenkinsserver:8080/configureSettings/ gegangen und habe dort einen Haken bei Jenkins absichern gesetzt. Anschließend musste nur noch die Option Unix Benutzer- / Gruppenverzeichnis aktiviert. Wenn man jetzt auf den Knopf Testen drückt, kommt i.d.R. eine Fehlermeldung wie diese hier:

Entweder muß Jenkins als root ausgeführt werden, oder
Benutzer 'jenkins' muß zu Gruppe root gehören und 
'chmod g+r /etc/shadow' muß ausgeführt werden, damit 
Jenkins /etc/shadow lesen kann.

Gesagt, getan: Mit dem Befehl usermod -g root jenkins fügt man den Benutzer jenkins zur Gruppe root hinzu. Der Benutzer jenkins sollte schon existieren und bei einer Installation von Jenkins durch ein Repo wird der Benutzer auch standardmäßig erstellt (soweit ich weiß). Will man nochmal testen, ob alles erfolgreich war, kann man dafür den Befehl groups jenkins ausführen. Bei der Ausgabe sollte auch root erscheinen.

Um noch die Datei /etc/shadow für den Benutzer jenkins lesbar zu machen, muss man noch – wie in der Fehlermeldung beschrieben – die Rechte der Datei ändern: chmod g+r /etc/shadow.

Wenn man in Jenkins wieder auf den Knopf Testen drückt, sollte keine Fehlermeldung auftauchen.

Nun kann man z.B. über die Einstellung “Matrix-basierte Einstellungen” Benutzer hinzufügen und ihnen die Rechte geben, die sie brauchen. Wichtig: Es sollte mindestens einen Benutzer geben, der alle Rechte hat, sonst kann es passieren, dass man sich selbst “ausschließt” und man am Schluss nichts mehr mit Jenkins anfangen kann.

[Jenkins] hudson.plugins.git.GitException: Could not init

Wenn man das Git Plugin in Jenkins benutzen möchte und es taucht ein Fehler wie z.B. dieser auf

Building in workspace C:\path\to\project
Cloning the remote Git repository
Cloning repository git@github.com:path/to-gut.git
> git.exe init C:\path\to\project # timeout=10
ERROR: Error cloning remote repo ‚origin‘
hudson.plugins.git.GitException: Could not init C:\path\to\project

dann liegt das i.d.R. daran, dass Jenkins versucht, git zu starten, es aber nicht schafft, weil es git nicht finden kann. Um das Problem zu lösen, gibt es mehrere Wege.

Linux

Unter Linux reicht es aus, git zu installieren. Unter CentOS / RedHat sähe der Befehl für die Installation von git so aus:

yum install -y git

Für Ubuntu / Debian sähe der Befehl so aus:

sudo apt-get install git

Windows

Unter Windows muss man ein paar Schritte mehr machen. Ich nehme hier jetzt an, dass git auf Windows schon installiert ist. Wenn git schon installiert ist, muss man herausfinden, wo das Programm git.exe liegt. Weiß man das, muss man in Jenkins auf die Seite http://jenkinsserver:8080/configureTools/ gehen. Dorthin kommt man auch über die Links Manage Jenkins -> Global Tool Configuration. Auf der Seite gibt es einen Bereich mit dem Title Git:

jenkins-git-path-to-git

Das Feld, auf das der rote Pfeil zeigt, muss nun auf den Pfad zur git.exe gesetzt werden (also da, wo bei mir jetzt nur git steht). Wenn also git.exe im Pfad C:\Path\To\Git liegt, dann muss das Feld den Wert C:\Path\To\Git\git.exe enthalten. Anschließend das Speichern nicht vergessen 😉

Ich hoffe, ich konnte euch helfen 🙂

CMake und gcov

Was ist gcov?

gcov ist ein Tool, um u.a. die Code Coverage zu messen. Dieses Tool kann aber nur im Zusammenhang mit dem GNU Compiler genutzt werden. Um dieses Tool zu nutzen, muss man den Quelltext mit den Parametern -fprofile-arcs und -ftest-coverage kompilieren.

Jetzt wollte ich das ganze mal mit CMake kombinieren. Die Idee hierbei ist folgende: Angenommen, jemand schreibt eine Bibliothek und möchte den Quellcode testen. Um den Quellcode zu testen, schreib der- oder diejenige kleine Testprogramme, die einige dieser Funktionen aufrufen und überprüfen, ob die jeweils getestete Funktion den Anforderungen entspricht. In dem folgenden CMake und gcov-Beispiel werde ich das nicht so aufwendig machen, sondern ich erstelle eine einzige C++-Datei, diese wird zu einer ausführbaren Datei kompiliert, anschließend gestartet (wir simulieren hier also das Starten eines Testprogramms) und zum Schluss werden die erzeugten gcov-Dateien gesammelt.

Diese gcov-Dateien, von denen hier die Rede ist, haben die Endungen gcno und gcda. Hat man diese Dateien gesammelt, kann man sie mit dem gcov-Tool öffnen, also z.B. gcov main.cpp.gcno. gcov erzeugt anschließend Dateien mit der Endung gcov, die man z.B. mit einem Texteditor öffnen und analysieren kann. Da ich die Dateien mit der Endung gcov auf demselben System angeschaut habe, auf dem auch der Quellcode liegt, hatte ich keine Probleme. Problematisch wird es dann, wenn man gcov startet, der Quelltext aber nicht vorhanden ist oder verschoben wurde.

Im Anschluss findet ihr eine Beispiel-CMakeLists.txt-Datei, die genau die beschriebenen Schritte durchführt. Ich hoffe, die Kommentare sind ausreichend. Falls nicht, schreibt mir in die Kommentare

cmake_minimum_required(VERSION 3.5)
project(gcov_ex)

# Beispielcode mit Parametern kompilieren, sodass
# man das Tool gcov verwenden kann
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")

set(SOURCES main.cpp)

# Zielordner, in dem die gcov-Dateien gesammelt werden
set(TARGET_DIR ${CMAKE_CURRENT_SOURCE_DIR}/target)

add_executable(${PROJECT_NAME} ${SOURCES})

# Sicherstellen, dass der Zielordner auch wirklich existiert
add_custom_command(TARGET ${PROJECT_NAME}
        POST_BUILD
        COMMAND mkdir -p ${TARGET_DIR})

# Testprogramm ausführen
add_custom_command(TARGET ${PROJECT_NAME}
        POST_BUILD
        COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME})

# gcno Dateie in den Zielordner kopieren
add_custom_command(TARGET ${PROJECT_NAME}
        POST_BUILD
        COMMAND
                pushd ${CMAKE_CURRENT_SOURCE_DIR}/CMakeFiles/${PROJECT_NAME}.dir && find . -name '*.gcno' -exec bash -c 'cp --parents -v "{}" ${TARGET_DIR}' \\\; && popd)

# gcda Dateien in den Zielordner kopieren
add_custom_command(TARGET ${PROJECT_NAME}
        POST_BUILD
        COMMAND
                pushd ${CMAKE_CURRENT_SOURCE_DIR}/CMakeFiles/${PROJECT_NAME}.dir && find . -name '*.gcda' -exec bash -c 'cp --parents -v "{}" ${TARGET_DIR}' \\\; && popd)

[Ubuntu] getdeb Repository entfernen

Um das getdeb Repository aus Ubuntus apt zu entfernen, muss man folgende Kommandos eingeben (als root):

apt-get remove getdeb-repository
apt purge getdeb-repository
apt clean
apt-get update

Wenn man die Befehle ausgeführt hat, muss man eventuell noch im Ordner /etc/apt/sources.list.d/ nachschauen, ob es in einen der Dateien noch Hinweise auf das getdeb Repository gibt. Wenn ja, dann kann man die Zeilen auskommentieren, indem man eine Raute davor setzt, z.B.

# deb http://archive.getdeb.net/ubuntu kali-rolling-getdeb games

find, xargs und die Pipe

Neulich habe ich mich ein wenig mit den Linux-Kommandotools find und xargs beschäftigt und das in Kombination mit einer Pipe. Wenn ich in der Kommandozeile das Kommando eintippe

# find . -name '*'

kommt bei mir folgendes heraus:

.
./eins
./zwei
./drei

Das sind drei Dateien, die ich zu Testzwecken erstellt hatte. find gibt also jede gefundene Datei in einer neuen Zeile aus. Möchte man aber, dass alles in einer Zeile ausgegeben wird, muss man noch zusätzlich den Parameter print0 angeben:

$ find . -name '*' -print0
../eins./zwei./drei

print0 macht nichts anderes als anstelle einer neuen Zeile (\n) ein NUL-Zeichen zu setzen. Nun wollte ich das Ergebnis durch eine Pipe schicken und mit xargs die gefundenen Dateien weiterleiten. Doch da tauchte plötzlich eine Warnung auf:

$ find . -name '*' -print0 | xargs echo
xargs: WARNING: a NUL character occurred in the input.  It cannot be passed through in the argument list.  Did you mean to use the --null option?
.

Also den Befehl wieder etwas abändern:

$ find . -name '*' -print0 | xargs --null echo
. ./eins ./zwei ./drei

CMake und proxygen

Vor kurzem musste ich ein kleines Beispiel in CMake mit der Bibliothek proxygen machen. Dabei kam es mir ziemlich gelegen, dass das Projekt auch ein Dockerfile hatte 😀 . Hier möchte ich kurz beschreiben, was ich gemacht habe, vielleicht hilft es ja dem ein oder anderen.

Erstmal das Projekt klonen:

git clone https://github.com/facebook/proxygen.git

Nach dem klonen, ein Docker Image bauen:

cd proxygen
docker build -t proxy_ex .

Beim Bau von dem Docker Image kam jedoch irgendwann diese Fehlermeldung:

sudo: command not found

Die Lösung dieses Problems war die, dass man noch das Paket sudo im Dockerfile mit installiert, d.h. man muss das Dockerfile in etwa so abhändern:

...
    autoconf-archive \
    libevent-dev \
    libgoogle-glog-dev \
    wget \
    sudo

...

Jedenfall hat das bei mir geholfen. Nachdem ich also die Änderung im Dockerfile gemacht habe, habe ich das Docker Image nochmal neu gebaut und es danach gestartet:

docker run -it proxy_ex /bin/bash

In dem gestarteten Dockercontainter kopierte ich ein Beispiel aus dem proxygen-Projekt und nahm es als Grundlage für mein proxygen-CMake-Beispiel

cd && cp -r /home/proxygen/proxygen/httpserver/samples/echo# .

Anschließend konnte ich eine Beispiel-CMakeLists.txt erstellen:

cmake_minimum_required(VERSION 2.8)
project(proxy_gen)

# Dieses Projekt braucht c++11 Unterstützung
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(SOURCES EchoHandler.cpp EchoServer.cpp)

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} proxygenhttpserver folly glog gflags pthread)

[CMake] Was bedeuten die Wörter PRIVATE, PUBLIC und INTERFACE?

In CMake kommen manchmal die Schlüsselwörter PRIVATE, PUBLIC und INTERFACE vor. In diesem Post will ich erläutern, welche Funktion diese Schlüsselwörter haben und wofür man sie braucht.

Bevor wir uns jedoch die Schlüsselwörter genauer anschauen, will ich hier noch zwei Begriffe erklären: private und öffentliche Header. Ich weiß nicht, ob das, was ich hier jetzt beschreibe, auch die wirkliche Definition ist, aber ich werde diese Begriffe weiter unten benutzen, deswegen will ich diese hier nochmal klar stellen.
Wenn wir eine Bibliothek B haben und diese Bibliothek weitergeben, damit ein anderer Programmierer mit dieser Bibliothek arbeiten kann, geben wir ihm eine Reihe von Headern dazu, in der in aller Regel Funktionen und sonstige genutzte Variablen deklariert sind. Dieser Header, die weiter gegeben werden, bezeichne ich hier als öffentliche Header. Alle anderen Header, die bei der Programmierung der Bibliothek B benutzt werden, aber nicht weiter gegeben werden, bezeichne ich hier als private Header.

Da wir nun die Begrifflichkeiten geklärt haben, nun zum eigentlichen Thema: Angenommen, man schreibt eine (C++-)Bibliothek A und diese Bibliothek ist von einer weiteren Bibliothek B abhängig. Nun gibt es verschiedene Fälle:

  1. In einer cpp-Datei oder einer privaten Header-Datei der Bibliothek A wurde eine Header-Datei der Bibliothek B aufgenommen (gemeint sind Einträge der Form #include <B.h>). Aber es wurde in keiner öffentlichen Header-Datei der Bibliothek A ein Header der Bibliothek B aufgenommen. Dann ist dieser Fall eine PRIVATE-Abhängigkeit.
  2. Von einer PUBLIC-Abhängigkeit wird gesprochen, wenn eine Header-Datei der Bibliothek B in sowohl einer öffentlichen Header-Datei aufgenommen worden ist, wie auch einer cpp- bzw. einer privaten Header-Datei.
  3. Wenn eine Header-Datei der Bibliothek B nur in öffentlichen Headern der Bibliothek A zu finden ist, dann sprechen wir von einer INTERFACE-Abhängigkeit.

Bei einfachen Abhängigkeitsstrukturen mag diese Unterscheidung noch uninteressant sein, wenn es jedoch komplexer wird, ist es gut, die Unterscheidung zu kennen. Wenn eine Bibliothek C von Bibliothek A abhängig ist, wie werden in CMake die Target-Eigenschaften weiter gegeben? Gemeint sind diejenigen Target-Eigenschaften, die man z.B. über Funktionen wie target_include_directories definiert.

Wenn beispielsweise A und B eine PRIVATE-Abhängigkeit haben, und C und A eine (egal was für eine) Abhängigkeit haben, dann bekommt C von B nichts mit. D.h. wenn wir in CMake z.B. eine Zeile der Art

tartget_include_directories(targetA PRIVATE ${INCLUDE_DIR_LIB_B})

haben, dann würde die Bibliothek C den Include-Pfad für Bibliothek B nicht mitbekommen. In den anderen Fällen (zwischen A und B) bekommt C jedoch von B was mit.

Wer mehr erfahren möchte, kann unter diesen Links weiter schmökern:

CMake und die Bibliothek GLFW

In diesem Post möchte ich ein Beispiel zeigen, wie man die Bibliothek GLFW in CMake nutzt. Alles, was ich hier mache, mache ich auf einer Ubuntu Maschine. Es war nicht sehr einfach, da es mehrere Probleme gab, aber ich konnte dennoch mein kleines Beispiel zum Laufen bringen. Ich möchte hier Schritt für Schritt zeigen, wie ich zur Lösung gekommen bin.

Fangen wir an. Zuerst eine main.cpp, die unser Beispielprogramm sein soll:

#include <iostream>
#include <GL/glew.h>
#include <GL/glfw.h>
 
int main(int argc, char **argv)
{
        if ( !glfwInit() ) {
                std::cout << "glfw failed" << std::endl;
        } else {
                std::cout << "glfw success" << std::endl;
        }
 
        return 0;
}

Um das Beispiel zum Laufen zu bekommen, muss man noch entsprechende Bibliotheken auf dem System installiert haben:

sudo apt-get install libglfw-dev libglew-dev

Nachdem wir also auch die Abhängigkeiten haben, fangen wir mit der CMakeLists.txt an:

cmake_minimum_required(VERSION 3.5)
project(glfw_ex)

set(SOURCES main.cpp)

find_package(PkgConfig REQUIRED)
pkg_search_module(GLFW REQUIRED glfw)

include_directories(${GLFW_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} ${GLFW_LIBRARIES})

Als ich CMake gestartet hatte (Kommandozeilenbefehl: cmake . && make ), tauchte folgender Fehler auf:

-- Found PkgConfig: /usr/bin/pkg-config (found version "0.29") 
-- Checking for one of the modules 'glfw'
CMake Error at /usr/local/share/cmake-3.5/Modules/FindPkgConfig.cmake:575 (message):
  None of the required 'glfw' found
Call Stack (most recent call first):
  CMakeLists.txt:10 (pkg_search_module)

Da ein schnelles googlen keine Ergebnisse gebracht hat, musste ich wohl etwas tiefer graben. Zuerst wollte ich sichergehen, dass ich auch wirklich eine Bibliothek mit dem Namen glfw auf der Kiste habe. Der folgende Befehl listet alle Dateien auf einem Ubuntu-System auf, die ein Paket installiert hat:

dpkg-query -L libglfw-dev

Und tatsächlich fand ich Einträge wie

...
/usr/lib/i386-linux-gnu/libglfw.a
...
/usr/lib/i386-linux-gnu/libglfw.so

D.h. die Bibliotheken ware da, aber in CMake kam trotzdem eine Fehlermeldung.

pkg_search_module und pkg-config

Also musste ich weitersuchen. Ich habe mir die Frage gestellt: Was macht eigentlich die CMake Funktion pkg_search_module? Die Funktion basiert auf dem Linux-Tool pkg-config, mit der man Informationen über Software abfragen kann. Bei Bibliotheken kann man das Tool nutzen, um die Compiler- und Linker-Parameter für die entsprechende Bibliothek zu extrahieren. Wenn man z.B. meine obige C++-Datei nimmt, und diese auf der Kommandozeile kompilieren möchte (also ganz ohne CMake), kann mann das über den Befehl

g++ -o glfw_ex1 -lglfw main.cpp

oder man nutzt die Ausgabe von pkg-config

g++ -o glfw_ex2 $(pkg-config --libs libglfw) main.cpp
# Ausgabe von 'pkg-config --libs libglfw' ist
# -lglfw -pthread

Aber das sollte jetzt nur ein kleiner Exkurs sein.

Durch eine schnelle Internetrecherche kam außerdem heraus, dass das Tool Dateien vom Typ *.pc nutzt. Bei der Auflistung der installierten Dateien fand ich auch folgenden Eintrag:

/usr/lib/i386-linux-gnu/pkgconfig/libglfw.pc

Als ich die Zeile gesehen habe, habe ich mir gedacht, dass ich die CMakeLists.txt wohl etwas abändern muss (ich zeige hier nur die veränderte Zeile):

pkg_search_module(GLFW REQUIRED libglfw)

Doch auch als ich diese Zeile geändert hatte, kam eine Fehlermeldung, was seltsam war, da das Kommando

pkg-config --libs libglfw

keine Fehlermeldung erzeugte, sondern Linkeroptionen ausgab. Die geänderte Zeile habe ich aber dann so gelassen. Nach längerem Suchen in dem CMake-Modul FindPkgConfig.cmake habe ich herausgefunden, dass CMake intern das Tool pkg-config folgendermaßen aufruft

pkg-config --exists --print-errors --short-errors libglfw

Also habe ich diesen Befehl auch in der Kommandozeile getestet. Tatsächlich wurde mir auch hier ein Fehler angezeigt:

Package 'xrandr' required by 'libglfw', not found

Nachdem ich aufgrund der Fehlermeldung das Paket libxrandr-dev installiert hatte, kam keine Fehlermeldung mehr beim Aufruf vom CMake-internen pkg-config-Befehl. Also habe ich nochmal CMake gestartet und danach lief CMake ohne Probleme durch 🙂