Automatisierter Modulimport für OpenCms

2 Kommentare

In einem unserer Projekte verwenden wir seit langer Zeit OpenCms als Redaktionssystem, ergänzt um ein Backend zur Realisierung von Fachlogik. Da wir agil arbeiten, haben wir natürlich auch den Anspruch, agil zu testen. Erste Maßnahme in Sprint 1 des aktuellen Teilprojekts war demnach das Aufsetzen einer Umgebung für Continuous Integration. Die zugehörige User Story hatte den Namen „Als Tester möchte ich tagesaktuell testen können, um Fehler frühzeitig zu finden“. Im Folgenden beschreibe ich, wie wir den Anspruch „tagesaktuell“ durch automatisierte Deployments realisiert haben.

Obwohl wir in der echten Zielumgebung sowohl OpenCms als auch die zugehörige Datenbank im Cluster betreiben, entschieden wir uns für die CI-Umgebung, eine einfache Installation auf einem einzelnen Server vorzunehmen. Aus der Erfahrung heraus sind Deployments von OpenCms-Modulen im Cluster technisch komplex, da alle importierten Ressourcen automatisch repliziert werden. Gleiches gilt für die Datenbank. Diese Komplexität vermeiden wir somit auf dem Testsystem. Wir sind uns dabei der Tatsache bewusst, dass wir Fehler die potenziell nur im Cluster auftreten erst nach dem Deployment in die Cluster-Umgebung finden können.

Um tagesaktuell testen zu können, ist natürlich auch ein tägliches Deployment der Cms-Module und der Anwendungen notwendig. Da wir alle Anwendungen und Cms-Module bereits seit längerer Zeit automatisch von Hudson bauen und paketieren lassen, fehlte uns also eigentlich nur noch das Bindeglied zwischen Hudson-Build und Deployment auf der Testumgebung.

Wir haben also Schritte festgelegt, die automatisch passieren müssen:

  1. Nach einem erfolgreichen Hudson-Build muss ein Upload des Artefakts auf den Testserver stattfinden.
  2. Der Testserver muss täglich zu einer definierten Uhrzeit alle von Hudson hochgeladenen Artefakte deployen.
  3. Nach dem Deployment soll eine Mail mit Log-Informationen verschickt werden.

Da Hudson die jeweiligen Builds mit Maven durchführt, war der Upload auf den Testserver einfach zu realisieren. Hierfür haben wir einen Ant-Call in die deploy-Phase eingehängt, der mittels SCP das Artefakt auf die Testmaschine kopiert.

Nun kommen wir zum eigentlichen Deployment. Für die lokale Entwicklung nutzen wir bereits seit mehreren Jahren Features der OpenCms Shell. Hiermit synchronisieren wir die Module zwischen Eclipse und OpenCms, und importieren unter anderem auch komplette Sites mit Testcontent.

Um ein Modul zu importieren, bedienen wir uns der Shell-Features „loginUser“, „deleteModule“ und „importModule“. Ein Beispiel:

loginUser Admin admin
deleteModule de.codecentric.example
importModule /opt/somewhere/autodeploy/de.codecentric.example.zip
exit

Hier sind natürlich die Zugangsdaten, der Modulname und der Pfad zum Modul-ZIP anzupassen.

Da wir viele Module parallel entwickeln und auch deployen wollen, haben wir entschieden, dynamisch ein Skript für die OpenCms Shell zu generieren, welches dann ausgeführt wird. Ich zeige nun das kommentierte Bash-Skript, das unser Deployment vorbereitet und durchführt:

#!/bin/bash
HOME_DIR=/opt/somewhere
PACKAGE_DIR=$HOME_DIR/autodeploy
JBOSS_BIN_DIR=$HOME_DIR/jboss/bin
JBOSS_SERVER_DIR=$HOME_DIR/jboss/server/opencms
OPENCMS_BASE=$JBOSS_SERVER_DIR/deploy/cms.war/WEB-INF
 
LOG_FILE=$PACKAGE_DIR/log
LOG_MAILTO=receiver@example.net
LOG_MAILFROM=autodeploy@yourhost
 
CMS_USER=Admin
CMS_PASSWORD=admin
 
JAVA=/opt/jdk1.6.0_12/bin/java

Nun sind alle wesentlichen Variablen gesetzt. Eine Convenience-Methode für das Auflisten der gefundenen Module folgt:

