preupg_fupath.sh.skel 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. #!/bin/bash
  2. shellfu import jat
  3. shellfu import pretty
  4. shellfu import jat_dump
  5. #
  6. # fake upgrade path generator
  7. #
  8. # This beakerlib module helps generate mock upgrade paths for purposes
  9. # of testing preupgrade-assistant.
  10. #
  11. #
  12. # =head1 HELLO WORLD
  13. #
  14. # {
  15. # echo '[preupgrade]'
  16. # echo 'description = hello world module'
  17. # echo '[MODULE]'
  18. # echo 'GROUP = foo'
  19. # echo 'NAME = bar'
  20. # echo 'LANG = py'
  21. # echo 'CODE = log_info('hello world')'
  22. # echo 'CODE = exit_informational()'
  23. # } > one.mdef
  24. # preupg_fupath RHEL6_7 one.mdef
  25. # preupg__upath=RHEL6_7/all-xccdf.xml
  26. # preupg__run1
  27. #
  28. # would generate:
  29. #
  30. # RHEL6_7
  31. # └── foo
  32. # └── bar
  33. # ├── module.ini
  34. # └── check
  35. #
  36. #
  37. # =head1 OVERVIEW
  38. #
  39. # The workflow is following:
  40. #
  41. # 1. Per each upgrade path module, generate a pseudo-INI
  42. # file, defining properties and content of the module.
  43. #
  44. # 2. Call preupg_fupath() to generate the
  45. # upgrade path for you, using standard tools under the
  46. # hood.
  47. #
  48. # 3. Call preupg (or better, preupg__run1()
  49. # from preupgrade-assistant/main) to use the upgrade path.
  50. #
  51. #
  52. # =head1 FORMAT
  53. #
  54. # A fuller example:
  55. #
  56. # [preupgrade]
  57. # content_description = test module bob
  58. # content_title = test module bob
  59. #
  60. # [MODULE]
  61. # GROUP = system
  62. # NAME = foo
  63. # LANG = sh
  64. # CODE = log_medium_risk foo
  65. # CODE = exit_pass
  66. #
  67. # [FILES]
  68. # solution.txt = Some text
  69. # solution.txt = Some more text
  70. # myfile.txt = first line of an arbitrary file
  71. # myfile.txt = second line of of an arbitrary file
  72. #
  73. # * Fields from "preupgrade" section are directly copied to
  74. # used the actual module.ini.
  75. #
  76. # * MODULE section should contain GROUP, NAME and a multi-line
  77. # CODE of the module. GROUP and NAME define placement of
  78. # the module in the tree, CODE will be appended to the
  79. # auto-generated script.
  80. #
  81. # LANG key is 'sh' for Bash (default) and 'py' for Python.
  82. #
  83. # * FILES section can be used to include any number of plain
  84. # text files (such as a hook scripts or config files) inside
  85. # the module. Here key name is name of the file and key
  86. # content is content of the file.
  87. #
  88. # Virtually any field may be omitted; the generator will try to
  89. # fill in as much as possible (even generate names from random
  90. # chars).
  91. #
  92. # FILES section may be used to fully overwrite any other files
  93. # (even module.ini file!). This is useful to test "extreme" cases such
  94. # as empty or malformed module.ini file, while retaining feature of
  95. # auto-generation mentioned above.
  96. #
  97. #
  98. # =head2 Indentation
  99. #
  100. # Multi-line values (CODE and values from FILES section) are
  101. # read in a way that preserves indentation by removing as many
  102. # spaces from first column as possible without removing other
  103. # characters. For example, following sections:
  104. #
  105. # CODE = def blah():
  106. # CODE = pass
  107. # CODE =
  108. # CODE = for p in get_dist_native_list():
  109. # CODE = blah()
  110. #
  111. # CODE = def blah():
  112. # CODE = pass
  113. # CODE =
  114. # CODE = for p in get_dist_native_list():
  115. # CODE = blah()
  116. #
  117. # CODE =def blah():
  118. # CODE = pass
  119. # CODE =
  120. # CODE =for p in get_dist_native_list():
  121. # CODE = blah()
  122. #
  123. # would result in exactly the same code:
  124. #
  125. # def blah():
  126. # pass
  127. #
  128. # for p in get_dist_native_list():
  129. # blah()
  130. #
  131. # This is in order to enable generation of valid Python scripts
  132. # while enabling maximum readability of your test code.
  133. #
  134. preupg_fupath() {
  135. #
  136. # Create custom test upgrade path from pseudo-INI files
  137. #
  138. # Usage:
  139. # preupg_fupath UPATH [./FILE]...
  140. #
  141. # UPATH must be in form CCCN_N, where C is letter from alphabet,
  142. # N is a decimal integer number and '_' is literally underscore.
  143. #
  144. # Note: folders UPATH, UPATH-raw and UPATH-results in current
  145. # folder will be silently deleted!
  146. #
  147. #
  148. # If FILE argument starts with "at sign" (`@`), it will be interpreted
  149. # as name of a built-in .mdef file included within this library; look
  150. # into "builtins" subfolder; the alias can be constructed as path
  151. # to .mdef file relative to that folder, with .mdef suffix stripped.
  152. #
  153. # For example,
  154. #
  155. # preupg_fupath RHEL6_7 @pass @failed some/other.mdef
  156. #
  157. # will construct upgrade path named RHEL6_7 with three modules based on
  158. # two built-in files and one custom file.
  159. #
  160. # To launch generated upgrade path, specify it to preupg using
  161. # `-c` parameter pointing to file all-xccdf.xml under folder
  162. # <UPATH>, for example:
  163. #
  164. # preupg --force -c RHEL6_7/all-xccdf.xml
  165. #
  166. # Recommended practice, though, is to set preupg__upath
  167. # to the path as above and use preupg__run1:
  168. #
  169. # shellfu import preupg
  170. # ...
  171. # preupg_fupath RHEL6_7 foo.mdef bar.mdef
  172. # preupg__upath=RHEL6_7/all-xccdf.xml
  173. # preupg__run1
  174. #
  175. # which will enable lot of additional checks.
  176. #
  177. local UPName=$1; shift # upgrade path name
  178. local MdefFile # each Mdef file
  179. local Adding=false # true: we're adding into existing upath (iteration 2+)
  180. local b_file="" # built-in .mdef file
  181. local MdefFileNick # Shorter .mdef File reference (usable if built-in)
  182. local Tmp # our temp dir
  183. Tmp=$(mktemp -dt preupg_fupath.XXXXXXXX)
  184. jat__cmd rm -rf "$UPName" "$UPName-raw" "$UPName-results"
  185. for MdefFile in "$@";
  186. do
  187. MdefFileNick=$MdefFile
  188. test "${MdefFile:0:1}" == @ && {
  189. b_file=$(_preupg_fupath__builtin "$MdefFile") || return 2
  190. jat__log_info "using built-in .mdef file: $MdefFile=${b_file#$_PREUPG_FUPATH__HOMEDIR/}"
  191. MdefFile=$b_file
  192. }
  193. _preupg_fupath__mdef2module
  194. Adding=true
  195. done
  196. _preupg_fupath__cook_tree "$UPName"
  197. rm -r "$Tmp"
  198. }
  199. # # don't jump around too much beyond this line #
  200. # INTERNAL # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  201. # # it's not a safe path #
  202. #
  203. # Library data dir (set during build)
  204. #
  205. _PREUPG_FUPATH__HOMEDIR=__SHELLFU_MODHOME__
  206. _preupg_fupath__builtin() {
  207. #
  208. # Show path of built-in .mdef file (if exists, else fail)
  209. #
  210. local b_file=$_PREUPG_FUPATH__HOMEDIR/builtins/${1:1}.mdef
  211. test -f "$b_file" || {
  212. jat__log_error "no such built-in: $b_file"
  213. return 2
  214. }
  215. echo "$b_file"
  216. }
  217. _preupg_fupath__cook_tree() {
  218. #
  219. # Create proper compose from raw tree
  220. #
  221. local tree=$1
  222. jat__cmd -h "build upgrade path" \
  223. preupg-xccdf-compose "$tree"
  224. jat__eval -h "put upgrade path in place" \
  225. "mv '$tree' '$tree-raw' && mv '$tree-results' '$tree'"
  226. find "$tree-raw" "$tree" | jat_dump__pipe -D FOREST
  227. }
  228. _preupg_fupath__draw_token() {
  229. #
  230. # Get a unique token
  231. #
  232. head -c 100 /dev/urandom | md5sum | head -c 5
  233. }
  234. _preupg_fupath__mdef2module() {
  235. #
  236. # Process single .mdef
  237. #
  238. local m_afile # additional file
  239. local m_code_body # module code body (after auto-header)
  240. local m_description # module description
  241. local m_srcmaj=6 # source OS version #FIXME: don't hard-code this
  242. local m_dstmaj=7 # destination OS version #FIXME: don't hard-code this
  243. local m_group # module group name
  244. local m_name # module name
  245. local m_lang # module language ('sh' for Bash or 'py' for Python)
  246. local m_title # module title
  247. local newmod_path # new module's subpath (derived from other parts)
  248. # Read starting values from $MdefFile or make them up
  249. #
  250. jat_dump__file -D "$MdefFile"
  251. m_group=$(_preupg_fupath__ini 1value "MODULE:GROUP")
  252. m_name=$(_preupg_fupath__ini 1value "MODULE:NAME")
  253. m_lang=$(_preupg_fupath__ini 1value "MODULE:LANG")
  254. m_code_body=$(_preupg_fupath__ini values "MODULE:CODE")
  255. m_title=$(_preupg_fupath__ini 1value "preupgrade:content_title")
  256. m_description=$(_preupg_fupath__ini 1value "preupgrade:content_description")
  257. test -n "$m_group" || m_group="group_$(_preupg_fupath__draw_token)"
  258. test -n "$m_name" || m_name="module_$(_preupg_fupath__draw_token)"
  259. test -n "$m_lang" || m_lang="sh"
  260. test -n "$m_code_body" || m_code_body="exit_pass"
  261. test -n "$m_title" || m_title="Mock module named $m_name"
  262. test -n "$m_description" || m_description="This is just a testing module named $m_name"
  263. debug -v m_group m_name m_lang m_code_body m_title m_description
  264. # create new module (most fields will be overwritten)
  265. #
  266. {
  267. echo "$UPName" # upgrade path name (also relative path)
  268. $Adding && echo y # we need to confirm if we want to add into existing
  269. $Adding || echo "$m_srcmaj" # source OS version
  270. $Adding || echo "$m_dstmaj" # destination OS version
  271. echo "$m_group" # module group
  272. echo "$m_name" # module name
  273. echo "$m_lang" # module language
  274. echo "$m_title" # module title
  275. echo "$m_description" # module description
  276. } >"$Tmp/answers"
  277. jat_dump__file -D "$Tmp/answers"
  278. jat__eval -h "create module: $m_name in group $m_group from $MdefFileNick" \
  279. "preupg-content-creator <$Tmp/answers >/dev/null"
  280. newmod_path="$UPName/$m_group/$m_name"
  281. # add arbitrary values from $MdefFile
  282. #
  283. local extra_keys
  284. extra_keys=$(
  285. for key in $(_preupg_fupath__ini lskeys "preupgrade");
  286. do
  287. value=$(_preupg_fupath__ini 1value "preupgrade:$key")
  288. grep "^$key *=" "$newmod_path/module.ini" && continue
  289. test -n "$value" && echo "$key = $value"
  290. done
  291. )
  292. debug -v extra_keys
  293. echo "$extra_keys" >> "$newmod_path/module.ini"
  294. # append CODE to module script
  295. echo "$m_code_body" >> "$newmod_path/check"
  296. # dump to keep context clear
  297. #
  298. jat_dump__file -D \
  299. "$newmod_path/module.ini" \
  300. "$newmod_path/check"
  301. # add any arbitrary files stored in FILES section
  302. #
  303. for m_afile in $(_preupg_fupath__ini lskeys FILES);
  304. do
  305. test -n "$m_code_body" \
  306. && test "$m_afile" == "check" \
  307. && jat__log_warning "overwriting CODE by file from FILES section: $m_afile"
  308. mkdir -p "$(dirname "$newmod_path/$m_afile")"
  309. _preupg_fupath__ini values "FILES:$m_afile" \
  310. > "$newmod_path/$m_afile"
  311. done
  312. debug -c find "$UPName"
  313. }
  314. _preupg_fupath__ini() {
  315. #
  316. # do ini operation
  317. #
  318. local op=$1
  319. local arg=$2
  320. local fn
  321. local flt=_preupg_fupath__ini_cat
  322. case $op in
  323. lskeys) fn=_preupg_fupath__ini_lskeys ;;
  324. sec) fn=_preupg_fupath__ini_grepsec ;;
  325. values) fn=_preupg_fupath__ini_greppath
  326. flt=_preupg_fupath__ini_unind ;;
  327. 1value) fn=_preupg_fupath__ini_greppath
  328. flt=_preupg_fupath__ini_strip ;;
  329. *) jat__log_error "incorrect use of \`_preupg_fupath__ini()\`"
  330. esac
  331. <"$MdefFile" $fn "$arg" | $flt
  332. }
  333. _preupg_fupath__ini_strip() {
  334. #
  335. # Strip a simple value
  336. #
  337. head -1 | sed 's/^ *//; s/ *$//'
  338. }
  339. _preupg_fupath__ini_unind() {
  340. #
  341. # Unindent multi-line value
  342. #
  343. local remove=0
  344. local tmp
  345. tmp=$(mktemp -dt _preupg_fupath__ini_unind.XXXXXXXX)
  346. cat >"$tmp/body"
  347. grep . "$tmp/body" | sed 's/[^ ].*$//' > "$tmp/air"
  348. remove=$(sort "$tmp/air" | head -1 | wc -c)
  349. ((remove--)) # newline
  350. case $remove in
  351. 0) cat "$tmp/body" ;;
  352. *) colrm 1 "$remove" < "$tmp/body" ;;
  353. esac
  354. rm -rf "$tmp"
  355. }
  356. _preupg_fupath__ini_cat() {
  357. #
  358. # A no-op for text stream
  359. #
  360. while IFS= read -r line;
  361. do
  362. printf -- "%s\n" "$line"
  363. done
  364. }
  365. _preupg_fupath__ini_grepkey() {
  366. #
  367. # Read key from a section
  368. #
  369. local wnt=$1
  370. grep -v '\s*#' \
  371. | sed -e 's/ *=/=/;' \
  372. | grep -e "^$wnt=" \
  373. | cut -d= -f2-
  374. }
  375. _preupg_fupath__ini_greppath() {
  376. #
  377. # Read key from the right section
  378. #
  379. # E.g. `files:share:my/lib.sh` should read
  380. #
  381. # [files:share]
  382. # my/lib.sh = proj/my/lib.sh
  383. #
  384. local wnt="$1"
  385. local wntkey="${wnt##*:}"
  386. local wntsec="${wnt%:$wntkey}"
  387. _preupg_fupath__ini_grepsec "$wntsec" \
  388. | _preupg_fupath__ini_grepkey "$wntkey"
  389. }
  390. _preupg_fupath__ini_grepsec() {
  391. #
  392. # Read one INI section
  393. #
  394. local wnt="$1"
  395. local ok=false
  396. grep -v '\s*#' \
  397. | while IFS= read -r line;
  398. do
  399. case "$line" in
  400. \[$wnt\]) ok=true; continue ;;
  401. \[*\]) ok=false; continue ;;
  402. esac
  403. $ok || continue
  404. printf -- "%s\n" "$line"
  405. done \
  406. | sed -e 's/ *=/=/;'
  407. }
  408. _preupg_fupath__ini_lskeys() {
  409. #
  410. # List keys from a section
  411. #
  412. local sct="$1"
  413. _preupg_fupath__ini_grepsec "$sct" \
  414. | cut -d= -f1 \
  415. | sort \
  416. | uniq
  417. }
  418. #shellfu module-version=__MKIT_PROJ_VERSION__