openshift_quick_installer.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. # pylint: disable=invalid-name,protected-access,import-error,line-too-long,attribute-defined-outside-init
  2. # This program is free software: you can redistribute it and/or modify
  3. # it under the terms of the GNU General Public License as published by
  4. # the Free Software Foundation, either version 3 of the License, or
  5. # (at your option) any later version.
  6. #
  7. # This program is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License
  13. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. """This file is a stdout callback plugin for the OpenShift Quick
  15. Installer. The purpose of this callback plugin is to reduce the amount
  16. of produced output for customers and enable simpler progress checking.
  17. What's different:
  18. * Playbook progress is expressed as: Play <current_play>/<total_plays> (Play Name)
  19. Ex: Play 3/30 (Initialize Megafrobber)
  20. * The Tasks and Handlers in each play (and included roles) are printed
  21. as a series of .'s following the play progress line.
  22. * Many of these methods include copy and paste code from the upstream
  23. default.py callback. We do that to give us control over the stdout
  24. output while allowing Ansible to handle the file logging
  25. normally. The biggest changes here are that we are manually setting
  26. `log_only` to True in the Display.display method and we redefine the
  27. Display.banner method locally so we can set log_only on that call as
  28. well.
  29. """
  30. from __future__ import (absolute_import, print_function)
  31. import imp
  32. import os
  33. import sys
  34. from ansible import constants as C
  35. from ansible.utils.color import colorize, hostcolor
  36. ANSIBLE_PATH = imp.find_module('ansible')[1]
  37. DEFAULT_PATH = os.path.join(ANSIBLE_PATH, 'plugins/callback/default.py')
  38. DEFAULT_MODULE = imp.load_source(
  39. 'ansible.plugins.callback.default',
  40. DEFAULT_PATH
  41. )
  42. try:
  43. from ansible.plugins.callback import CallbackBase
  44. BASECLASS = CallbackBase
  45. except ImportError: # < ansible 2.1
  46. BASECLASS = DEFAULT_MODULE.CallbackModule
  47. reload(sys)
  48. sys.setdefaultencoding('utf-8')
  49. class CallbackModule(DEFAULT_MODULE.CallbackModule):
  50. """
  51. Ansible callback plugin
  52. """
  53. CALLBACK_VERSION = 2.2
  54. CALLBACK_TYPE = 'stdout'
  55. CALLBACK_NAME = 'openshift_quick_installer'
  56. CALLBACK_NEEDS_WHITELIST = False
  57. plays_count = 0
  58. plays_total_ran = 0
  59. def banner(self, msg, color=None):
  60. '''Prints a header-looking line with stars taking up to 80 columns
  61. of width (3 columns, minimum)
  62. Overrides the upstream banner method so that display is called
  63. with log_only=True
  64. '''
  65. msg = msg.strip()
  66. star_len = (79 - len(msg))
  67. if star_len < 0:
  68. star_len = 3
  69. stars = "*" * star_len
  70. self._display.display("\n%s %s" % (msg, stars), color=color, log_only=True)
  71. def v2_playbook_on_start(self, playbook):
  72. """This is basically the start of it all"""
  73. self.plays_count = len(playbook.get_plays())
  74. self.plays_total_ran = 0
  75. if self._display.verbosity > 1:
  76. from os.path import basename
  77. self.banner("PLAYBOOK: %s" % basename(playbook._file_name))
  78. def v2_playbook_on_play_start(self, play):
  79. """Each play calls this once before running any tasks
  80. We could print the number of tasks here as well by using
  81. `play.get_tasks()` but that is not accurate when a play includes a
  82. role. Only the tasks directly assigned to a play are exposed in the
  83. `play` object.
  84. """
  85. self.plays_total_ran += 1
  86. print("")
  87. print("Play %s/%s (%s)" % (self.plays_total_ran, self.plays_count, play.get_name()))
  88. name = play.get_name().strip()
  89. if not name:
  90. msg = "PLAY"
  91. else:
  92. msg = "PLAY [%s]" % name
  93. self._play = play
  94. self.banner(msg)
  95. # pylint: disable=unused-argument,no-self-use
  96. def v2_playbook_on_task_start(self, task, is_conditional):
  97. """This prints out the task header. For example:
  98. TASK [openshift_facts : Ensure PyYaml is installed] ***...
  99. Rather than print out all that for every task, we print a dot
  100. character to indicate a task has been started.
  101. """
  102. sys.stdout.write('.')
  103. args = ''
  104. # args can be specified as no_log in several places: in the task or in
  105. # the argument spec. We can check whether the task is no_log but the
  106. # argument spec can't be because that is only run on the target
  107. # machine and we haven't run it thereyet at this time.
  108. #
  109. # So we give people a config option to affect display of the args so
  110. # that they can secure this if they feel that their stdout is insecure
  111. # (shoulder surfing, logging stdout straight to a file, etc).
  112. if not task.no_log and C.DISPLAY_ARGS_TO_STDOUT:
  113. args = ', '.join(('%s=%s' % a for a in task.args.items()))
  114. args = ' %s' % args
  115. self.banner("TASK [%s%s]" % (task.get_name().strip(), args))
  116. if self._display.verbosity >= 2:
  117. path = task.get_path()
  118. if path:
  119. self._display.display("task path: %s" % path, color=C.COLOR_DEBUG, log_only=True)
  120. # pylint: disable=unused-argument,no-self-use
  121. def v2_playbook_on_handler_task_start(self, task):
  122. """Print out task header for handlers
  123. Rather than print out a header for every handler, we print a dot
  124. character to indicate a handler task has been started.
  125. """
  126. sys.stdout.write('.')
  127. self.banner("RUNNING HANDLER [%s]" % task.get_name().strip())
  128. # pylint: disable=unused-argument,no-self-use
  129. def v2_playbook_on_cleanup_task_start(self, task):
  130. """Print out a task header for cleanup tasks
  131. Rather than print out a header for every handler, we print a dot
  132. character to indicate a handler task has been started.
  133. """
  134. sys.stdout.write('.')
  135. self.banner("CLEANUP TASK [%s]" % task.get_name().strip())
  136. def v2_playbook_on_include(self, included_file):
  137. """Print out paths to statically included files"""
  138. msg = 'included: %s for %s' % (included_file._filename, ", ".join([h.name for h in included_file._hosts]))
  139. self._display.display(msg, color=C.COLOR_SKIP, log_only=True)
  140. def v2_runner_on_ok(self, result):
  141. """This prints out task results in a fancy format
  142. The only thing we change here is adding `log_only=True` to the
  143. .display() call
  144. """
  145. delegated_vars = result._result.get('_ansible_delegated_vars', None)
  146. self._clean_results(result._result, result._task.action)
  147. if result._task.action in ('include', 'include_role'):
  148. return
  149. elif result._result.get('changed', False):
  150. if delegated_vars:
  151. msg = "changed: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
  152. else:
  153. msg = "changed: [%s]" % result._host.get_name()
  154. color = C.COLOR_CHANGED
  155. else:
  156. if delegated_vars:
  157. msg = "ok: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
  158. else:
  159. msg = "ok: [%s]" % result._host.get_name()
  160. color = C.COLOR_OK
  161. if result._task.loop and 'results' in result._result:
  162. self._process_items(result)
  163. else:
  164. if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
  165. msg += " => %s" % (self._dump_results(result._result),)
  166. self._display.display(msg, color=color, log_only=True)
  167. self._handle_warnings(result._result)
  168. def v2_runner_item_on_ok(self, result):
  169. """Print out task results for items you're iterating over"""
  170. delegated_vars = result._result.get('_ansible_delegated_vars', None)
  171. if result._task.action in ('include', 'include_role'):
  172. return
  173. elif result._result.get('changed', False):
  174. msg = 'changed'
  175. color = C.COLOR_CHANGED
  176. else:
  177. msg = 'ok'
  178. color = C.COLOR_OK
  179. if delegated_vars:
  180. msg += ": [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
  181. else:
  182. msg += ": [%s]" % result._host.get_name()
  183. msg += " => (item=%s)" % (self._get_item(result._result),)
  184. if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
  185. msg += " => %s" % self._dump_results(result._result)
  186. self._display.display(msg, color=color, log_only=True)
  187. def v2_runner_item_on_skipped(self, result):
  188. """Print out task results when an item is skipped"""
  189. if C.DISPLAY_SKIPPED_HOSTS:
  190. msg = "skipping: [%s] => (item=%s) " % (result._host.get_name(), self._get_item(result._result))
  191. if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
  192. msg += " => %s" % self._dump_results(result._result)
  193. self._display.display(msg, color=C.COLOR_SKIP, log_only=True)
  194. def v2_runner_on_skipped(self, result):
  195. """Print out task results when a task (or something else?) is skipped"""
  196. if C.DISPLAY_SKIPPED_HOSTS:
  197. if result._task.loop and 'results' in result._result:
  198. self._process_items(result)
  199. else:
  200. msg = "skipping: [%s]" % result._host.get_name()
  201. if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
  202. msg += " => %s" % self._dump_results(result._result)
  203. self._display.display(msg, color=C.COLOR_SKIP, log_only=True)
  204. def v2_playbook_on_notify(self, res, handler):
  205. """What happens when a task result is 'changed' and the task has a
  206. 'notify' list attached.
  207. """
  208. self._display.display("skipping: no hosts matched", color=C.COLOR_SKIP, log_only=True)
  209. def v2_playbook_on_stats(self, stats):
  210. """Print the final playbook run stats"""
  211. self._display.display("", screen_only=True)
  212. self.banner("PLAY RECAP")
  213. hosts = sorted(stats.processed.keys())
  214. for h in hosts:
  215. t = stats.summarize(h)
  216. self._display.display(
  217. u"%s : %s %s %s %s" % (
  218. hostcolor(h, t),
  219. colorize(u'ok', t['ok'], C.COLOR_OK),
  220. colorize(u'changed', t['changed'], C.COLOR_CHANGED),
  221. colorize(u'unreachable', t['unreachable'], C.COLOR_UNREACHABLE),
  222. colorize(u'failed', t['failures'], C.COLOR_ERROR)),
  223. screen_only=True
  224. )
  225. self._display.display(
  226. u"%s : %s %s %s %s" % (
  227. hostcolor(h, t, False),
  228. colorize(u'ok', t['ok'], None),
  229. colorize(u'changed', t['changed'], None),
  230. colorize(u'unreachable', t['unreachable'], None),
  231. colorize(u'failed', t['failures'], None)),
  232. log_only=True
  233. )
  234. self._display.display("", screen_only=True)
  235. self._display.display("", screen_only=True)
  236. # Some plays are conditional and won't run (such as load
  237. # balancers) if they aren't required. Let the user know about
  238. # this to avoid potential confusion.
  239. if self.plays_total_ran != self.plays_count:
  240. print("Installation Complete: Note: Play count is an estimate and some were skipped because your install does not require them")
  241. self._display.display("", screen_only=True)