function list {
    if [ 0 != $# ]; then
        echo "Folgende Module wurden gefunden:"
        i=1
 
        for package in $@
        do
                echo "  $i. `basename "$package"`"
                ((i=$i+1))
        done
    else
	echo -e "Keine Module gefunden.\n"
    fi
}

Es folgt das Herzstück unseres Skripts: die deploy-Funktion. Als Parameter erhält sie eine Liste von Pfaden zu ZIP-Files, also zu den Cms-Modulen, die deployed werden sollen. Nur wenn diese Liste nicht leer ist, wird das Cms-Shell-Skript generiert. Alles was nach dem Hinzufügen von „exit“ zu dem Skript noch folgt ist kopiert aus der mitgelieferten cmsshell.sh (siehe WEB-INF des WARs) und ein wenig angepasst. Der nötige Classpath wird befüllt, anschließend wird die Cms-Shell mit dem vorher generierten Skript gestartet und das Deployment der Module findet statt. Eine letzte Anmerkung: wir haben hier noch „purgeJspRepository“ eingefügt, um sicherzustellen dass alle eventuell im FlexCache befindlichen JSP-Fragmente entfernt werden.

function deploy {
    if [ 0 != $# ]; then
	cd $PACKAGE_DIR
 
	# Add login statement to the CMS script
	echo "loginUser $CMS_USER $CMS_PASSWORD" > script
 
	# Add import statements to the CMS script
	for zipfile in $@
	do
		echo "deleteModule \"`basename $zipfile .zip`\"" >> script
		echo "importModule \"$zipfile\"" >> script
	done
 
	# Purge JSP Repo
	echo "purgeJSPRepository" >> script
 
	# Add exit statement to the CMS script
	echo "exit" >> script
 
	for JAR in $JBOSS_SERVER_DIR/lib/*.jar; do
	    TOMCAT_CLASSPATH="$TOMCAT_CLASSPATH:$JAR"
	done
 
	OPENCMS_CLASSPATH=""
	for JAR in $JBOSS_SERVER_DIR/deploy/web.war/WEB-INF/lib/*.jar; do
	    OPENCMS_CLASSPATH="$OPENCMS_CLASSPATH:$JAR"
	done
 
	$JAVA -classpath "$OPENCMS_CLASSPATH:$TOMCAT_CLASSPATH:classes" org.opencms.main.CmsShell -base="$OPENCMS_BASE" -script=script >> $LOG_FILE
    fi
}

Das Skript enthält ansonsten noch einige Hilfsmethoden, die (in unserem Fall) JBoss stoppen und starten, die Mail mit dem Log versenden und nach dem Deployment aufräumen:

function stopJBOSS {
    $JBOSS_BIN_DIR/shutdown.sh
    tail -f $JBOSS_SERVER_DIR/log/server.log > $PACKAGE_DIR/jboss_down.log &
    until tail -n 1 $JBOSS_SERVER_DIR/log/server.log | grep "Shutdown complete"; do sleep 0.01; done
    killall tail
}
 
function startJBOSS {
    rm $JBOSS_SERVER_DIR/tmp/* -r
    rm $JBOSS_SERVER_DIR/work/* -r
    $JBOSS_BIN_DIR/startup.sh
    tail -f $JBOSS_SERVER_DIR/log/server.log > $PACKAGE_DIR/jboss_up.log &
    until tail -n 1 $JBOSS_SERVER_DIR/log/server.log | grep "Started"; do sleep 0.01; done
    killall tail
}
 
function mailLog {
	mailsubject="Auto-Deploy-Log vom `date "+%d.%m.%Y, %H:%M Uhr"`" 
	cat $LOG_FILE $PACKAGE_DIR/jboss_down.log $PACKAGE_DIR/jboss_up.log | mail -s "$mailsubject" $LOG_MAILTO -a "From: $LOG_MAILFROM"
}
 
function cleanup {
	rm $PACKAGE_DIR/*
}

Hier ist zu beachten, dass startup.sh und shutdown.sh nicht zur normalen JBoss-Distribution gehören sondern selbstgemachte Wrapper-Skripte sind.

Dann fehlt eigentlich nur noch der Teil des Skripts, der die tatsächliche Arbeit erledigt:

echo -e "Suche nach Modulen in: $PACKAGE_DIR"
 
packages=`find $PACKAGE_DIR -name *.zip`
 
# Do some work! :)
stopJBOSS
list $packages
deploy $packages
startJBOSS
mailLog
cleanup

Wie man sieht, werden hier einfach aus dem PACKAGE_DIR alle dort befindlichen ZIPs als OpenCms-Module betrachtet und deployed. Unsere aktuell im Einsatz befindliche Version des Skripts tut noch einige weitere Dinge – zum Beispiel werden WAR-Files automatisch deployed, es werden Regeln für eine der Anwendungen aktualisiert, und zukünftig werden auch SQL-Skripte automatisch bei jedem Deployment ausgeführt. Das Skript ist natürlich frei nach dem persönlichen Geschmack erweiter- und änderbar.

Mit Hilfe dieses Skripts erfüllen wir passgenau die Anforderung „Als Tester möchte ich tagesaktuell testen können, um Fehler frühzeitig zu finden“. Notfalls kann das Skript sogar während des Tages manuell angestoßen werden um schnell einen Bugfix deployen und testen zu können. Selbstverständlich laufen nach dem automatischen Deployment auch sofort unsere automatisierten Fachtests. Wir haben also eine sehr gute und sehr agile Umgebung für Continuous Integration geschaffen, die uns beim Entwickeln qualitativ hochwertiger Software erheblich unterstützt.

Eine Notiz zum Ende dieses Artikels: ursprünglich wurde unser Autodeploy-Skript nur in Kombination mit OpenCms 7.5.2 und JBoss 4.2.3 genutzt. Mittlerweile hat ein anderes Projekt bei uns im Haus das Skript auch für automatisierte Deployments im Kontext von BEA WebLogic erfolgreich zum Einsatz gebracht.

Für die Zukunft überlegen wir, eventuell auch Deployments in die Zielumgebung, zumindest auf die erste von drei Stages, zumindest partiell zu automatisieren. Wir werden sehen, ob OCEE (Infos zu OCEE hier) sich mit der Cms Shell verträgt. Wenn dazu schon jemand Erfahrungen hat, freue ich mich über entsprechende Kommentare.

Kommentare

  • ben

    Hi.

    > Wir werden sehen, ob OCEE (Infos zu OCEE hier) sich mit der Cms Shell verträgt.

    Konntet ihr das in der Zwischenzeit testen? Funktioniert das?

    Besten Dank für den Eintrag! Super Sache… google hilft also doch… 😉

    ben

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.