#! /bin/bash
#
# Wrapper um cfengine-Konfigurationsdateien zu importieren
# und dabei die Reihenfolge der Bearbeitungsschritte definieren
# zu koennen.
#
# Das Problem ist folgendes:
#
# -- datei: cf.main ---
# <blabla>
# import:
#   cf.task1
#   cf.task2
# <blabla>
# -- eof: cf.main ---
# -- datei: cf.task1 ---
# control:
#   actionsequence = ( copy shellcommands )
# copy:
#   $(filetree)/etc/someconf /etc/someconf
# shellcommands:
#   "/etc/init.d/some restart"
# -- eof: cf.task1 ---
#
# -- datei: cf.task2 ---
# control:
#   actionsequence = ( shellcommands copy ) <-- beachte umgekehrte reihenfolge
# shellcommands:
#   "/etc/init.d/other restart"
# copy:
#   /var/run/something.pid /var/backup/something.pid
# -- eof: cf.task2 ---
# 
# Wie man sieht, soll task1 _zuerst_ kopieren und _dann_ restarten
# Task2 koennte sowas wie ein regelmaessiger restart sein, bei dem
# nachher eine Status-Datei gesichert wird
#
# Was jetzt passiert ist folgendes:
# - cfengine macht aus allen Includes ein grosses
# - bei der Controlsequence wird immer angehangen
# - dh. das resultierende "file" sieht so aus:
#
# -- datei: <virtual> ---
# control:
#   actionsequence = ( copy shellcommands shellcommands copy )
# shellcommands:
#   "/etc/init.d/some restart"
#   "/etc/init.d/other restart"
# copy:
#   $(filetree)/etc/someconf /etc/someconf
#   /var/run/something.pid /var/backup/something.pid
# -- eof: <virtual> ---
# 
# Das verursacht folgende Probleme:
# - copy und shellcommands werden 2x ausgefuehrt, dabei
#   ist bei der 2.Ausfuehrung unter Umstaenden noch nicht eine Minute
#   (default-Wert von "ifelapsed") vergangen, so das beim 2. mal
#   _garnichts_ gemacht wird
# - es ist fraglich ob Task 2 ueberhaupt noch funktioniert
# - durch die doppelte Ausfuehrung wird die Bearbeitungszeit erhoeht
# - alle Tasks teilen sich einen Namespace fuer Locks, so dass
#   z.B. "processes" nicht mehr Funktioniert, da SetOptionString nicht
#   mehr ausgefuehrt wird, da IfElapsed noch nicht vorbei ist
# - alle Tasks teilen sich einen Namespace fuer lokale Variablen
# wir man sieht, fuehrt dieses Konstrukt bei grossen imports, 
# wie z.B. im Linuxpool zu teilweise unvorhersehbaren Effekten
#
# Die Loesung besteht darin, dass cfengine sich selbst per shellcommands
# startet, dabei aber ueber "-f" den Name der Taskdatei uebergibt.
# Der Hauptvorteil liegt darin, dass mit dieser Vorgehensweise
# _EIN_ cfagent-Lauf ausreicht um alle Tasks vollstaendig und in der 
# gewuenschten Reihenfolge laufen zu lassen. Dabei wirken Locks jetzt
# "lokal", d.h. pro Datei in der sie definiert sind.
# 
# Der Nachteil besteht darin, dass man sich jetzt selbst um das
# weiterreichen von Variablen und Definierten Klassen kuemmern
# muss.
# 1. Wie werden "globale" Variable weitergereicht ?
#    Globale Variable, die z.B. "filetree" werden in einer Extradatei
#    abgelegt und von jedem Task selbst importiert
# 2. Wie werden "lokale" Variable an Subtask weitergereicht ?
#    Das geht im Moment (noch?) nicht. D.h. Variablen, die in
#    30xts/cf.main definiert werden sind fuer Tasks der "Klasse"
#    30xts nicht verfuegbar. Das koennte man ueber ein 2. import
#    realisieren, damit koennen Tasks aber nicht mehr zwischen 
#    Klassen hin und her geschoben werden. (Was ziemlich nuetzlich ist ;-)
# 3. Wie werden Classes weitergereicht ?
#    Moeglichkeit1: Ueber import. Das funktioniert aber nicht. (Warum
#      auch immer, habe in der cfengine-Doku nichts dazu gefunden)
#    Moeglichkeit2: Ueber die Environment und den Kommandozeilen-Parameter
#      "--define" von cfagent. <- genau das macht dieses Skript ;-)
#      Die definierten Klassen stellt cfengine in der Variable ALLCLASSES
#      an shellcommands zur Verfuegung, wenn man es mit "--use-env" startet
# 4. Aufgrund des kranken Scopings von cfengine, passiert folgendes:
#    control:
#      ... 
#    import:
#     datei-mit-super-variablen
#     subtaskX
#    shellcommands:
#      blabla
# 
#  Merkwuerdigerweise, kann man in subtaskX auf die Variablen, die in der 
#  Datei "datei-mit-super-variablen" definiert sind, zugreifen.
#  An der Stelle "blabla", ist jedoch _keine_ dieser Variablen definiert.
#
#  Dieses Verhalten ist nicht besonders intuitiv, weshalb alle Scripte
#  die mit import-hack.sh ausgefuehrt werden, nochmal gewrappt werden.
#  D.h. es wir eine temporaere Datei erzeugt, mit folgendem Inhalt:
#  import:
#    <global-settings>
#    <eigentlich auszufuehrendes script>
#
# Damit sind dann im eigentlichen "Zielscript" alle Variablen wie gewohnt
# Verfuegbar...


