diff --git a/README b/README index 51fb323c..965138c4 100644 --- a/README +++ b/README @@ -89,7 +89,7 @@ cdist was tested or is know to run on at least ## Installation -### Preperation +### Preparation Ensure you have Python 3.2 installed on the machine you use to **deploy to the targets** (the ***source host***). diff --git a/build b/build index ea4ca83c..2d98e862 100755 --- a/build +++ b/build @@ -85,7 +85,7 @@ case "$1" in ;; release) - "$0" clean && "$0" man && "$0" web + ./doc/dev/releasechecklist ;; speeches) @@ -113,8 +113,8 @@ case "$1" in # cp ${SPEECHESDIR}/*.pdf ${WEBDIR}/${WEBBASE}/speeches # git describe > ${WEBDIR}/${WEBBASE}/man/VERSION - cp ${MAN1DSTDIR}/*.html ${WEBMAN}/man1 - cp ${MAN7DSTDIR}/*.html ${WEBMAN}/man7 + cp ${MAN1DSTDIR}/*.html ${MAN1DSTDIR}/*.css ${WEBMAN}/man1 + cp ${MAN7DSTDIR}/*.html ${MAN7DSTDIR}/*.css ${WEBMAN}/man7 cd ${WEBDIR} && git add ${WEBBASE} cd ${WEBDIR} && git commit -m "cdist update" ${WEBBASE} ${WEBPAGE} @@ -123,7 +123,7 @@ case "$1" in # Fix ikiwiki, which does not like symlinks for pseudo security ssh tee.schottelius.org \ "cd /home/services/www/nico/www.nico.schottelius.org/www/software/cdist/man && - ln -sf "$version" latest" + rm -f latest && ln -sf "$version" latest" ;; p|pu|pub) diff --git a/conf/explorer/runlevel b/conf/explorer/runlevel new file mode 100755 index 00000000..7cdd81ef --- /dev/null +++ b/conf/explorer/runlevel @@ -0,0 +1,26 @@ +#!/bin/sh +# +# 2012 Nico Schottelius (nico-cdist at schottelius.org) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# + +set +e +executable=$(which runlevel 2>/dev/null) +if [ -x "$executable" ]; then + "$executable" | awk '{ print $2 }' +fi diff --git a/conf/type/__key_value/explorer/state b/conf/type/__key_value/explorer/state new file mode 100755 index 00000000..94a5ea7f --- /dev/null +++ b/conf/type/__key_value/explorer/state @@ -0,0 +1,53 @@ +#!/bin/sh +# +# 2011 Steven Armstrong (steven-cdist at armstrong.cc) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +key="$(cat "$__object/parameter/key" 2>/dev/null \ + || echo "$__object_id")" +state="$(cat "$__object/parameter/state" 2>/dev/null \ + || echo "present")" +file="$(cat "$__object/parameter/file")" +delimiter="$(cat "$__object/parameter/delimiter")" +value="$(cat "$__object/parameter/value" 2>/dev/null \ + || echo "__CDIST_NOTSET__")" + +case "$state" in + absent) + if grep -q -E "^$key$delimiter+" "$file"; then + # if the key exists, with whatever value, we will have to remove it + # so report it as present + echo present + else + # key does not exist + echo absent + fi + ;; + present) + if grep -q -E "^$key$delimiter+$value$" "$file"; then + # key exists and value is same + echo present + elif grep -q -E "^$key$delimiter+" "$file"; then + # key exists, but value is empty or different + echo wrongvalue + else + # key does not exist + echo absent + fi + ;; +esac diff --git a/conf/type/__key_value/gencode-remote b/conf/type/__key_value/gencode-remote index eff0925c..0846dca1 100755 --- a/conf/type/__key_value/gencode-remote +++ b/conf/type/__key_value/gencode-remote @@ -18,35 +18,40 @@ # along with cdist. If not, see . # -value_is="$(cat "$__object/explorer/value")" -value_should="$(cat "$__object/parameter/value")" - key="$(cat "$__object/parameter/key")" file="$(cat "$__object/parameter/file")" delimiter="$(cat "$__object/parameter/delimiter")" +value="$(cat "$__object/parameter/value")" -if [ "$value_is" != "$value_should" ]; then - case "$value_is" in - __NOTSET__) - # add key and value - echo "echo \"${key}${delimiter}${value_should}\" >> \"$file\"" - ;; - *) - if [ "$value_should" = '__NOTSET__' ]; then - # remove key and value - cat << DONE -sed -i '/^${key}/d' "$file" -DONE - else - # change value - cat << DONE -awk -F "$delimiter" ' -/${key}${delimiter}*/{gsub("$value_is", "$value_should")};{print}' "$file" > "${file}+" \ -&& mv "${file}+" "$file" +state_is="$(cat "$__object/explorer/state")" +state_should="$(cat "$__object/parameter/state")" -DONE - fi - ;; - esac +if [ "$state_is" = "$state_should" ]; then + # nothing to do + exit 0 fi +case "$state_should" in + absent) + # remove lines starting with key + echo "sed -i '/^$key\($delimiter\+\)/d' \"$file\"" + ;; + present) + case "$state_is" in + absent) + # add new key and value + echo "echo \"${key}${delimiter}${value}\" >> \"$file\"" + ;; + wrongvalue) + # change exisiting value + echo "sed -i \"s|^$key\($delimiter\+\).*|$key\1$value|\" \"$file\"" + ;; + *) + echo "Unknown explorer state: $state_is" >&2 + exit 1 + esac + ;; + *) + echo "Unknown state: $state_should" >&2 + exit 1 +esac diff --git a/conf/type/__key_value/man.text b/conf/type/__key_value/man.text index 3e4e8013..1423fc7d 100644 --- a/conf/type/__key_value/man.text +++ b/conf/type/__key_value/man.text @@ -16,9 +16,6 @@ file. REQUIRED PARAMETERS ------------------- -value:: - The value for the key. Setting the value to `__NOTSET__` will remove the key - from the file. file:: The file to operate on. delimiter:: @@ -27,8 +24,13 @@ delimiter:: OPTIONAL PARAMETERS ------------------- +state:: + present or absent, defaults to present. If present, sets the key to value, + if absent, removes the key from the file. key:: The key to change. Defaults to object_id. +value:: + The value for the key. Optional if state=absent, required otherwise. EXAMPLES @@ -45,6 +47,9 @@ __key_value my-fancy-id --file /etc/login.defs --key SYS_UID_MAX --value 666 \ # Enable packet forwarding __key_value net.ipv4.ip_forward --file /etc/sysctl.conf --value 1 \ --delimiter '=' + +# Remove existing key/value +__key_value LEGACY_KEY --file /etc/somefile --state absent --delimiter '=' -------------------------------------------------------------------------------- diff --git a/conf/type/__key_value/manifest b/conf/type/__key_value/manifest index 706b0b0d..2e75e175 100755 --- a/conf/type/__key_value/manifest +++ b/conf/type/__key_value/manifest @@ -18,9 +18,13 @@ # along with cdist. If not, see . # -if [ -f "$__object/parameter/key" ]; then - key="$(cat "$__object/parameter/key")" -else - echo "$__object_id" > "$__object/parameter/key" -fi +# set defaults +key="$(cat "$__object/parameter/key" 2>/dev/null \ + || echo "$__object_id" | tee "$__object/parameter/key")" +state="$(cat "$__object/parameter/state" 2>/dev/null \ + || echo "present" | tee "$__object/parameter/state")" +if [ "$state" = "present" -a ! -f "$__object/parameter/value" ]; then + echo "Missing required parameter 'value'" >&2 + exit 1 +fi diff --git a/conf/type/__key_value/parameter/optional b/conf/type/__key_value/parameter/optional index 06bfde49..483e3192 100644 --- a/conf/type/__key_value/parameter/optional +++ b/conf/type/__key_value/parameter/optional @@ -1 +1,3 @@ key +value +state diff --git a/conf/type/__key_value/parameter/required b/conf/type/__key_value/parameter/required index 8f4aa53c..3ae10da3 100644 --- a/conf/type/__key_value/parameter/required +++ b/conf/type/__key_value/parameter/required @@ -1,3 +1,2 @@ -value file delimiter diff --git a/conf/type/__package_pip/explorer/state b/conf/type/__package_pip/explorer/state index 3a086e58..5be07280 100644 --- a/conf/type/__package_pip/explorer/state +++ b/conf/type/__package_pip/explorer/state @@ -35,14 +35,15 @@ else pip="pip" fi -# which is not posix, but command is :-) +# If there is no pip, it may get created from somebody else. +# If it will be created, there is probably no package installed. if ! command -v "$pip" >/dev/null 2>&1; then - echo "No usable pip found at path \"$pip\"" >&2 - exit 1 -fi - -if "$pip" freeze | grep -i -q "^$name=="; then - echo present -else echo absent +else + + if "$pip" freeze | grep -i -q "^$name=="; then + echo present + else + echo absent + fi fi diff --git a/conf/type/__package_pip/man.text b/conf/type/__package_pip/man.text index bc773763..2a620658 100644 --- a/conf/type/__package_pip/man.text +++ b/conf/type/__package_pip/man.text @@ -1,5 +1,5 @@ cdist-type__package_pip(7) -============================= +========================== Nico Schottelius diff --git a/conf/type/__start_on_boot/explorer/state b/conf/type/__start_on_boot/explorer/state new file mode 100755 index 00000000..ff092a65 --- /dev/null +++ b/conf/type/__start_on_boot/explorer/state @@ -0,0 +1,64 @@ +#!/bin/sh +# +# 2012 Nico Schottelius (nico-cdist at schottelius.org) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# +# Check whether the given name will be started on boot or not +# + +os=$("$__explorer/os") +runlevel=$("$__explorer/runlevel") +name="$__object_id" + + +case "$os" in + archlinux) + # convert bash array to shell + daemons=$(grep ^DAEMONS /etc/rc.conf | sed -e 's/^.*=(//' -e 's/)$//') + + # absent, as long as not found + state="absent" + + # iterate, last one wins. + for daemon in $daemons; do + if [ "$daemon" = "$name" -o "$daemon" = "@${name}" ]; then + state="present" + elif [ "$daemon" = "!${name}" ]; then + state="absent" + fi + done + ;; + + debian|ubuntu) + state="present" + [ -f "/etc/rc$runlevel.d/S"??"$name" ] || state="absent" + ;; + + centos|fedora|owl|redhat) + state="present" + state=$(chkconfig --level "$runlevel" \"$name\" || echo absent) + [ "$state" ] || state="present" + ;; + + *) + echo "Unsupported os: $os" >&2 + exit 1 + ;; +esac + +echo $state diff --git a/conf/type/__start_on_boot/gencode-remote b/conf/type/__start_on_boot/gencode-remote new file mode 100755 index 00000000..be2bd98b --- /dev/null +++ b/conf/type/__start_on_boot/gencode-remote @@ -0,0 +1,89 @@ +#!/bin/sh +# +# 2012 Nico Schottelius (nico-cdist at schottelius.org) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# + +state_should="$(cat "$__object/parameter/state")" +state_is=$(cat "$__object/explorer/state") + +# Nothing todo, go away +[ "$state_should" = "$state_is" ] && exit 0 + +os=$(cat "$__global/explorer/os") +name="$__object_id" + +case "$state_should" in + present) + case "$os" in + archlinux) + echo "sed -i /etc/rc.conf \'s/^\\(DAEMONS=.*\\))/\\1 $name)/\'" + ;; + debian|ubuntu) + echo "update-rc.d \"$name\" defaults >/dev/null" + ;; + +# FIXME: Disabled until the explorer is checked +# gentoo) +# echo rc-update add \"$name\" default +# ;; + + centos|fedora|owl|redhat) + echo chkconfig \"$name\" on + ;; + + *) + echo "Unsupported os: $os" >&2 + exit 1 + ;; + esac + ;; + + absent) + case "$os" in + archlinux) + # Replace a) at the beginning b) in the middle c) end d) only + # Support @name as well...makes it more ugly, but well... + echo "sed -i /etc/rc.conf -e 's/^\\(DAEMONS=(\\)@\\{0,1\\}$name /\\1/' -e 's/^\\(DAEMONS=(.* \\)@\\{0,1\\}$name \\(.*\\)/\\1\\2/' -e 's/^\\(DAEMONS=(.*\\) @\\{0,1\\}$name)/\\1)/' -e 's/^\\(DAEMONS=(\\)@\\{0,1\\}$name)/\\1)/'" + ;; + debian|ubuntu) + echo update-rc.d -f \"$name\" remove + ;; + +# FIXME: Disabled until the explorer is checked +# gentoo) +# echo rc-update del \"$name\" +# ;; + + centos|fedora|owl|redhat) + echo chkconfig \"$name\" off + ;; + + *) + echo "Unsupported os: $os" >&2 + exit 1 + ;; + esac + + ;; + + *) + echo "Unknown state: $state_should" >&2 + exit 1 + ;; +esac diff --git a/conf/type/__start_on_boot/man.text b/conf/type/__start_on_boot/man.text new file mode 100644 index 00000000..0e75c9ab --- /dev/null +++ b/conf/type/__start_on_boot/man.text @@ -0,0 +1,53 @@ +cdist-type__start_on_boot(7) +============================ +Nico Schottelius + + +NAME +---- +cdist-type__start_on_boot - Manage stuff to be started at boot + + +DESCRIPTION +----------- +This cdist type allows you to enable or disable stuff to be started +at boot of your operating system. + +Warning: This type has not been tested intensively and is not fully +supported (i.e. gentoo and *bsd are not implemented). + + +REQUIRED PARAMETERS +------------------- +None. + +OPTIONAL PARAMETERS +------------------- +state:: + 'present' or 'absent', defaults to 'present' + + +EXAMPLES +-------- + +-------------------------------------------------------------------------------- +# Ensure snmpd is started at boot +__start_on_boot snmpd + +# Same, but more explicit +__start_on_boot snmpd --state present + +# Ensure legacy configuration management will not be started +__start_on_boot puppet --state absent +-------------------------------------------------------------------------------- + + +SEE ALSO +-------- +- cdist-type(7) + + +COPYING +------- +Copyright \(C) 2012 Nico Schottelius. Free use of this software is +granted under the terms of the GNU General Public License version 3 (GPLv3). diff --git a/conf/type/__key_value/explorer/value b/conf/type/__start_on_boot/manifest similarity index 62% rename from conf/type/__key_value/explorer/value rename to conf/type/__start_on_boot/manifest index 3afc7cc5..6b5e1ca7 100755 --- a/conf/type/__key_value/explorer/value +++ b/conf/type/__start_on_boot/manifest @@ -1,6 +1,7 @@ #!/bin/sh # # 2011 Steven Armstrong (steven-cdist at armstrong.cc) +# 2012 Nico Schottelius (nico-cdist at schottelius.org) # # This file is part of cdist. # @@ -17,21 +18,7 @@ # You should have received a copy of the GNU General Public License # along with cdist. If not, see . # -# -# Get the current value of key or __NOTSET__ if the key doesn't exist. -# - -if [ -f "$__object/parameter/key" ]; then - key="$(cat "$__object/parameter/key")" -else - key="$__object_id" -fi -file="$(cat "$__object/parameter/file")" -delimiter="$(cat "$__object/parameter/delimiter")" - -awk -F "$delimiter" ' -BEGIN { found=0 } -/^'$key'/ { print $2; found=1 } -END { if (found) exit 0; else exit 1 }' "$file" \ -|| echo "__NOTSET__" +# set default: present, if not setup +statefile="$__object/parameter/state" +[ -f "$statefile" ] || echo present > "$statefile" diff --git a/conf/type/__start_on_boot/parameter/optional b/conf/type/__start_on_boot/parameter/optional new file mode 100644 index 00000000..ff72b5c7 --- /dev/null +++ b/conf/type/__start_on_boot/parameter/optional @@ -0,0 +1 @@ +state diff --git a/conf/type/__user/gencode-remote b/conf/type/__user/gencode-remote index 595c7f64..8979c56e 100755 --- a/conf/type/__user/gencode-remote +++ b/conf/type/__user/gencode-remote @@ -28,6 +28,7 @@ cd "$__object/parameter" if grep -q "^${name}:" "$__object/explorer/passwd"; then for property in $(ls .); do new_value="$(cat "$property")" + unset current_value file="$__object/explorer/passwd" @@ -36,9 +37,16 @@ if grep -q "^${name}:" "$__object/explorer/passwd"; then if $(echo "$new_value" | grep -q '^[0-9][0-9]*$'); then field=4 else - # group name - file="$__object/explorer/group" - field=1 + # We were passed a group name. Compare the gid in + # the user's /etc/passwd entry with the gid of the + # group returned by the group explorer. + gid_from_group=$(awk -F: '{ print $3 }' "$__object/explorer/group") + gid_from_passwd=$(awk -F: '{ print $4 }' "$file") + if [ "$gid_from_group" != "$gid_from_passwd" ]; then + current_value="$gid_from_passwd" + else + current_value="$new_value" + fi fi ;; password) @@ -51,8 +59,12 @@ if grep -q "^${name}:" "$__object/explorer/passwd"; then uid) field=3 ;; esac - export field - current_value="$(awk -F: '{ print $ENVIRON["field"] }' < "$file")" + # If we haven't already set $current_value above, pull it from the + # appropriate file/field. + if [ -z "$current_value" ]; then + export field + current_value="$(awk -F: '{ print $ENVIRON["field"] }' < "$file")" + fi if [ "$new_value" != "$current_value" ]; then set -- "$@" "--$property" \'$new_value\' diff --git a/doc/changelog b/doc/changelog index 7e070cc5..ab006435 100644 --- a/doc/changelog +++ b/doc/changelog @@ -4,11 +4,22 @@ Changelog * Changes are always commented with their author in (braces) * Exception: No braces means author == Nico Schottelius -2.0.7: +2.0.8: + * Cleanup: Better hint to source of error + * Cleanup: Do not output failing script, but path to script only + * Cleanup: Remove support for __debug variable in manifests (Type != Core + debugging) + * Feature Core: Support boolean parameters (Steven Armstrong) + +2.0.7: 2012-02-13 * Bugfix __file: Use chmod after chown/chgrp (Matt Coddington) * Bugfix __user: Correct shadow field in explorer (Matt Coddington) * Bugfix __link: Properly handle existing links (Steven Armstrong) + * Bugfix __key_value: More robust implementation (Steven Armstrong) + * Bugfix __user: Fix for changing a user's group by name (Matt Coddington) * New Type: __package_pip + * Bugfix/Cleanup: Correctly allow Object ID to start and end with /, but + not contain //. 2.0.6: 2012-01-28 * Bugfix __apt_ppa: diff --git a/doc/dev/logs/2012-02-10.object_id-and-slashes b/doc/dev/logs/2012-02-10.object_id-and-slashes new file mode 100644 index 00000000..de46a1f8 --- /dev/null +++ b/doc/dev/logs/2012-02-10.object_id-and-slashes @@ -0,0 +1,18 @@ +__typename /foo/bar # possible, usual use case +require="__a//b" __typename /foo/bar # possible and happens often for __a/$id in loops + +__typename /foo/bar/ # trailing slash will be stripped, can be documented + +__typename /foo//bar//baz # // will be converted to / implicitly through fs; error prone; disallow + +require="__a//b//c" __typename # // will be converted to / implicitly through fs; error prone; disallow + + +Solution: + + 1) allow require __a//b: type __a, object id /b + => strip first slash of object id, as we do in non-dep-mode + 2) allow _one_ trailing /: __type /foo/bar/ and require="__foo/abc/" + => strip one leading slash of object id + 3) disallow // within object id + 4) disallow starting or ending / after 1) and 2) diff --git a/doc/dev/logs/2012-02-13.dependencies b/doc/dev/logs/2012-02-13.dependencies new file mode 100644 index 00000000..3b0f3f21 --- /dev/null +++ b/doc/dev/logs/2012-02-13.dependencies @@ -0,0 +1,23 @@ +possible dependencies: + + - unix pattern __foo/* + - object: __foo//bar, __foo/bar + - singleton with object_id: __foo/singleton + - singleton without object_id: __foo/ + +solving dependencies: + + solve_dep(object, run_list): + - list = [me] + - if status == IN_DEPENDENCY: + fail: circular dependency + - status = IN_DEPENDENCY + - create_list_of_deps(object) + - try pattern expansion + - for each dependency: + if object does not exist: + fail + else: + list.append(solve_dep(object, run_list)): + - status == IN_LIST + - return [me, dependencies [, dependencies of dependencies]] diff --git a/doc/dev/logs/2012-02-15.steven b/doc/dev/logs/2012-02-15.steven new file mode 100644 index 00000000..2d513728 --- /dev/null +++ b/doc/dev/logs/2012-02-15.steven @@ -0,0 +1,132 @@ +- parameter/setting default from manifest + ==> BRANCH[feature_default_parameters], + ==> PERSON[Steven or Nico] + ==> PROPOSAL(1) + - current bug + - proposal 1: parameter/default/$name (for optional ones) + - new way + - catches --state absent|present + - needs changes of types + - also possible for explorer + - support for it in core? + - handling of ${o} $o "$o" ? + - handling which variables? + - introduction of "templating language" + - aka macros + - possible problems: + - inconsistency + - redoing shell functionality + - raising expectations for more templating from users + - possible benefit + - no need for eval + - once in core, not everytime in type + - OTOH: one extra word. + - a=$(cat $__object/parameter/name) vs. $(eval $(cat $__object/parameter/name)) + - only possible for static defaults + - --name overrides name not possible vs. object_id + - Is this the only case???? + - if yes: don't care. + - possible solution: + - echo '/$__object_id' > typename/parameter/default/name + - eval $(cat $__object/parameter/name) + - probably allows code injection + - is possible anyway??? + - $(cat /etc/shadow) + - other eval side effects??? + - none: go for it + - some: have headache + - many: don't do + - proposal 2: 2 dbs (user input vs. stuff changable by type) + - explicit 2nd db [parameter_user and parameter/] + - not very clean (both agreed) + - proposal 3: parameter are read-only + - breaks current types (in core probably elsewhere) + - can't enforce, but user is on his own => breaks, her problem + + clean seperation between core and type (nico) + - parameter belongs to type not core (steven) + - proposal 4: core ignores changes in parameter/* of object + - implicit 2nd db [see automagic below] + - steven+++ + - does not work with divergent emulator not being in core + - because emulators primary db __is__ fs. + +1 manifest: + +__foo bar == emulator +echo present > $__global/object/__foo/bar/parameter/state + +# fails +__foo bar == emulator + +! automagic / filesystem + ! fsproperty: + - kill, write explicitly to disk + ==> BRANCH[cleanup_fsproperty] + ==> PERSON[Steven] + ==> PROPOSAL(just cleanup) + + - implicit/automatic writes/read to fs + - explicit interfaces are better then implicit + - same problems as in cdist 1.x to 2.x move! (environment!) + - format on disk should not change/dictate code flow + - degrade python to shell (nico++! steven--) + - user should not care about python, ruby, .net or ASM implementation (steven++ nico++) + + ? proposal 1: diverge emulator / core + - emulator verifies input + - emulator writes to fs + - core reads/syncs from/to fs before passing control to user + + ? proposal 2: emulator is dumb and passes data to core + - core creates objects + - no fs involved + - core reads/syncs from/to fs before passing control to user + - passing: + - full objects via pickle + - parameters only + - how??? + - unix socket? + - not everywhere possible? + - tcp / ip + - not everywhere possible + - chroot / local only + - rfc 1149 + - not everywhere possible + - missing avian carriers + - 0mq + - not everywhere possible + - not installed + - shm (ipcs and friends) + - not everywhere possible + - no /dev/shm, different libraries? cleanups needed... + - what speaks against FS? + - emulator_input/.../ + + - nico: to fancy probably + +! boolean implementation + ==> BRANCH[feature_boolean_parameter] + ==> PERSON[Steven] + - nico: + - parameters/boolean: document + - argparse changes (consider parameters/boolean) + - create + - can be implemented with changes in emulator + - use store_true, del if false => never seen by core + - INDEPENDENT INDEPENDT OF FS.PROPERTIES!!111111! + +- emulator: + - how much integrated into core + - also: using CdistObject???? + - dependency on filesystem: good (nico) | bad (steven) + +- singleton / support without object_id + - not discussed + +- __apt_ppa: + ==> BRANCH[bugfix_do_not_change_state_in_manifest] + ==> PERSON[Nico] + +- logging divergent between emulator / core + - no problem (nico) + - may be helpful (steven) diff --git a/doc/dev/releasechecklist b/doc/dev/releasechecklist index 19ab7b18..eba81dc0 100755 --- a/doc/dev/releasechecklist +++ b/doc/dev/releasechecklist @@ -15,11 +15,21 @@ changelog_version=$(grep '^[[:digit:]]' doc/changelog | head -n1 | sed 's/:.*//' #git_version=$(git describe) lib_version=$(grep ^VERSION lib/cdist/__init__.py | sed -e 's/.*= //' -e 's/"//g') +# get date +date_today="$(date +%Y-%m-%d)" +date_changelog=$(grep '^[[:digit:]]' doc/changelog | head -n1 | sed 's/.*: //') + echo "Ensure you fixed/prepared version files: $files" echo "changelog: $changelog_version" #echo "git: $git_version" echo "lib: $lib_version" +if [ "$date_today" != "$date_changelog" ]; then + echo "Messed up date, not releasing:" + echo "Changelog: $date_changelog" + exit 1 +fi + if [ "$lib_version" != "$changelog_version" ]; then echo "Messed up versions, not releasing" exit 1 diff --git a/doc/dev/todo/TAKEME b/doc/dev/todo/TAKEME index 95bda7fb..b40936f6 100644 --- a/doc/dev/todo/TAKEME +++ b/doc/dev/todo/TAKEME @@ -35,3 +35,5 @@ USER INTERFACE TYPES ------ - Add testing framework (proposed by Evax Software) +- __user + add option to include --create-home diff --git a/doc/dev/todo/niconext b/doc/dev/todo/niconext index 4db98d58..bead6d72 100644 --- a/doc/dev/todo/niconext +++ b/doc/dev/todo/niconext @@ -1,11 +1,13 @@ -- __file/foo/bar//bar - - fails later +- introduce default parameters + +- cleanup object_id handling + - have a look at singletons -- __user - add option to include --create-home - ensure that all types, which support --state support present and absent (consistent look and feel) +-------------------------------------------------------------------------------- + - update/create docs - cdist-cache:: How to get use information about the hosts we have been working on [advanced] diff --git a/doc/man/cdist-reference.text.sh b/doc/man/cdist-reference.text.sh index 898771c7..a76e7941 100755 --- a/doc/man/cdist-reference.text.sh +++ b/doc/man/cdist-reference.text.sh @@ -49,10 +49,10 @@ The following global explorers are available: eof ( - cd ../../conf/explorer - for explorer in *; do - echo "- $explorer" - done + cd ../../conf/explorer + for explorer in *; do + echo "- $explorer" + done ) cat << eof @@ -62,77 +62,80 @@ PATHS If not specified otherwise, all paths are relative to the checkout directory. conf/:: - Contains the (static) configuration like manifests, types and explorers. + Contains the (static) configuration like manifests, types and explorers. conf/manifest/init:: - This is the central entry point used by cdist-manifest-init(1). - It is an executable (+x bit set) shell script that can use - values from the explorers to decide which configuration to create - for the specified target host. - It should be primary used to define mapping from configurations to hosts. + This is the central entry point used by cdist-manifest-init(1). + It is an executable (+x bit set) shell script that can use + values from the explorers to decide which configuration to create + for the specified target host. + It should be primary used to define mapping from configurations to hosts. conf/manifest/*:: - All other files in this directory are not directly used by cdist, but you - can seperate configuration mappings, if you have a lot of code in the - manifest/init file. This may also be helpful to have different admins - maintain different groups of hosts. + All other files in this directory are not directly used by cdist, but you + can seperate configuration mappings, if you have a lot of code in the + manifest/init file. This may also be helpful to have different admins + maintain different groups of hosts. conf/explorer/:: - Contains explorers to be run on the target hosts, see cdist-explorer(7). + Contains explorers to be run on the target hosts, see cdist-explorer(7). conf/type/:: - Contains all available types, which are used to provide - some kind of functionality. See cdist-type(7). + Contains all available types, which are used to provide + some kind of functionality. See cdist-type(7). conf/type//:: - Home of the type . + Home of the type . - This directory is referenced by the variable __type (see below). + This directory is referenced by the variable __type (see below). conf/type//man.text:: - Manpage in Asciidoc format (required for inclusion into upstream) + Manpage in Asciidoc format (required for inclusion into upstream) conf/type//manifest:: - Used to generate additional objects from a type. + Used to generate additional objects from a type. conf/type//gencode-local:: - Used to generate code to be executed on the server. + Used to generate code to be executed on the server. conf/type//gencode-remote:: - Used to generate code to be executed on the client. + Used to generate code to be executed on the client. -conf/type//parameters/required:: - Parameters required by type, \n seperated list. +conf/type//parameter/required:: + Parameters required by type, \n seperated list. -conf/type//parameters/optional:: - Parameters optionally accepted by type, \n seperated list. +conf/type//parameter/optional:: + Parameters optionally accepted by type, \n seperated list. + +conf/type//parameter/boolean:: + Boolean parameters accepted by type, \n seperated list. conf/type//explorer:: - Location of the type specific explorers. - This directory is referenced by the variable __type_explorer (see below). - See cdist-explorer(7). + Location of the type specific explorers. + This directory is referenced by the variable __type_explorer (see below). + See cdist-explorer(7). out/:: - This directory contains output of cdist and is usually located - in a temporary directory and thus will be removed after the run. - This directory is referenced by the variable __global (see below). + This directory contains output of cdist and is usually located + in a temporary directory and thus will be removed after the run. + This directory is referenced by the variable __global (see below). out/explorer:: - Output of general explorers. + Output of general explorers. out/object:: - Objects created for the host. + Objects created for the host. out/object/:: - Contains all object specific information. - This directory is referenced by the variable __object (see below). + Contains all object specific information. + This directory is referenced by the variable __object (see below). out/object//explorers:: - Output of type specific explorers, per object. + Output of type specific explorers, per object. tmp_dir:: - A tempdir and a tempfile is used by cdist internally, - which will be removed when the scripts end automatically. + A tempdir and a tempfile is used by cdist internally, + which will be removed when the scripts end automatically. TYPES ----- @@ -141,13 +144,13 @@ The following types are available: eof for type in man7/cdist-type__*.text; do - no_dir="${type#man7/}"; - no_type="${no_dir#cdist-type}"; - name="${no_type%.text}"; - name_no_underline="$(echo $name | sed 's/^__/\\__/g')" - man="${no_dir%.text}(7)" + no_dir="${type#man7/}"; + no_type="${no_dir#cdist-type}"; + name="${no_type%.text}"; + name_no_underline="$(echo $name | sed 's/^__/\\__/g')" + man="${no_dir%.text}(7)" - echo "- $name_no_underline" "($man)" + echo "- $name_no_underline" "($man)" done cat << eof @@ -159,43 +162,47 @@ For object to object communication and tests, the following paths are usable within a object directory: changed:: - This empty file exists in an object directory, if the object has - code to be excuted (either remote or local) + This empty file exists in an object directory, if the object has + code to be excuted (either remote or local) ENVIRONMENT VARIABLES --------------------- __explorer:: - Directory that contains all global explorers. - Available for: explorer + Directory that contains all global explorers. + Available for: explorer, type explorer __manifest:: - Directory that contains the initial manifest. - Available for: initial manifest + Directory that contains the initial manifest. + Available for: initial manifest __global:: - Directory that contains generic output like explorer. - Available for: initial manifest, type manifest, type gencode + Directory that contains generic output like explorer. + Available for: initial manifest, type manifest, type gencode __object:: - Directory that contains the current object. - Available for: type manifest, type explorer, type gencode + Directory that contains the current object. + Available for: type manifest, type explorer, type gencode __object_id:: - The type unique object id. - Available for: type manifest, type explorer, type gencode - Note: The leading "/" will always be stripped. + The type unique object id. + Available for: type manifest, type explorer, type gencode + + Note: The leading and the trailing "/" will always be stripped (caused by + the filesystem database and ensured by the core). + + Note: Double slashes ("//") will not be fixed and result in an error. __self:: - DEPRECATED: Same as __object_name, do not use anymore, use __object_name instead. - Will be removed in cdist 3.x. + DEPRECATED: Same as __object_name, do not use anymore, use __object_name instead. + Will be removed in cdist 3.x. __object_name:: - The full qualified name of the current object. - Available for: type manifest, type explorer, type gencode + The full qualified name of the current object. + Available for: type manifest, type explorer, type gencode __target_host:: - The host we are deploying to. - Available for: initial manifest, type manifest, type gencode + The host we are deploying to. + Available for: explorer, initial manifest, type explorer, type manifest, type gencode __type:: - Path to the current type. - Available for: type manifest, type gencode + Path to the current type. + Available for: type manifest, type gencode __type_explorer:: - Directory that contains the type explorers. - Available for: type explorer + Directory that contains the type explorers. + Available for: type explorer SEE ALSO diff --git a/doc/man/man7/cdist-hacker.text b/doc/man/man7/cdist-hacker.text index 9bdf63d4..646439a3 100644 --- a/doc/man/man7/cdist-hacker.text +++ b/doc/man/man7/cdist-hacker.text @@ -61,12 +61,19 @@ including it. HOW TO SUBMIT A NEW TYPE ------------------------ +For detailled information about types, see cdist-type(7). + Submitting a type works as described above, with the additional requirement that a corresponding manpage named man.text in asciidoc format with the manpage-name "cdist-type__NAME" is included in the type directory AND asciidoc is able to compile it (i.e. do NOT have to many "=" in the second line). +Warning: Submitting "exec" or "run" types that simply echo their parameter in +gencode* will not be accepted, because they are of no use. Every type can output +code and thus such a type introduces redundant functionality that is given by +core cdist already. + SEE ALSO -------- diff --git a/doc/man/man7/cdist-type.text b/doc/man/man7/cdist-type.text index 48d412f1..1147511e 100644 --- a/doc/man/man7/cdist-type.text +++ b/doc/man/man7/cdist-type.text @@ -40,7 +40,7 @@ A list of supported types can be found in the cdist-reference(7) manpage. SINGLETON TYPES --------------- -If a type is flagged as a singleton, it may be used only +If a type is flagged as a singleton, it may be used only once per host. This is useful for types which can be used only once on a system. Singleton types do not take an object name as argument. @@ -72,15 +72,42 @@ To begin a new type, just create the directory **conf/type/__NAME**. DEFINING PARAMETERS ------------------- -Every type consists of optional and required parameters, which must -be created in a newline seperated file in ***parameters/required*** and -***parameters/optional***. If either or both missing, the type will have -no required, no optional or no parameters at all. +Every type consists of required, optional and boolean parameters, which must +be created in a newline seperated file in ***parameter/required***, +***parameter/optional*** and ***parameter/boolean***. If either is missing, +the type will have no required, no optional, no boolean or no parameters at +all. Example: -------------------------------------------------------------------------------- echo servername >> conf/type/__nginx_vhost/parameter/required echo logdirectory >> conf/type/__nginx_vhost/parameter/optional +echo use_ssl >> conf/type/__nginx_vhost/parameter/boolean +-------------------------------------------------------------------------------- + + +USING PARAMETERS +---------------- +The parameters given to a type can be accessed and used in all type scripts +(e.g manifest, gencode-*, explorer/*). Note that boolean parameters are +represented by file existence. File exists -> True, +file does not exist -> False + +Example: (e.g. in conf/type/__nginx_vhost/manifest) +-------------------------------------------------------------------------------- +# required parameter +servername="$(cat "$__object/parameter/servername")" + +# optional parameter +if [ -f "$__object/parameter/logdirectory" ]; then + logdirectory="$(cat "$__object/parameter/logdirectory")" +fi + +# boolean parameter +if [ -f "$__object/parameter/use_ssl" ]; then + # file exists -> True + # do some fancy ssl stuff +fi -------------------------------------------------------------------------------- @@ -116,7 +143,7 @@ SINGLETON - ONLY INSTANCE ONLY ------------------------------ If you want to ensure that a type can only be used once per target, you can mark it as a singleton: Just create the (empty) file "singleton" in your type -directory: +directory: -------------------------------------------------------------------------------- touch conf/type/__NAME/singleton @@ -128,7 +155,7 @@ This will also change the way your type must be called: __YOURTYPE --parameter value -------------------------------------------------------------------------------- -As you can see, the object ID is omitted, because it does not make any sense, +As you can see, the object ID is omitted, because it does not make any sense, if your type can be used only once. diff --git a/lib/cdist/__init__.py b/lib/cdist/__init__.py index 664b6456..4742a937 100644 --- a/lib/cdist/__init__.py +++ b/lib/cdist/__init__.py @@ -19,7 +19,7 @@ # # -VERSION = "2.0.6" +VERSION = "2.0.7" BANNER = """ .. . .x+=:. s @@ -44,15 +44,17 @@ class Error(Exception): """Base exception class for this project""" pass +class CdistObjectError(Error): + """Something went wrong with an object""" + + def __init__(self, cdist_object, message): + self.name = cdist_object.name + self.source = " ".join(cdist_object.source) + self.message = message -class MissingEnvironmentVariableError(Error): - """Raised when a required environment variable is not set.""" - - def __init__(self, name): - self.name = name def __str__(self): - return 'Missing required environment variable: ' + str(self.name) + return '%s: %s (defined at %s)' % (self.name, self.message, self.source) def file_to_list(filename): """Return list from \n seperated file""" diff --git a/lib/cdist/config_install.py b/lib/cdist/config_install.py index 542f2024..7cce240e 100644 --- a/lib/cdist/config_install.py +++ b/lib/cdist/config_install.py @@ -89,9 +89,9 @@ class ConfigInstall(object): new_objects_created = True while new_objects_created: new_objects_created = False - for cdist_object in core.Object.list_objects(self.local.object_path, + for cdist_object in core.CdistObject.list_objects(self.local.object_path, self.local.type_path): - if cdist_object.state == core.Object.STATE_PREPARED: + if cdist_object.state == core.CdistObject.STATE_PREPARED: self.log.debug("Skipping re-prepare of object %s", cdist_object) continue else: @@ -103,16 +103,16 @@ class ConfigInstall(object): self.log.info("Running manifest and explorers for " + cdist_object.name) self.explorer.run_type_explorers(cdist_object) self.manifest.run_type_manifest(cdist_object) - cdist_object.state = core.Object.STATE_PREPARED + cdist_object.state = core.CdistObject.STATE_PREPARED def object_run(self, cdist_object): """Run gencode and code for an object""" self.log.debug("Trying to run object " + cdist_object.name) - if cdist_object.state == core.Object.STATE_DONE: + if cdist_object.state == core.CdistObject.STATE_DONE: # TODO: remove once we are sure that this really never happens. raise cdist.Error("Attempting to run an already finished object: %s", cdist_object) - cdist_type = cdist_object.type + cdist_type = cdist_object.cdist_type # Generate self.log.info("Generating and executing code for " + cdist_object.name) @@ -130,13 +130,13 @@ class ConfigInstall(object): # Mark this object as done self.log.debug("Finishing run of " + cdist_object.name) - cdist_object.state = core.Object.STATE_DONE + cdist_object.state = core.CdistObject.STATE_DONE def stage_run(self): """The final (and real) step of deployment""" self.log.info("Generating and executing code") - objects = core.Object.list_objects( + objects = core.CdistObject.list_objects( self.local.object_path, self.local.type_path) diff --git a/lib/cdist/core/__init__.py b/lib/cdist/core/__init__.py index c61c659b..66ee00a5 100644 --- a/lib/cdist/core/__init__.py +++ b/lib/cdist/core/__init__.py @@ -19,11 +19,11 @@ # # -from cdist.core.type import Type -from cdist.core.type import NoSuchTypeError -from cdist.core.object import Object -from cdist.core.object import IllegalObjectIdError -from cdist.core.object import OBJECT_MARKER -from cdist.core.explorer import Explorer -from cdist.core.manifest import Manifest -from cdist.core.code import Code +from cdist.core.cdist_type import CdistType +from cdist.core.cdist_type import NoSuchTypeError +from cdist.core.cdist_object import CdistObject +from cdist.core.cdist_object import IllegalObjectIdError +from cdist.core.cdist_object import OBJECT_MARKER +from cdist.core.explorer import Explorer +from cdist.core.manifest import Manifest +from cdist.core.code import Code diff --git a/lib/cdist/core/object.py b/lib/cdist/core/cdist_object.py similarity index 74% rename from lib/cdist/core/object.py rename to lib/cdist/core/cdist_object.py index da2f21a6..e12bcfbd 100644 --- a/lib/cdist/core/object.py +++ b/lib/cdist/core/cdist_object.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # 2011 Steven Armstrong (steven-cdist at armstrong.cc) -# 2011 Nico Schottelius (nico-cdist at schottelius.org) +# 2011-2012 Nico Schottelius (nico-cdist at schottelius.org) # # This file is part of cdist. # @@ -42,7 +42,7 @@ class IllegalObjectIdError(cdist.Error): return '%s: %s' % (self.message, self.object_id) -class Object(object): +class CdistObject(object): """Represents a cdist object. All interaction with objects in cdist should be done through this class. @@ -61,7 +61,7 @@ class Object(object): """Return a list of object instances""" for object_name in cls.list_object_names(object_base_path): type_name, object_id = cls.split_name(object_name) - yield cls(cdist.core.Type(type_base_path, type_name), object_base_path, object_id=object_id) + yield cls(cdist.core.CdistType(type_base_path, type_name), object_base_path, object_id=object_id) @classmethod def list_type_names(cls, object_base_path): @@ -96,30 +96,58 @@ class Object(object): """ return os.path.join(type_name, object_id) - @staticmethod - def validate_object_id(object_id): + def validate_object_id(self): + # FIXME: also check that there is no object ID when type is singleton? + """Validate the given object_id and raise IllegalObjectIdError if it's not valid. """ - if object_id: - if object_id.startswith('/'): - raise IllegalObjectIdError(object_id, 'object_id may not start with /') - if OBJECT_MARKER in object_id.split(os.sep): - raise IllegalObjectIdError(object_id, 'object_id may not contain \'%s\'' % OBJECT_MARKER) + if self.object_id: + if OBJECT_MARKER in self.object_id.split(os.sep): + raise IllegalObjectIdError(self.object_id, 'object_id may not contain \'%s\'' % OBJECT_MARKER) + if '//' in self.object_id: + raise IllegalObjectIdError(self.object_id, 'object_id may not contain //') + + # If no object_id and type is not singleton => error out + if not self.object_id and not self.cdist_type.is_singleton: + raise IllegalObjectIdError(self.object_id, + "Missing object_id and type is not a singleton.") def __init__(self, cdist_type, base_path, object_id=None): - self.validate_object_id(object_id) - self.type = cdist_type # instance of Type + self.cdist_type = cdist_type # instance of Type self.base_path = base_path self.object_id = object_id - self.name = self.join_name(self.type.name, self.object_id) - self.path = os.path.join(self.type.path, self.object_id, OBJECT_MARKER) + + self.validate_object_id() + self.sanitise_object_id() + + self.name = self.join_name(self.cdist_type.name, self.object_id) + self.path = os.path.join(self.cdist_type.path, self.object_id, OBJECT_MARKER) self.absolute_path = os.path.join(self.base_path, self.path) self.code_local_path = os.path.join(self.path, "code-local") self.code_remote_path = os.path.join(self.path, "code-remote") self.parameter_path = os.path.join(self.path, "parameter") + def object_from_name(self, object_name): + """Convenience method for creating an object instance from an object name. + + Mainly intended to create objects when resolving requirements. + + e.g: + .object_from_name('__other/object') -> + + """ + + base_path = self.base_path + type_path = self.cdist_type.base_path + + type_name, object_id = self.split_name(object_name) + + cdist_type = self.cdist_type.__class__(type_path, type_name) + + return self.__class__(cdist_type, base_path, object_id=object_id) + def __repr__(self): - return '' % self.name + return '' % self.name def __eq__(self, other): """define equality as 'name is the same'""" @@ -128,23 +156,23 @@ class Object(object): def __hash__(self): return hash(self.name) - def __lt__(self, other): return isinstance(other, self.__class__) and self.name < other.name - def object_from_name(self, object_name): - """Convenience method for creating an object instance from an object name. - - Mainly intended to create objects when resolving requirements. - - e.g: - .object_from_name('__other/object') -> - + def sanitise_object_id(self): """ - type_path = self.type.base_path - base_path = self.base_path - type_name, object_id = self.split_name(object_name) - return self.__class__(self.type.__class__(type_path, type_name), base_path, object_id=object_id) + Remove leading and trailing slash (one only) + """ + + # Allow empty object id for singletons + if self.object_id: + # Remove leading slash + if self.object_id[0] == '/': + self.object_id = self.object_id[1:] + + # Remove trailing slash + if self.object_id[-1] == '/': + self.object_id = self.object_id[:-1] # FIXME: still needed? @property diff --git a/lib/cdist/core/type.py b/lib/cdist/core/cdist_type.py similarity index 86% rename from lib/cdist/core/type.py rename to lib/cdist/core/cdist_type.py index 20365b8d..1d2472c4 100644 --- a/lib/cdist/core/type.py +++ b/lib/cdist/core/cdist_type.py @@ -34,7 +34,7 @@ class NoSuchTypeError(cdist.Error): return "Type '%s' does not exist at %s" % (self.type_path, self.type_absolute_path) -class Type(object): +class CdistType(object): """Represents a cdist type. All interaction with types in cdist should be done through this class. @@ -61,7 +61,7 @@ class Type(object): # name is second argument name = args[1] if not name in cls._instances: - instance = super(Type, cls).__new__(cls) + instance = super(CdistType, cls).__new__(cls) cls._instances[name] = instance # return instance so __init__ is called return cls._instances[name] @@ -82,9 +82,10 @@ class Type(object): self.__explorers = None self.__required_parameters = None self.__optional_parameters = None + self.__boolean_parameters = None def __repr__(self): - return '' % self.name + return '' % self.name def __eq__(self, other): return isinstance(other, self.__class__) and self.name == other.name @@ -144,3 +145,19 @@ class Type(object): finally: self.__optional_parameters = parameters return self.__optional_parameters + + @property + def boolean_parameters(self): + """Return a list of boolean parameters""" + if not self.__boolean_parameters: + parameters = [] + try: + with open(os.path.join(self.absolute_path, "parameter", "boolean")) as fd: + for line in fd: + parameters.append(line.strip()) + except EnvironmentError: + # error ignored + pass + finally: + self.__boolean_parameters = parameters + return self.__boolean_parameters diff --git a/lib/cdist/core/code.py b/lib/cdist/core/code.py index 51912559..2ffef9cf 100644 --- a/lib/cdist/core/code.py +++ b/lib/cdist/core/code.py @@ -92,17 +92,14 @@ class Code(object): '__global': self.local.out_path, } - if log.getEffectiveLevel() == logging.DEBUG: - self.env.update({'__debug': "yes" }) - def _run_gencode(self, cdist_object, which): - cdist_type = cdist_object.type + cdist_type = cdist_object.cdist_type script = os.path.join(self.local.type_path, getattr(cdist_type, 'gencode_%s_path' % which)) if os.path.isfile(script): env = os.environ.copy() env.update(self.env) env.update({ - '__type': cdist_object.type.absolute_path, + '__type': cdist_object.cdist_type.absolute_path, '__object': cdist_object.absolute_path, '__object_id': cdist_object.object_id, '__object_name': cdist_object.name, diff --git a/lib/cdist/core/explorer.py b/lib/cdist/core/explorer.py index 01c4c81d..d49b7ac4 100644 --- a/lib/cdist/core/explorer.py +++ b/lib/cdist/core/explorer.py @@ -73,8 +73,6 @@ class Explorer(object): '__target_host': self.target_host, '__explorer': self.remote.global_explorer_path, } - if self.log.getEffectiveLevel() == logging.DEBUG: - self.env.update({'__debug': "yes" }) self._type_explorers_transferred = [] ### global @@ -121,15 +119,28 @@ class Explorer(object): in the object. """ - self.log.debug("Transfering type explorers for type: %s", cdist_object.type) - self.transfer_type_explorers(cdist_object.type) + self.log.debug("Transfering type explorers for type: %s", cdist_object.cdist_type) + self.transfer_type_explorers(cdist_object.cdist_type) self.log.debug("Transfering object parameters for object: %s", cdist_object.name) self.transfer_object_parameters(cdist_object) - for explorer in self.list_type_explorer_names(cdist_object.type): + for explorer in self.list_type_explorer_names(cdist_object.cdist_type): output = self.run_type_explorer(explorer, cdist_object) self.log.debug("Running type explorer '%s' for object '%s'", explorer, cdist_object.name) cdist_object.explorers[explorer] = output + def run_type_explorer(self, explorer, cdist_object): + """Run the given type explorer for the given object and return it's output.""" + cdist_type = cdist_object.cdist_type + env = self.env.copy() + env.update({ + '__object': os.path.join(self.remote.object_path, cdist_object.path), + '__object_id': cdist_object.object_id, + '__object_fq': cdist_object.path, + '__type_explorer': os.path.join(self.remote.type_path, cdist_type.explorer_path) + }) + script = os.path.join(self.remote.type_path, cdist_type.explorer_path, explorer) + return self.remote.run_script(script, env=env, return_output=True) + def transfer_type_explorers(self, cdist_type): """Transfer the type explorers for the given type to the remote side.""" if cdist_type.explorers: @@ -149,16 +160,3 @@ class Explorer(object): destination = os.path.join(self.remote.object_path, cdist_object.parameter_path) self.remote.mkdir(destination) self.remote.transfer(source, destination) - - def run_type_explorer(self, explorer, cdist_object): - """Run the given type explorer for the given object and return it's output.""" - cdist_type = cdist_object.type - env = self.env.copy() - env.update({ - '__object': os.path.join(self.remote.object_path, cdist_object.path), - '__object_id': cdist_object.object_id, - '__object_fq': cdist_object.path, - '__type_explorer': os.path.join(self.remote.type_path, cdist_type.explorer_path) - }) - script = os.path.join(self.remote.type_path, cdist_type.explorer_path, explorer) - return self.remote.run_script(script, env=env, return_output=True) diff --git a/lib/cdist/core/manifest.py b/lib/cdist/core/manifest.py index 704a3978..8b784229 100644 --- a/lib/cdist/core/manifest.py +++ b/lib/cdist/core/manifest.py @@ -87,7 +87,7 @@ class Manifest(object): self.local.run_script(script, env=env) def run_type_manifest(self, cdist_object): - script = os.path.join(self.local.type_path, cdist_object.type.manifest_path) + script = os.path.join(self.local.type_path, cdist_object.cdist_type.manifest_path) if os.path.isfile(script): env = os.environ.copy() env.update(self.env) @@ -96,7 +96,7 @@ class Manifest(object): '__object_id': cdist_object.object_id, '__object_name': cdist_object.name, '__self': cdist_object.name, - '__type': cdist_object.type.absolute_path, + '__type': cdist_object.cdist_type.absolute_path, '__cdist_manifest': script, }) self.local.run_script(script, env=env) diff --git a/lib/cdist/emulator.py b/lib/cdist/emulator.py index 05202a39..c4b84feb 100644 --- a/lib/cdist/emulator.py +++ b/lib/cdist/emulator.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# 2011 Nico Schottelius (nico-cdist at schottelius.org) +# 2011-2012 Nico Schottelius (nico-cdist at schottelius.org) # # This file is part of cdist. # @@ -26,29 +26,22 @@ import os import cdist from cdist import core - -class IllegalRequirementError(cdist.Error): - def __init__(self, requirement, message=None): - self.requirement = requirement - self.message = message or 'Illegal requirement' - - def __str__(self): - return '%s: %s' % (self.message, self.requirement) - class Emulator(object): def __init__(self, argv): self.argv = argv self.object_id = False self.global_path = os.environ['__global'] - self.object_source = os.environ['__cdist_manifest'] self.target_host = os.environ['__target_host'] + + # Internally only + self.object_source = os.environ['__cdist_manifest'] self.type_base_path = os.environ['__cdist_type_base_path'] self.object_base_path = os.path.join(self.global_path, "object") self.type_name = os.path.basename(argv[0]) - self.cdist_type = core.Type(self.type_base_path, self.type_name) + self.cdist_type = core.CdistType(self.type_base_path, self.type_name) self.__init_log() @@ -94,7 +87,7 @@ class Emulator(object): def commandline(self): """Parse command line""" - parser = argparse.ArgumentParser(add_help=False) + parser = argparse.ArgumentParser(add_help=False, argument_default=argparse.SUPPRESS) for parameter in self.cdist_type.optional_parameters: argument = "--" + parameter @@ -102,6 +95,9 @@ class Emulator(object): for parameter in self.cdist_type.required_parameters: argument = "--" + parameter parser.add_argument(argument, dest=parameter, action='store', required=True) + for parameter in self.cdist_type.boolean_parameters: + argument = "--" + parameter + parser.add_argument(argument, dest=parameter, action='store_const', const='') # If not singleton support one positional parameter if not self.cdist_type.is_singleton: @@ -113,20 +109,15 @@ class Emulator(object): def setup_object(self): - # FIXME: verify object id - - # Setup object_id + # Setup object_id - FIXME: unset / do not setup anymore! if self.cdist_type.is_singleton: self.object_id = "singleton" else: self.object_id = self.args.object_id[0] del self.args.object_id - # strip leading slash from object_id - self.object_id = self.object_id.lstrip('/') - # Instantiate the cdist object we are defining - self.cdist_object = core.Object(self.cdist_type, self.object_base_path, self.object_id) + self.cdist_object = core.CdistObject(self.cdist_type, self.object_base_path, self.object_id) # Create object with given parameters self.parameters = {} @@ -137,12 +128,15 @@ class Emulator(object): if self.cdist_object.exists: if self.cdist_object.parameters != self.parameters: raise cdist.Error("Object %s already exists with conflicting parameters:\n%s: %s\n%s: %s" - % (self.cdist_object, " ".join(self.cdist_object.source), self.cdist_object.parameters, self.object_source, self.parameters) + % (self.cdist_object.name, " ".join(self.cdist_object.source), self.cdist_object.parameters, self.object_source, self.parameters) ) else: self.cdist_object.create() self.cdist_object.parameters = self.parameters + # Record / Append source + self.cdist_object.source.append(self.object_source) + def record_requirements(self): """record requirements""" @@ -151,25 +145,17 @@ class Emulator(object): self.log.debug("reqs = " + requirements) for requirement in requirements.split(" "): # Ignore empty fields - probably the only field anyway - if len(requirement) == 0: - continue + if len(requirement) == 0: continue - requirement_type_name, requirement_object_id = core.Object.split_name(requirement) - # Instantiate type which fails if type does not exist - requirement_type = core.Type(self.type_base_path, requirement_type_name) - - if requirement_object_id: - # Validate object_id if any - core.Object.validate_object_id(requirement_object_id) - elif not requirement_type.is_singleton: - # Only singeltons have no object_id - raise IllegalRequirementError(requirement, "Missing object_id and type is not a singleton.") + # Raises an error, if object cannot be created + cdist_object = self.cdist_object.object_from_name(requirement) self.log.debug("Recording requirement: " + requirement) - self.cdist_object.requirements.append(requirement) - # Record / Append source - self.cdist_object.source.append(self.object_source) + # Save the sanitised version, not the user supplied one + # (__file//bar => __file/bar) + # This ensures pattern matching is done against sanitised list + self.cdist_object.requirements.append(cdist_object.name) def record_auto_requirements(self): """An object shall automatically depend on all objects that it defined in it's type manifest. diff --git a/lib/cdist/exec/local.py b/lib/cdist/exec/local.py index 613f5cf2..d3c6a0ce 100644 --- a/lib/cdist/exec/local.py +++ b/lib/cdist/exec/local.py @@ -32,18 +32,6 @@ import logging import cdist from cdist import core - -class LocalScriptError(cdist.Error): - def __init__(self, script, command, script_content): - self.script = script - self.command = command - self.script_content = script_content - - def __str__(self): - plain_command = " ".join(self.command) - return "Local script execution failed: %s" % plain_command - - class Local(object): """Execute commands locally. @@ -119,35 +107,16 @@ class Local(object): command = ["/bin/sh", "-e"] command.append(script) - self.log.debug("Local run script: %s", command) - - if env is None: - env = os.environ.copy() - # Export __target_host for use in __remote_{copy,exec} scripts - env['__target_host'] = self.target_host - - self.log.debug("Local run script env: %s", env) - - try: - if return_output: - return subprocess.check_output(command, env=env).decode() - else: - subprocess.check_call(command, env=env) - except subprocess.CalledProcessError as error: - script_content = self.run(["cat", script], return_output=True) - self.log.error("Code that raised the error:\n%s", script_content) - raise LocalScriptError(script, command, script_content) - except EnvironmentError as error: - raise cdist.Error(" ".join(command) + ": " + error.args[1]) + return self.run(command, env, return_output) def link_emulator(self, exec_path): """Link emulator to types""" src = os.path.abspath(exec_path) - for cdist_type in core.Type.list_types(self.type_path): + for cdist_type in core.CdistType.list_types(self.type_path): dst = os.path.join(self.bin_path, cdist_type.name) self.log.debug("Linking emulator: %s to %s", src, dst) try: os.symlink(src, dst) except OSError as e: - raise cdist.Error("Linking emulator from " + src + " to " + dst + " failed: " + e.__str__()) + raise cdist.Error("Linking emulator from %s to %s failed: %s" % (src, dst, e.__str__())) diff --git a/lib/cdist/exec/remote.py b/lib/cdist/exec/remote.py index 87db7273..124c1b4f 100644 --- a/lib/cdist/exec/remote.py +++ b/lib/cdist/exec/remote.py @@ -20,8 +20,6 @@ # # -# FIXME: common base class with Local? - import io import os import sys @@ -30,17 +28,6 @@ import logging import cdist - -class RemoteScriptError(cdist.Error): - def __init__(self, script, command, script_content): - self.script = script - self.command = command - self.script_content = script_content - - def __str__(self): - plain_command = " ".join(self.command) - return "Remote script execution failed: %s" % plain_command - class DecodeError(cdist.Error): def __init__(self, command): self.command = command @@ -93,6 +80,17 @@ class Remote(object): command.extend(["-r", source, self.target_host + ":" + destination]) self._run_command(command) + def run_script(self, script, env=None, return_output=False): + """Run the given script with the given environment on the remote side. + Return the output as a string. + + """ + + command = ["/bin/sh", "-e"] + command.append(script) + + return self.run(command, env, return_output) + def run(self, command, env=None, return_output=False): """Run the given command with the given environment on the remote side. Return the output as a string. @@ -101,7 +99,15 @@ class Remote(object): # prefix given command with remote_exec cmd = self._exec.split() cmd.append(self.target_host) + + # can't pass environment to remote side, so prepend command with + # variable declarations + if env: + remote_env = ["%s=%s" % item for item in env.items()] + cmd.extend(remote_env) + cmd.extend(command) + return self._run_command(cmd, env=env, return_output=return_output) def _run_command(self, command, env=None, return_output=False): @@ -115,14 +121,6 @@ class Remote(object): os_environ = os.environ.copy() os_environ['__target_host'] = self.target_host - # can't pass environment to remote side, so prepend command with - # variable declarations - if env: - cmd = ["%s=%s" % item for item in env.items()] - cmd.extend(command) - else: - cmd = command - self.log.debug("Remote run: %s", command) try: if return_output: @@ -135,39 +133,3 @@ class Remote(object): raise cdist.Error(" ".join(*args) + ": " + error.args[1]) except UnicodeDecodeError: raise DecodeError(command) - - def run_script(self, script, env=None, return_output=False): - """Run the given script with the given environment on the remote side. - Return the output as a string. - - """ - command = self._exec.split() - command.append(self.target_host) - - # export target_host for use in __remote_{exec,copy} scripts - os_environ = os.environ.copy() - os_environ['__target_host'] = self.target_host - - # can't pass environment to remote side, so prepend command with - # variable declarations - if env: - command.extend(["%s=%s" % item for item in env.items()]) - - command.extend(["/bin/sh", "-e"]) - command.append(script) - - self.log.debug("Remote run script: %s", command) - if env: - self.log.debug("Remote run script env: %s", env) - - try: - if return_output: - return subprocess.check_output(command, env=os_environ).decode() - else: - subprocess.check_call(command, env=os_environ) - except subprocess.CalledProcessError as error: - script_content = self.run(["cat", script], return_output=True) - self.log.error("Code that raised the error:\n%s", script_content) - raise RemoteScriptError(script, command, script_content) - except EnvironmentError as error: - raise cdist.Error(" ".join(command) + ": " + error.args[1]) diff --git a/lib/cdist/resolver.py b/lib/cdist/resolver.py index 24a5e496..368c9eb8 100644 --- a/lib/cdist/resolver.py +++ b/lib/cdist/resolver.py @@ -125,7 +125,7 @@ class DependencyResolver(object): resolved.append(cdist_object) unresolved.remove(cdist_object) except RequirementNotFoundError as e: - raise cdist.Error(cdist_object.name + " requires non-existing " + e.requirement) + raise cdist.CdistObjectError(cdist_object, "requires non-existing " + e.requirement) def __iter__(self): """Iterate over all unique objects while resolving dependencies. diff --git a/lib/cdist/test/code/__init__.py b/lib/cdist/test/code/__init__.py index 2f061086..dc701cce 100644 --- a/lib/cdist/test/code/__init__.py +++ b/lib/cdist/test/code/__init__.py @@ -54,8 +54,8 @@ class CodeTestCase(test.CdistTestCase): self.code = code.Code(self.target_host, self.local, self.remote) - self.cdist_type = core.Type(self.local.type_path, '__dump_environment') - self.cdist_object = core.Object(self.cdist_type, self.local.object_path, 'whatever') + self.cdist_type = core.CdistType(self.local.type_path, '__dump_environment') + self.cdist_object = core.CdistObject(self.cdist_type, self.local.object_path, 'whatever') self.cdist_object.create() self.log = logging.getLogger("cdist") diff --git a/lib/cdist/test/emulator/__init__.py b/lib/cdist/test/emulator/__init__.py index e67bed4a..370d3d82 100644 --- a/lib/cdist/test/emulator/__init__.py +++ b/lib/cdist/test/emulator/__init__.py @@ -79,7 +79,7 @@ class EmulatorTestCase(test.CdistTestCase): os.environ.update(self.env) os.environ['require'] = '__file' emu = emulator.Emulator(argv) - self.assertRaises(emulator.IllegalRequirementError, emu.run) + self.assertRaises(core.IllegalObjectIdError, emu.run) def test_singleton_object_requirement(self): argv = ['__file', '/tmp/foobar'] @@ -119,14 +119,14 @@ class AutoRequireEmulatorTestCase(test.CdistTestCase): def test_autorequire(self): initial_manifest = os.path.join(self.local.manifest_path, "init") self.manifest.run_initial_manifest(initial_manifest) - cdist_type = core.Type(self.local.type_path, '__saturn') - cdist_object = core.Object(cdist_type, self.local.object_path, 'singleton') + cdist_type = core.CdistType(self.local.type_path, '__saturn') + cdist_object = core.CdistObject(cdist_type, self.local.object_path, 'singleton') self.manifest.run_type_manifest(cdist_object) expected = ['__planet/Saturn', '__moon/Prometheus'] self.assertEqual(sorted(cdist_object.requirements), sorted(expected)) -class ArgumentsWithDashesTestCase(test.CdistTestCase): +class ArgumentsTestCase(test.CdistTestCase): def setUp(self): self.temp_dir = self.mkdtemp() @@ -156,6 +156,62 @@ class ArgumentsWithDashesTestCase(test.CdistTestCase): emu = emulator.Emulator(argv) emu.run() - cdist_type = core.Type(self.local.type_path, '__arguments_with_dashes') - cdist_object = core.Object(cdist_type, self.local.object_path, 'some-id') + cdist_type = core.CdistType(self.local.type_path, '__arguments_with_dashes') + cdist_object = core.CdistObject(cdist_type, self.local.object_path, 'some-id') self.assertTrue('with-dash' in cdist_object.parameters) + + def test_boolean(self): + type_name = '__arguments_boolean' + object_id = 'some-id' + argv = [type_name, object_id, '--boolean1'] + os.environ.update(self.env) + emu = emulator.Emulator(argv) + emu.run() + + cdist_type = core.CdistType(self.local.type_path, type_name) + cdist_object = core.CdistObject(cdist_type, self.local.object_path, object_id) + self.assertTrue('boolean1' in cdist_object.parameters) + self.assertFalse('boolean2' in cdist_object.parameters) + # empty file -> True + self.assertTrue(cdist_object.parameters['boolean1'] == '') + + def test_required(self): + type_name = '__arguments_required' + object_id = 'some-id' + value = 'some value' + argv = [type_name, object_id, '--required1', value, '--required2', value] + os.environ.update(self.env) + emu = emulator.Emulator(argv) + emu.run() + + cdist_type = core.CdistType(self.local.type_path, type_name) + cdist_object = core.CdistObject(cdist_type, self.local.object_path, object_id) + self.assertTrue('required1' in cdist_object.parameters) + self.assertTrue('required2' in cdist_object.parameters) + self.assertEqual(cdist_object.parameters['required1'], value) + self.assertEqual(cdist_object.parameters['required2'], value) + +# def test_required_missing(self): +# type_name = '__arguments_required' +# object_id = 'some-id' +# value = 'some value' +# argv = [type_name, object_id, '--required1', value] +# os.environ.update(self.env) +# emu = emulator.Emulator(argv) +# +# self.assertRaises(SystemExit, emu.run) + + def test_optional(self): + type_name = '__arguments_optional' + object_id = 'some-id' + value = 'some value' + argv = [type_name, object_id, '--optional1', value] + os.environ.update(self.env) + emu = emulator.Emulator(argv) + emu.run() + + cdist_type = core.CdistType(self.local.type_path, type_name) + cdist_object = core.CdistObject(cdist_type, self.local.object_path, object_id) + self.assertTrue('optional1' in cdist_object.parameters) + self.assertFalse('optional2' in cdist_object.parameters) + self.assertEqual(cdist_object.parameters['optional1'], value) diff --git a/lib/cdist/test/emulator/fixtures/conf/type/__arguments_boolean/parameter/boolean b/lib/cdist/test/emulator/fixtures/conf/type/__arguments_boolean/parameter/boolean new file mode 100644 index 00000000..3215c409 --- /dev/null +++ b/lib/cdist/test/emulator/fixtures/conf/type/__arguments_boolean/parameter/boolean @@ -0,0 +1,2 @@ +boolean1 +boolean2 diff --git a/lib/cdist/test/emulator/fixtures/conf/type/__arguments_optional/parameter/optional b/lib/cdist/test/emulator/fixtures/conf/type/__arguments_optional/parameter/optional new file mode 100644 index 00000000..31647628 --- /dev/null +++ b/lib/cdist/test/emulator/fixtures/conf/type/__arguments_optional/parameter/optional @@ -0,0 +1 @@ +optional1 diff --git a/lib/cdist/test/emulator/fixtures/conf/type/__arguments_required/parameter/required b/lib/cdist/test/emulator/fixtures/conf/type/__arguments_required/parameter/required new file mode 100644 index 00000000..e0fba2c9 --- /dev/null +++ b/lib/cdist/test/emulator/fixtures/conf/type/__arguments_required/parameter/required @@ -0,0 +1,2 @@ +required1 +required2 diff --git a/lib/cdist/test/explorer/__init__.py b/lib/cdist/test/explorer/__init__.py index cafe34fc..257ad8a9 100644 --- a/lib/cdist/test/explorer/__init__.py +++ b/lib/cdist/test/explorer/__init__.py @@ -83,19 +83,19 @@ class ExplorerClassTestCase(test.CdistTestCase): shutil.rmtree(out_path) def test_list_type_explorer_names(self): - cdist_type = core.Type(self.local.type_path, '__test_type') + cdist_type = core.CdistType(self.local.type_path, '__test_type') expected = cdist_type.explorers self.assertEqual(self.explorer.list_type_explorer_names(cdist_type), expected) def test_transfer_type_explorers(self): - cdist_type = core.Type(self.local.type_path, '__test_type') + cdist_type = core.CdistType(self.local.type_path, '__test_type') self.explorer.transfer_type_explorers(cdist_type) source = os.path.join(self.local.type_path, cdist_type.explorer_path) destination = os.path.join(self.remote.type_path, cdist_type.explorer_path) self.assertEqual(os.listdir(source), os.listdir(destination)) def test_transfer_type_explorers_only_once(self): - cdist_type = core.Type(self.local.type_path, '__test_type') + cdist_type = core.CdistType(self.local.type_path, '__test_type') # first transfer self.explorer.transfer_type_explorers(cdist_type) source = os.path.join(self.local.type_path, cdist_type.explorer_path) @@ -109,8 +109,8 @@ class ExplorerClassTestCase(test.CdistTestCase): self.assertFalse(os.listdir(destination)) def test_transfer_object_parameters(self): - cdist_type = core.Type(self.local.type_path, '__test_type') - cdist_object = core.Object(cdist_type, self.local.object_path, 'whatever') + cdist_type = core.CdistType(self.local.type_path, '__test_type') + cdist_object = core.CdistObject(cdist_type, self.local.object_path, 'whatever') cdist_object.create() cdist_object.parameters = {'first': 'first value', 'second': 'second value'} self.explorer.transfer_object_parameters(cdist_object) @@ -119,15 +119,15 @@ class ExplorerClassTestCase(test.CdistTestCase): self.assertEqual(sorted(os.listdir(source)), sorted(os.listdir(destination))) def test_run_type_explorer(self): - cdist_type = core.Type(self.local.type_path, '__test_type') - cdist_object = core.Object(cdist_type, self.local.object_path, 'whatever') + cdist_type = core.CdistType(self.local.type_path, '__test_type') + cdist_object = core.CdistObject(cdist_type, self.local.object_path, 'whatever') self.explorer.transfer_type_explorers(cdist_type) output = self.explorer.run_type_explorer('world', cdist_object) self.assertEqual(output, 'hello\n') def test_run_type_explorers(self): - cdist_type = core.Type(self.local.type_path, '__test_type') - cdist_object = core.Object(cdist_type, self.local.object_path, 'whatever') + cdist_type = core.CdistType(self.local.type_path, '__test_type') + cdist_object = core.CdistObject(cdist_type, self.local.object_path, 'whatever') cdist_object.create() self.explorer.run_type_explorers(cdist_object) self.assertEqual(cdist_object.explorers, {'world': 'hello'}) diff --git a/lib/cdist/test/manifest/__init__.py b/lib/cdist/test/manifest/__init__.py index 2383adf7..a188c788 100644 --- a/lib/cdist/test/manifest/__init__.py +++ b/lib/cdist/test/manifest/__init__.py @@ -79,8 +79,8 @@ class ManifestTestCase(test.CdistTestCase): self.assertEqual(output_dict['__manifest'], self.local.manifest_path) def test_type_manifest_environment(self): - cdist_type = core.Type(self.local.type_path, '__dump_environment') - cdist_object = core.Object(cdist_type, self.local.object_path, 'whatever') + cdist_type = core.CdistType(self.local.type_path, '__dump_environment') + cdist_object = core.CdistObject(cdist_type, self.local.object_path, 'whatever') handle, output_file = self.mkstemp(dir=self.temp_dir) os.close(handle) os.environ['__cdist_test_out'] = output_file diff --git a/lib/cdist/test/object/__init__.py b/lib/cdist/test/object/__init__.py index f199ffb5..3a91f709 100644 --- a/lib/cdist/test/object/__init__.py +++ b/lib/cdist/test/object/__init__.py @@ -25,6 +25,8 @@ import shutil from cdist import test from cdist import core +import cdist + import os.path as op my_dir = op.abspath(op.dirname(__file__)) fixtures = op.join(my_dir, 'fixtures') @@ -34,48 +36,48 @@ type_base_path = op.join(fixtures, 'type') class ObjectClassTestCase(test.CdistTestCase): def test_list_object_names(self): - object_names = list(core.Object.list_object_names(object_base_path)) + object_names = list(core.CdistObject.list_object_names(object_base_path)) self.assertEqual(object_names, ['__first/man', '__second/on-the', '__third/moon']) def test_list_type_names(self): - type_names = list(core.Object.list_type_names(object_base_path)) + type_names = list(cdist.core.CdistObject.list_type_names(object_base_path)) self.assertEqual(type_names, ['__first', '__second', '__third']) def test_list_objects(self): - objects = list(core.Object.list_objects(object_base_path, type_base_path)) + objects = list(core.CdistObject.list_objects(object_base_path, type_base_path)) objects_expected = [ - core.Object(core.Type(type_base_path, '__first'), object_base_path, 'man'), - core.Object(core.Type(type_base_path, '__second'), object_base_path, 'on-the'), - core.Object(core.Type(type_base_path, '__third'), object_base_path, 'moon'), + core.CdistObject(core.CdistType(type_base_path, '__first'), object_base_path, 'man'), + core.CdistObject(core.CdistType(type_base_path, '__second'), object_base_path, 'on-the'), + core.CdistObject(core.CdistType(type_base_path, '__third'), object_base_path, 'moon'), ] self.assertEqual(objects, objects_expected) class ObjectIdTestCase(test.CdistTestCase): - def test_object_id_starts_with_slash(self): - cdist_type = core.Type(type_base_path, '__third') - illegal_object_id = '/object_id/may/not/start/with/slash' + def test_object_id_contains_double_slash(self): + cdist_type = core.CdistType(type_base_path, '__third') + illegal_object_id = '/object_id//may/not/contain/double/slash' with self.assertRaises(core.IllegalObjectIdError): - core.Object(cdist_type, object_base_path, illegal_object_id) + core.CdistObject(cdist_type, object_base_path, illegal_object_id) def test_object_id_contains_object_marker(self): - cdist_type = core.Type(type_base_path, '__third') + cdist_type = core.CdistType(type_base_path, '__third') illegal_object_id = 'object_id/may/not/contain/%s/anywhere' % core.OBJECT_MARKER with self.assertRaises(core.IllegalObjectIdError): - core.Object(cdist_type, object_base_path, illegal_object_id) + core.CdistObject(cdist_type, object_base_path, illegal_object_id) def test_object_id_contains_object_marker_string(self): - cdist_type = core.Type(type_base_path, '__third') + cdist_type = core.CdistType(type_base_path, '__third') illegal_object_id = 'object_id/may/contain_%s_in_filename' % core.OBJECT_MARKER - core.Object(cdist_type, object_base_path, illegal_object_id) + core.CdistObject(cdist_type, object_base_path, illegal_object_id) # if we get here, the test passed class ObjectTestCase(test.CdistTestCase): def setUp(self): - self.cdist_type = core.Type(type_base_path, '__third') - self.cdist_object = core.Object(self.cdist_type, object_base_path, 'moon') + self.cdist_type = core.CdistType(type_base_path, '__third') + self.cdist_object = core.CdistObject(self.cdist_type, object_base_path, 'moon') def tearDown(self): self.cdist_object.changed = False @@ -159,16 +161,16 @@ class ObjectTestCase(test.CdistTestCase): self.assertEqual(self.cdist_object.state, '') def test_state_prepared(self): - self.cdist_object.state = core.Object.STATE_PREPARED - self.assertEqual(self.cdist_object.state, core.Object.STATE_PREPARED) + self.cdist_object.state = core.CdistObject.STATE_PREPARED + self.assertEqual(self.cdist_object.state, core.CdistObject.STATE_PREPARED) def test_state_running(self): - self.cdist_object.state = core.Object.STATE_RUNNING - self.assertEqual(self.cdist_object.state, core.Object.STATE_RUNNING) + self.cdist_object.state = core.CdistObject.STATE_RUNNING + self.assertEqual(self.cdist_object.state, core.CdistObject.STATE_RUNNING) def test_state_done(self): - self.cdist_object.state = core.Object.STATE_DONE - self.assertEqual(self.cdist_object.state, core.Object.STATE_DONE) + self.cdist_object.state = core.CdistObject.STATE_DONE + self.assertEqual(self.cdist_object.state, core.CdistObject.STATE_DONE) def test_source(self): self.assertEqual(list(self.cdist_object.source), []) @@ -195,6 +197,6 @@ class ObjectTestCase(test.CdistTestCase): self.cdist_object.code_remote = 'Hello World' other_name = '__first/man' other_object = self.cdist_object.object_from_name(other_name) - self.assertTrue(isinstance(other_object, core.Object)) - self.assertEqual(other_object.type.name, '__first') + self.assertTrue(isinstance(other_object, core.CdistObject)) + self.assertEqual(other_object.cdist_type.name, '__first') self.assertEqual(other_object.object_id, 'man') diff --git a/lib/cdist/test/resolver/__init__.py b/lib/cdist/test/resolver/__init__.py index cca058a4..ae8f6915 100644 --- a/lib/cdist/test/resolver/__init__.py +++ b/lib/cdist/test/resolver/__init__.py @@ -37,7 +37,7 @@ type_base_path = op.join(fixtures, 'type') class ResolverTestCase(test.CdistTestCase): def setUp(self): - self.objects = list(core.Object.list_objects(object_base_path, type_base_path)) + self.objects = list(core.CdistObject.list_objects(object_base_path, type_base_path)) self.object_index = dict((o.name, o) for o in self.objects) self.dependency_resolver = resolver.DependencyResolver(self.objects) diff --git a/lib/cdist/test/type/__init__.py b/lib/cdist/test/type/__init__.py index 7bb8654c..5e774aa9 100644 --- a/lib/cdist/test/type/__init__.py +++ b/lib/cdist/test/type/__init__.py @@ -33,115 +33,126 @@ class TypeTestCase(test.CdistTestCase): def test_list_type_names(self): base_path = op.join(fixtures, 'list_types') - type_names = core.Type.list_type_names(base_path) + type_names = core.CdistType.list_type_names(base_path) self.assertEqual(type_names, ['__first', '__second', '__third']) def test_list_types(self): base_path = op.join(fixtures, 'list_types') - types = list(core.Type.list_types(base_path)) + types = list(core.CdistType.list_types(base_path)) types_expected = [ - core.Type(base_path, '__first'), - core.Type(base_path, '__second'), - core.Type(base_path, '__third'), + core.CdistType(base_path, '__first'), + core.CdistType(base_path, '__second'), + core.CdistType(base_path, '__third'), ] self.assertEqual(types, types_expected) def test_only_one_instance(self): base_path = fixtures - cdist_type1 = core.Type(base_path, '__name_path') - cdist_type2 = core.Type(base_path, '__name_path') + cdist_type1 = core.CdistType(base_path, '__name_path') + cdist_type2 = core.CdistType(base_path, '__name_path') self.assertEqual(id(cdist_type1), id(cdist_type2)) def test_nonexistent_type(self): base_path = fixtures - self.assertRaises(core.NoSuchTypeError, core.Type, base_path, '__i-dont-exist') + self.assertRaises(core.NoSuchTypeError, core.CdistType, base_path, '__i-dont-exist') def test_name(self): base_path = fixtures - cdist_type = core.Type(base_path, '__name_path') + cdist_type = core.CdistType(base_path, '__name_path') self.assertEqual(cdist_type.name, '__name_path') def test_path(self): base_path = fixtures - cdist_type = core.Type(base_path, '__name_path') + cdist_type = core.CdistType(base_path, '__name_path') self.assertEqual(cdist_type.path, '__name_path') def test_base_path(self): base_path = fixtures - cdist_type = core.Type(base_path, '__name_path') + cdist_type = core.CdistType(base_path, '__name_path') self.assertEqual(cdist_type.base_path, base_path) def test_absolute_path(self): base_path = fixtures - cdist_type = core.Type(base_path, '__name_path') + cdist_type = core.CdistType(base_path, '__name_path') self.assertEqual(cdist_type.absolute_path, os.path.join(base_path, '__name_path')) def test_manifest_path(self): base_path = fixtures - cdist_type = core.Type(base_path, '__name_path') + cdist_type = core.CdistType(base_path, '__name_path') self.assertEqual(cdist_type.manifest_path, os.path.join('__name_path', 'manifest')) def test_explorer_path(self): base_path = fixtures - cdist_type = core.Type(base_path, '__name_path') + cdist_type = core.CdistType(base_path, '__name_path') self.assertEqual(cdist_type.explorer_path, os.path.join('__name_path', 'explorer')) def test_gencode_local_path(self): base_path = fixtures - cdist_type = core.Type(base_path, '__name_path') + cdist_type = core.CdistType(base_path, '__name_path') self.assertEqual(cdist_type.gencode_local_path, os.path.join('__name_path', 'gencode-local')) def test_gencode_remote_path(self): base_path = fixtures - cdist_type = core.Type(base_path, '__name_path') + cdist_type = core.CdistType(base_path, '__name_path') self.assertEqual(cdist_type.gencode_remote_path, os.path.join('__name_path', 'gencode-remote')) def test_singleton_is_singleton(self): base_path = fixtures - cdist_type = core.Type(base_path, '__singleton') + cdist_type = core.CdistType(base_path, '__singleton') self.assertTrue(cdist_type.is_singleton) def test_not_singleton_is_singleton(self): base_path = fixtures - cdist_type = core.Type(base_path, '__not_singleton') + cdist_type = core.CdistType(base_path, '__not_singleton') self.assertFalse(cdist_type.is_singleton) def test_install_is_install(self): base_path = fixtures - cdist_type = core.Type(base_path, '__install') + cdist_type = core.CdistType(base_path, '__install') self.assertTrue(cdist_type.is_install) def test_not_install_is_install(self): base_path = fixtures - cdist_type = core.Type(base_path, '__not_install') + cdist_type = core.CdistType(base_path, '__not_install') self.assertFalse(cdist_type.is_install) def test_with_explorers(self): base_path = fixtures - cdist_type = core.Type(base_path, '__with_explorers') + cdist_type = core.CdistType(base_path, '__with_explorers') self.assertEqual(cdist_type.explorers, ['whatever']) def test_without_explorers(self): base_path = fixtures - cdist_type = core.Type(base_path, '__without_explorers') + cdist_type = core.CdistType(base_path, '__without_explorers') self.assertEqual(cdist_type.explorers, []) def test_with_required_parameters(self): base_path = fixtures - cdist_type = core.Type(base_path, '__with_required_parameters') + cdist_type = core.CdistType(base_path, '__with_required_parameters') self.assertEqual(cdist_type.required_parameters, ['required1', 'required2']) def test_without_required_parameters(self): base_path = fixtures - cdist_type = core.Type(base_path, '__without_required_parameters') + cdist_type = core.CdistType(base_path, '__without_required_parameters') self.assertEqual(cdist_type.required_parameters, []) def test_with_optional_parameters(self): base_path = fixtures - cdist_type = core.Type(base_path, '__with_optional_parameters') + cdist_type = core.CdistType(base_path, '__with_optional_parameters') self.assertEqual(cdist_type.optional_parameters, ['optional1', 'optional2']) def test_without_optional_parameters(self): base_path = fixtures - cdist_type = core.Type(base_path, '__without_optional_parameters') + cdist_type = core.CdistType(base_path, '__without_optional_parameters') self.assertEqual(cdist_type.optional_parameters, []) + + def test_with_boolean_parameters(self): + base_path = fixtures + cdist_type = core.CdistType(base_path, '__with_boolean_parameters') + self.assertEqual(cdist_type.boolean_parameters, ['boolean1', 'boolean2']) + + def test_without_boolean_parameters(self): + base_path = fixtures + cdist_type = core.CdistType(base_path, '__without_boolean_parameters') + self.assertEqual(cdist_type.boolean_parameters, []) + diff --git a/lib/cdist/test/type/fixtures/__with_boolean_parameters/parameter/boolean b/lib/cdist/test/type/fixtures/__with_boolean_parameters/parameter/boolean new file mode 100644 index 00000000..3215c409 --- /dev/null +++ b/lib/cdist/test/type/fixtures/__with_boolean_parameters/parameter/boolean @@ -0,0 +1,2 @@ +boolean1 +boolean2 diff --git a/lib/cdist/test/type/fixtures/__without_boolean_parameters/.keep b/lib/cdist/test/type/fixtures/__without_boolean_parameters/.keep new file mode 100644 index 00000000..e69de29b