openshift_quick_installer.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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 sys
  32. from ansible import constants as C
  33. from ansible.plugins.callback import CallbackBase
  34. from ansible.utils.color import colorize, hostcolor
  35. class CallbackModule(CallbackBase):
  36. """
  37. Ansible callback plugin
  38. """
  39. CALLBACK_VERSION = 2.2
  40. CALLBACK_TYPE = 'stdout'
  41. CALLBACK_NAME = 'openshift_quick_installer'
  42. CALLBACK_NEEDS_WHITELIST = False
  43. plays_count = 0
  44. plays_total_ran = 0
  45. def banner(self, msg, color=None):
  46. '''Prints a header-looking line with stars taking up to 80 columns
  47. of width (3 columns, minimum)
  48. Overrides the upstream banner method so that display is called
  49. with log_only=True
  50. '''
  51. msg = msg.strip()
  52. star_len = (79 - len(msg))
  53. if star_len < 0:
  54. star_len = 3
  55. stars = "*" * star_len
  56. self._display.display("\n%s %s" % (msg, stars), color=color, log_only=True)
  57. def v2_playbook_on_start(self, playbook):
  58. """This is basically the start of it all"""
  59. self.plays_count = len(playbook.get_plays())
  60. self.plays_total_ran = 0
  61. if self._display.verbosity > 1:
  62. from os.path import basename
  63. self.banner("PLAYBOOK: %s" % basename(playbook._file_name))
  64. def v2_playbook_on_play_start(self, play):
  65. """Each play calls this once before running any tasks
  66. We could print the number of tasks here as well by using
  67. `play.get_tasks()` but that is not accurate when a play includes a
  68. role. Only the tasks directly assigned to a play are exposed in the
  69. `play` object.
  70. """
  71. self.plays_total_ran += 1
  72. print("")
  73. print("Play %s/%s (%s)" % (self.plays_total_ran, self.plays_count, play.get_name()))
  74. name = play.get_name().strip()
  75. if not name:
  76. msg = "PLAY"
  77. else:
  78. msg = "PLAY [%s]" % name
  79. self._play = play
  80. self.banner(msg)
  81. # pylint: disable=unused-argument,no-self-use
  82. def v2_playbook_on_task_start(self, task, is_conditional):
  83. """This prints out the task header. For example:
  84. TASK [openshift_facts : Ensure PyYaml is installed] ***...
  85. Rather than print out all that for every task, we print a dot
  86. character to indicate a task has been started.
  87. """
  88. sys.stdout.write('.')
  89. args = ''
  90. # args can be specified as no_log in several places: in the task or in
  91. # the argument spec. We can check whether the task is no_log but the
  92. # argument spec can't be because that is only run on the target
  93. # machine and we haven't run it thereyet at this time.
  94. #
  95. # So we give people a config option to affect display of the args so
  96. # that they can secure this if they feel that their stdout is insecure
  97. # (shoulder surfing, logging stdout straight to a file, etc).
  98. if not task.no_log and C.DISPLAY_ARGS_TO_STDOUT:
  99. args = ', '.join(('%s=%s' % a for a in task.args.items()))
  100. args = ' %s' % args
  101. self.banner("TASK [%s%s]" % (task.get_name().strip(), args))
  102. if self._display.verbosity >= 2:
  103. path = task.get_path()
  104. if path:
  105. self._display.display("task path: %s" % path, color=C.COLOR_DEBUG, log_only=True)
  106. # pylint: disable=unused-argument,no-self-use
  107. def v2_playbook_on_handler_task_start(self, task):
  108. """Print out task header for handlers
  109. Rather than print out a header for every handler, we print a dot
  110. character to indicate a handler task has been started.
  111. """
  112. sys.stdout.write('.')
  113. self.banner("RUNNING HANDLER [%s]" % task.get_name().strip())
  114. # pylint: disable=unused-argument,no-self-use
  115. def v2_playbook_on_cleanup_task_start(self, task):
  116. """Print out a task header for cleanup tasks
  117. Rather than print out a header for every handler, we print a dot
  118. character to indicate a handler task has been started.
  119. """
  120. sys.stdout.write('.')
  121. self.banner("CLEANUP TASK [%s]" % task.get_name().strip())
  122. def v2_playbook_on_include(self, included_file):
  123. """Print out paths to statically included files"""
  124. msg = 'included: %s for %s' % (included_file._filename, ", ".join([h.name for h in included_file._hosts]))
  125. self._display.display(msg, color=C.COLOR_SKIP, log_only=True)
  126. def v2_runner_on_ok(self, result):
  127. """This prints out task results in a fancy format
  128. The only thing we change here is adding `log_only=True` to the
  129. .display() call
  130. """
  131. delegated_vars = result._result.get('_ansible_delegated_vars', None)
  132. self._clean_results(result._result, result._task.action)
  133. if result._task.action in ('include', 'include_role'):
  134. return
  135. elif result._result.get('changed', False):
  136. if delegated_vars:
  137. msg = "changed: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
  138. else:
  139. msg = "changed: [%s]" % result._host.get_name()
  140. color = C.COLOR_CHANGED
  141. else:
  142. if delegated_vars:
  143. msg = "ok: [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
  144. else:
  145. msg = "ok: [%s]" % result._host.get_name()
  146. color = C.COLOR_OK
  147. if result._task.loop and 'results' in result._result:
  148. self._process_items(result)
  149. else:
  150. if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
  151. msg += " => %s" % (self._dump_results(result._result),)
  152. self._display.display(msg, color=color, log_only=True)
  153. self._handle_warnings(result._result)
  154. def v2_runner_item_on_ok(self, result):
  155. """Print out task results for items you're iterating over"""
  156. delegated_vars = result._result.get('_ansible_delegated_vars', None)
  157. if result._task.action in ('include', 'include_role'):
  158. return
  159. elif result._result.get('changed', False):
  160. msg = 'changed'
  161. color = C.COLOR_CHANGED
  162. else:
  163. msg = 'ok'
  164. color = C.COLOR_OK
  165. if delegated_vars:
  166. msg += ": [%s -> %s]" % (result._host.get_name(), delegated_vars['ansible_host'])
  167. else:
  168. msg += ": [%s]" % result._host.get_name()
  169. msg += " => (item=%s)" % (self._get_item(result._result),)
  170. if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
  171. msg += " => %s" % self._dump_results(result._result)
  172. self._display.display(msg, color=color, log_only=True)
  173. def v2_runner_item_on_skipped(self, result):
  174. """Print out task results when an item is skipped"""
  175. if C.DISPLAY_SKIPPED_HOSTS:
  176. msg = "skipping: [%s] => (item=%s) " % (result._host.get_name(), self._get_item(result._result))
  177. if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
  178. msg += " => %s" % self._dump_results(result._result)
  179. self._display.display(msg, color=C.COLOR_SKIP, log_only=True)
  180. def v2_runner_on_skipped(self, result):
  181. """Print out task results when a task (or something else?) is skipped"""
  182. if C.DISPLAY_SKIPPED_HOSTS:
  183. if result._task.loop and 'results' in result._result:
  184. self._process_items(result)
  185. else:
  186. msg = "skipping: [%s]" % result._host.get_name()
  187. if (self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result:
  188. msg += " => %s" % self._dump_results(result._result)
  189. self._display.display(msg, color=C.COLOR_SKIP, log_only=True)
  190. def v2_playbook_on_notify(self, res, handler):
  191. """What happens when a task result is 'changed' and the task has a
  192. 'notify' list attached.
  193. """
  194. self._display.display("skipping: no hosts matched", color=C.COLOR_SKIP, log_only=True)
  195. def v2_playbook_on_stats(self, stats):
  196. """Print the final playbook run stats"""
  197. self._display.display("", screen_only=True)
  198. self.banner("PLAY RECAP")
  199. hosts = sorted(stats.processed.keys())
  200. for h in hosts:
  201. t = stats.summarize(h)
  202. self._display.display(
  203. u"%s : %s %s %s %s" % (
  204. hostcolor(h, t),
  205. colorize(u'ok', t['ok'], C.COLOR_OK),
  206. colorize(u'changed', t['changed'], C.COLOR_CHANGED),
  207. colorize(u'unreachable', t['unreachable'], C.COLOR_UNREACHABLE),
  208. colorize(u'failed', t['failures'], C.COLOR_ERROR)),
  209. screen_only=True
  210. )
  211. self._display.display(
  212. u"%s : %s %s %s %s" % (
  213. hostcolor(h, t, False),
  214. colorize(u'ok', t['ok'], None),
  215. colorize(u'changed', t['changed'], None),
  216. colorize(u'unreachable', t['unreachable'], None),
  217. colorize(u'failed', t['failures'], None)),
  218. log_only=True
  219. )
  220. self._display.display("", screen_only=True)
  221. self._display.display("", screen_only=True)
  222. # Some plays are conditional and won't run (such as load
  223. # balancers) if they aren't required. Let the user know about
  224. # this to avoid potential confusion.
  225. if self.plays_total_ran != self.plays_count:
  226. print("Installation Complete: Note: Play count is an estimate and some were skipped because your install does not require them")
  227. self._display.display("", screen_only=True)