#########################################################################
# Import-Script
# - emulates imports, where global variables and classes are inherited, 
#   _but_ actionsequence is script-specific
#
# written by Jens Braeuer <jensb@cs.tu-berlin.de>, 14.06.2005
#
# WARNING:: before editing, be _SURE_ YOU KNOW WHAT YOU DO and that
#           YOU UNDERSTOOD THE IMPLEMENTATION OF CFENGINE's
#           variable and class SCOPES !!!
#########################################################################

# ------------------------------------------------------
# binary settings - as needed in Debian Sarge
#                   other system may need modifications
RM=/bin/rm
CAT=/bin/cat
ECHO=/bin/echo
CUT=/usr/bin/cut
TR=/usr/bin/tr
LOGGER="/usr/bin/logger -t import-hack "
CFAGENT=/usr/sbin/cfagent
TEMPFILE=/bin/tempfile
BASENAME=/usr/bin/basename

# ------------------------------------------------------
# env settings
export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin

# ------------------------------------------------------
# default options for cfagent
cfopts="--use-env "

# this file is imported by generated scripts
cfglobal="/etc/cfengine/global-options.conf"

# ------------------------------------------------------
# function to filter out hard-classes. these classes can not be defined
# via --define, so they _have_to_ be filtered
#
# WARNING: result is stored in $filterResult
#
filterResult=""
function filterClasses() {
    filterResult=""
    case "$1" in
	130_149*|ipv4_*|net_iface*|*zrz_TU_Berlin_DE) 
          ;;
	Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday) 
          ;;
	January|February|March|April|May|June)
          ;;
	July|August|September|November|December) 
          ;;
	Yr[0-9]*|Day[0-9]*|Hr[0-9]*|Min[0-9]*|Q[1-4]) 
          ;;
	any|compiled_on*|cfengine_*)
          ;;
	debian*)
	  ;;
	32_bit|64_bit|i686|i586|i486)
          ;;
	linux|linux_*)
	    ;;
        *)
	    filterResult="--define $1"
	    ;;
    esac
}

# ------------------------------------------------------
# print and log the msg, then exit
function fatalError() {
   local msg="$@"
   ${LOGGER} "${msg}"
   ${ECHO} "${msg}"
   exit 1
}

# ------------------------------------------------------
# main()
# ------------------------------------------------------

# sanity checks
filename="${1}"
if [ "${filename}" == "" ]; then
    fatalError "no first argument given"
elif [ ! -f "${filename}" ]; then
    fatalError "file does not exist: ${filename}"
elif [ -z "${CFALLCLASSES}" ]; then
    fatalError "environment-variable CFALLCLASSES not set."
fi

# get all classes (including hard-classes) from $CFALLCLASSES
classes=`${ECHO} ${CFALLCLASSES} | "${TR}" ':' ' '`
for cl in ${classes}; do
    filterClasses "$cl"
    if [ "${filterResult}" != "" ]; then
	cfopts="${cfopts} ${filterResult}"
    fi
done

# create a temp-wrapper-script, DONT use tempname, this will 
# f*ck off cfeniges locks-database and make all
# ifelapsed-settings non-functional. (you know what you do ? ;-)
umask 0077
wrappername=`${BASENAME} "${filename}"`
wrappername="/tmp/${wrappername}"
if [ -e "${wrappername}" ]; then
    fatalError "file ${wrappername} already exists. Either this is a bug or the previous run is not finished yet..."
fi
${CAT} >"${wrappername}" <<EOF
  #
  # automatic generated wrapper.
  #
  control:
    AllowRedefinitionOf = ( OutputPrefix )
    OutputPrefix = ( "" )
  import:
    ${cfglobal}
    ${filename}
EOF

# log what we do
${LOGGER} "wrapped: ${filename} into ${wrappername}"
${LOGGER} "running: ${CFAGENT} ${cfopts} -f ${wrappername}" 

# and exec command
${CFAGENT} ${cfopts} -f "${wrappername}"
cfexitcode=$?

# remove tmp-wrapper filename
${RM} "${wrappername}"

# return the exit-code of cfagent
exit ${cfexitcode}

# BTW: cfengine rocks ;-) (apart from variable- and class-scopes ;-)

