#!/bin/bash shellfu import jat shellfu import pretty shellfu import jat_dump # # fake upgrade path generator # # This beakerlib module helps generate mock upgrade paths for purposes # of testing preupgrade-assistant. # # # =head1 HELLO WORLD # # { # echo '[preupgrade]' # echo 'description = hello world module' # echo '[MODULE]' # echo 'GROUP = foo' # echo 'NAME = bar' # echo 'LANG = py' # echo 'CODE = log_info('hello world')' # echo 'CODE = exit_informational()' # } > one.mdef # preupg_fupath RHEL6_7 one.mdef # preupg__upath=RHEL6_7/all-xccdf.xml # preupg__run1 # # would generate: # # RHEL6_7 # └── foo # └── bar # ├── module.ini # └── check # # # =head1 OVERVIEW # # The workflow is following: # # 1. Per each upgrade path module, generate a pseudo-INI # file, defining properties and content of the module. # # 2. Call preupg_fupath() to generate the # upgrade path for you, using standard tools under the # hood. # # 3. Call preupg (or better, preupg__run1() # from preupgrade-assistant/main) to use the upgrade path. # # # =head1 FORMAT # # A fuller example: # # [preupgrade] # content_description = test module bob # content_title = test module bob # # [MODULE] # GROUP = system # NAME = foo # LANG = sh # CODE = log_medium_risk foo # CODE = exit_pass # # [FILES] # solution.txt = Some text # solution.txt = Some more text # myfile.txt = first line of an arbitrary file # myfile.txt = second line of of an arbitrary file # # * Fields from "preupgrade" section are directly copied to # used the actual module.ini. # # * MODULE section should contain GROUP, NAME and a multi-line # CODE of the module. GROUP and NAME define placement of # the module in the tree, CODE will be appended to the # auto-generated script. # # LANG key is 'sh' for Bash (default) and 'py' for Python. # # * FILES section can be used to include any number of plain # text files (such as a hook scripts or config files) inside # the module. Here key name is name of the file and key # content is content of the file. # # Virtually any field may be omitted; the generator will try to # fill in as much as possible (even generate names from random # chars). # # FILES section may be used to fully overwrite any other files # (even module.ini file!). This is useful to test "extreme" cases such # as empty or malformed module.ini file, while retaining feature of # auto-generation mentioned above. # # # =head2 Indentation # # Multi-line values (CODE and values from FILES section) are # read in a way that preserves indentation by removing as many # spaces from first column as possible without removing other # characters. For example, following sections: # # CODE = def blah(): # CODE = pass # CODE = # CODE = for p in get_dist_native_list(): # CODE = blah() # # CODE = def blah(): # CODE = pass # CODE = # CODE = for p in get_dist_native_list(): # CODE = blah() # # CODE =def blah(): # CODE = pass # CODE = # CODE =for p in get_dist_native_list(): # CODE = blah() # # would result in exactly the same code: # # def blah(): # pass # # for p in get_dist_native_list(): # blah() # # This is in order to enable generation of valid Python scripts # while enabling maximum readability of your test code. # preupg_fupath() { # # Create custom test upgrade path from pseudo-INI files # # Usage: # preupg_fupath UPATH [./FILE]... # # UPATH must be in form CCCN_N, where C is letter from alphabet, # N is a decimal integer number and '_' is literally underscore. # # Note: folders UPATH, UPATH-raw and UPATH-results in current # folder will be silently deleted! # # # If FILE argument starts with "at sign" (`@`), it will be interpreted # as name of a built-in .mdef file included within this library; look # into "builtins" subfolder; the alias can be constructed as path # to .mdef file relative to that folder, with .mdef suffix stripped. # # For example, # # preupg_fupath RHEL6_7 @pass @failed some/other.mdef # # will construct upgrade path named RHEL6_7 with three modules based on # two built-in files and one custom file. # # To launch generated upgrade path, specify it to preupg using # `-c` parameter pointing to file all-xccdf.xml under folder # , for example: # # preupg --force -c RHEL6_7/all-xccdf.xml # # Recommended practice, though, is to set preupg__upath # to the path as above and use preupg__run1: # # shellfu import preupg # ... # preupg_fupath RHEL6_7 foo.mdef bar.mdef # preupg__upath=RHEL6_7/all-xccdf.xml # preupg__run1 # # which will enable lot of additional checks. # local UPName=$1; shift # upgrade path name local MdefFile # each Mdef file local Adding=false # true: we're adding into existing upath (iteration 2+) local b_file="" # built-in .mdef file local MdefFileNick # Shorter .mdef File reference (usable if built-in) local Tmp # our temp dir Tmp=$(mktemp -dt preupg_fupath.XXXXXXXX) jat__cmd rm -rf "$UPName" "$UPName-raw" "$UPName-results" for MdefFile in "$@"; do MdefFileNick=$MdefFile test "${MdefFile:0:1}" == @ && { b_file=$(_preupg_fupath__builtin "$MdefFile") || return 2 jat__log_info "using built-in .mdef file: $MdefFile=${b_file#$_PREUPG_FUPATH__HOMEDIR/}" MdefFile=$b_file } _preupg_fupath__mdef2module Adding=true done _preupg_fupath__cook_tree "$UPName" rm -r "$Tmp" } # # don't jump around too much beyond this line # # INTERNAL # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # # it's not a safe path # # # Library data dir (set during build) # _PREUPG_FUPATH__HOMEDIR=__SHELLFU_MODHOME__ _preupg_fupath__builtin() { # # Show path of built-in .mdef file (if exists, else fail) # local b_file=$_PREUPG_FUPATH__HOMEDIR/builtins/${1:1}.mdef test -f "$b_file" || { jat__log_error "no such built-in: $b_file" return 2 } echo "$b_file" } _preupg_fupath__cook_tree() { # # Create proper compose from raw tree # local tree=$1 jat__cmd -h "build upgrade path" \ preupg-xccdf-compose "$tree" jat__eval -h "put upgrade path in place" \ "mv '$tree' '$tree-raw' && mv '$tree-results' '$tree'" find "$tree-raw" "$tree" | jat_dump__pipe -D FOREST } _preupg_fupath__draw_token() { # # Get a unique token # head -c 100 /dev/urandom | md5sum | head -c 5 } _preupg_fupath__mdef2module() { # # Process single .mdef # local m_afile # additional file local m_code_body # module code body (after auto-header) local m_description # module description local m_srcmaj=6 # source OS version #FIXME: don't hard-code this local m_dstmaj=7 # destination OS version #FIXME: don't hard-code this local m_group # module group name local m_name # module name local m_lang # module language ('sh' for Bash or 'py' for Python) local m_title # module title local newmod_path # new module's subpath (derived from other parts) # Read starting values from $MdefFile or make them up # jat_dump__file -D "$MdefFile" m_group=$(_preupg_fupath__ini 1value "MODULE:GROUP") m_name=$(_preupg_fupath__ini 1value "MODULE:NAME") m_lang=$(_preupg_fupath__ini 1value "MODULE:LANG") m_code_body=$(_preupg_fupath__ini values "MODULE:CODE") m_title=$(_preupg_fupath__ini 1value "preupgrade:content_title") m_description=$(_preupg_fupath__ini 1value "preupgrade:content_description") test -n "$m_group" || m_group="group_$(_preupg_fupath__draw_token)" test -n "$m_name" || m_name="module_$(_preupg_fupath__draw_token)" test -n "$m_lang" || m_lang="sh" test -n "$m_code_body" || m_code_body="exit_pass" test -n "$m_title" || m_title="Mock module named $m_name" test -n "$m_description" || m_description="This is just a testing module named $m_name" debug -v m_group m_name m_lang m_code_body m_title m_description # create new module (most fields will be overwritten) # { echo "$UPName" # upgrade path name (also relative path) $Adding && echo y # we need to confirm if we want to add into existing $Adding || echo "$m_srcmaj" # source OS version $Adding || echo "$m_dstmaj" # destination OS version echo "$m_group" # module group echo "$m_name" # module name echo "$m_lang" # module language echo "$m_title" # module title echo "$m_description" # module description } >"$Tmp/answers" jat_dump__file -D "$Tmp/answers" jat__eval -h "create module: $m_name in group $m_group from $MdefFileNick" \ "preupg-content-creator <$Tmp/answers >/dev/null" newmod_path="$UPName/$m_group/$m_name" # add arbitrary values from $MdefFile # local extra_keys extra_keys=$( for key in $(_preupg_fupath__ini lskeys "preupgrade"); do value=$(_preupg_fupath__ini 1value "preupgrade:$key") grep "^$key *=" "$newmod_path/module.ini" && continue test -n "$value" && echo "$key = $value" done ) debug -v extra_keys echo "$extra_keys" >> "$newmod_path/module.ini" # append CODE to module script echo "$m_code_body" >> "$newmod_path/check" # dump to keep context clear # jat_dump__file -D \ "$newmod_path/module.ini" \ "$newmod_path/check" # add any arbitrary files stored in FILES section # for m_afile in $(_preupg_fupath__ini lskeys FILES); do test -n "$m_code_body" \ && test "$m_afile" == "check" \ && jat__log_warning "overwriting CODE by file from FILES section: $m_afile" mkdir -p "$(dirname "$newmod_path/$m_afile")" _preupg_fupath__ini values "FILES:$m_afile" \ > "$newmod_path/$m_afile" done debug -c find "$UPName" } _preupg_fupath__ini() { # # do ini operation # local op=$1 local arg=$2 local fn local flt=_preupg_fupath__ini_cat case $op in lskeys) fn=_preupg_fupath__ini_lskeys ;; sec) fn=_preupg_fupath__ini_grepsec ;; values) fn=_preupg_fupath__ini_greppath flt=_preupg_fupath__ini_unind ;; 1value) fn=_preupg_fupath__ini_greppath flt=_preupg_fupath__ini_strip ;; *) jat__log_error "incorrect use of \`_preupg_fupath__ini()\`" esac <"$MdefFile" $fn "$arg" | $flt } _preupg_fupath__ini_strip() { # # Strip a simple value # head -1 | sed 's/^ *//; s/ *$//' } _preupg_fupath__ini_unind() { # # Unindent multi-line value # local remove=0 local tmp tmp=$(mktemp -dt _preupg_fupath__ini_unind.XXXXXXXX) cat >"$tmp/body" grep . "$tmp/body" | sed 's/[^ ].*$//' > "$tmp/air" remove=$(sort "$tmp/air" | head -1 | wc -c) ((remove--)) # newline case $remove in 0) cat "$tmp/body" ;; *) colrm 1 "$remove" < "$tmp/body" ;; esac rm -rf "$tmp" } _preupg_fupath__ini_cat() { # # A no-op for text stream # while IFS= read -r line; do printf -- "%s\n" "$line" done } _preupg_fupath__ini_grepkey() { # # Read key from a section # local wnt=$1 grep -v '\s*#' \ | sed -e 's/ *=/=/;' \ | grep -e "^$wnt=" \ | cut -d= -f2- } _preupg_fupath__ini_greppath() { # # Read key from the right section # # E.g. `files:share:my/lib.sh` should read # # [files:share] # my/lib.sh = proj/my/lib.sh # local wnt="$1" local wntkey="${wnt##*:}" local wntsec="${wnt%:$wntkey}" _preupg_fupath__ini_grepsec "$wntsec" \ | _preupg_fupath__ini_grepkey "$wntkey" } _preupg_fupath__ini_grepsec() { # # Read one INI section # local wnt="$1" local ok=false grep -v '\s*#' \ | while IFS= read -r line; do case "$line" in \[$wnt\]) ok=true; continue ;; \[*\]) ok=false; continue ;; esac $ok || continue printf -- "%s\n" "$line" done \ | sed -e 's/ *=/=/;' } _preupg_fupath__ini_lskeys() { # # List keys from a section # local sct="$1" _preupg_fupath__ini_grepsec "$sct" \ | cut -d= -f1 \ | sort \ | uniq } #shellfu module-version=__MKIT_PROJ_VERSION__