2017-11-21 15:34:58 +00:00
|
|
|
import ntpath
|
2018-12-30 20:32:11 +00:00
|
|
|
import os
|
2020-06-26 22:57:06 +00:00
|
|
|
import sys
|
2020-11-17 19:32:51 +00:00
|
|
|
from abc import ABC
|
2020-06-18 12:44:29 +00:00
|
|
|
|
2017-11-16 15:21:19 +00:00
|
|
|
from ansible.cli import CLI
|
2021-05-09 13:55:05 +00:00
|
|
|
from ansible.cli.arguments import option_helpers
|
2018-12-30 20:40:03 +00:00
|
|
|
from ansible.release import __version__ as ansible_version
|
2020-11-13 09:41:51 +00:00
|
|
|
from ansible.utils.display import Display
|
2019-06-09 23:00:49 +00:00
|
|
|
from packaging import version
|
2017-11-16 15:21:19 +00:00
|
|
|
|
|
|
|
from ansibleplaybookgrapher import __prog__, __version__
|
2020-11-13 09:41:51 +00:00
|
|
|
from ansibleplaybookgrapher.grapher import PlaybookGrapher
|
2020-11-15 15:50:56 +00:00
|
|
|
from ansibleplaybookgrapher.postprocessor import PostProcessor
|
|
|
|
|
2021-05-09 13:55:05 +00:00
|
|
|
# At some time, we needed to know if we are using ansible 2.8 because the CLI has been refactored in this PR:
|
2019-06-09 23:00:49 +00:00
|
|
|
# https://github.com/ansible/ansible/pull/50069
|
2020-01-13 19:17:10 +00:00
|
|
|
IS_ANSIBLE_2_9_X = version.parse(ansible_version) >= version.parse("2.9")
|
2019-06-09 23:00:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_cli_class():
|
|
|
|
"""
|
2021-05-09 13:29:06 +00:00
|
|
|
Utility function to return the class to use as CLI depending on Ansible version.
|
2019-06-09 23:00:49 +00:00
|
|
|
:return:
|
|
|
|
"""
|
2021-05-09 13:29:06 +00:00
|
|
|
|
|
|
|
return PlaybookGrapherCLI
|
2019-06-09 23:00:49 +00:00
|
|
|
|
|
|
|
|
2020-11-17 19:32:51 +00:00
|
|
|
class GrapherCLI(CLI, ABC):
|
2019-06-09 23:00:49 +00:00
|
|
|
"""
|
2021-05-09 13:29:06 +00:00
|
|
|
An abstract class to be implemented by the different Grapher CLIs.
|
2020-11-17 19:32:51 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
super(GrapherCLI, self).run()
|
|
|
|
|
|
|
|
loader, inventory, variable_manager = CLI._play_prereqs()
|
|
|
|
# Looks like the display is a singleton. This instruction will NOT return a new instance.
|
|
|
|
# This is why we set the verbosity later because someone set it before us.
|
|
|
|
display = Display()
|
|
|
|
display.verbosity = self.options.verbosity
|
|
|
|
|
|
|
|
grapher = PlaybookGrapher(data_loader=loader, inventory_manager=inventory, variable_manager=variable_manager,
|
|
|
|
display=display, tags=self.options.tags, skip_tags=self.options.skip_tags,
|
|
|
|
playbook_filename=self.options.playbook_filename,
|
|
|
|
include_role_tasks=self.options.include_role_tasks)
|
|
|
|
|
|
|
|
grapher.make_graph()
|
|
|
|
|
|
|
|
svg_path = grapher.render_graph(self.options.output_filename, self.options.save_dot_file)
|
|
|
|
post_processor = PostProcessor(svg_path=svg_path)
|
|
|
|
post_processor.post_process(graph_representation=grapher.graph_representation)
|
|
|
|
post_processor.write()
|
|
|
|
|
2021-03-12 06:39:42 +00:00
|
|
|
display.display(f"The graph has been exported to {svg_path}")
|
2020-11-17 19:32:51 +00:00
|
|
|
|
|
|
|
return svg_path
|
|
|
|
|
|
|
|
|
2021-05-09 13:29:06 +00:00
|
|
|
class PlaybookGrapherCLI(GrapherCLI):
|
2019-06-09 23:00:49 +00:00
|
|
|
"""
|
2021-05-09 13:55:05 +00:00
|
|
|
The dedicated playbook CLI for Ansible 2.9 and above (for the moment)
|
2019-06-09 23:00:49 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, args, callback=None):
|
2021-05-09 13:29:06 +00:00
|
|
|
super(PlaybookGrapherCLI, self).__init__(args=args, callback=callback)
|
2021-05-09 13:55:05 +00:00
|
|
|
# We keep the old options as instance attribute for backward compatibility for the grapher CLI.
|
|
|
|
# From Ansible 2.8, they remove this instance attribute 'options' and use a global context instead.
|
|
|
|
# But this may change in the future: https://github.com/ansible/ansible/blob/bcb64054edaa7cf636bd38b8ab0259f6fb93f3f9/lib/ansible/context.py#L8
|
2019-06-09 23:00:49 +00:00
|
|
|
self.options = None
|
|
|
|
|
2020-01-13 19:17:10 +00:00
|
|
|
def _add_my_options(self):
|
|
|
|
"""
|
|
|
|
Add some of my options to the parser
|
|
|
|
:param parser:
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
self.parser.prog = __prog__
|
|
|
|
|
|
|
|
self.parser.add_argument('-i', '--inventory', dest='inventory', action="append",
|
2020-06-26 22:57:06 +00:00
|
|
|
help="specify inventory host path or comma separated host list.")
|
2020-01-13 19:17:10 +00:00
|
|
|
|
|
|
|
self.parser.add_argument("--include-role-tasks", dest="include_role_tasks", action='store_true', default=False,
|
|
|
|
help="Include the tasks of the role in the graph.")
|
|
|
|
|
|
|
|
self.parser.add_argument("-s", "--save-dot-file", dest="save_dot_file", action='store_true', default=False,
|
|
|
|
help="Save the dot file used to generate the graph.")
|
|
|
|
|
|
|
|
self.parser.add_argument("-o", "--ouput-file-name", dest='output_filename',
|
|
|
|
help="Output filename without the '.svg' extension. Default: <playbook>.svg")
|
|
|
|
|
|
|
|
self.parser.add_argument('--version', action='version',
|
|
|
|
version="%s %s (with ansible %s)" % (__prog__, __version__, ansible_version))
|
|
|
|
|
|
|
|
self.parser.add_argument('playbook_filename', help='Playbook to graph', metavar='playbook')
|
|
|
|
|
2021-05-09 13:55:05 +00:00
|
|
|
# Use ansible helper to add some default options also
|
|
|
|
option_helpers.add_subset_options(self.parser)
|
|
|
|
option_helpers.add_vault_options(self.parser)
|
|
|
|
option_helpers.add_runtask_options(self.parser)
|
|
|
|
|
2019-06-09 23:00:49 +00:00
|
|
|
def init_parser(self, usage="", desc=None, epilog=None):
|
2021-05-09 13:29:06 +00:00
|
|
|
super(PlaybookGrapherCLI, self).init_parser(usage="%s [options] playbook.yml" % __prog__,
|
|
|
|
desc="Make graphs from your Ansible Playbooks.", epilog=epilog)
|
2019-06-09 23:00:49 +00:00
|
|
|
|
2020-01-13 19:17:10 +00:00
|
|
|
self._add_my_options()
|
2019-06-09 23:00:49 +00:00
|
|
|
|
2020-01-13 19:17:10 +00:00
|
|
|
def post_process_args(self, options):
|
2021-05-09 13:29:06 +00:00
|
|
|
options = super(PlaybookGrapherCLI, self).post_process_args(options)
|
2019-06-09 23:00:49 +00:00
|
|
|
|
|
|
|
# init the options
|
|
|
|
self.options = options
|
|
|
|
|
|
|
|
if self.options.output_filename is None:
|
|
|
|
# use the playbook name (without the extension) as output filename
|
|
|
|
self.options.output_filename = os.path.splitext(ntpath.basename(self.options.playbook_filename))[0]
|
|
|
|
|
2020-01-13 19:17:10 +00:00
|
|
|
return options
|
2019-06-09 23:00:49 +00:00
|
|
|
|
|
|
|
|
2019-02-18 22:42:54 +00:00
|
|
|
def main(args=None):
|
|
|
|
args = args or sys.argv
|
2019-06-09 23:00:49 +00:00
|
|
|
cli = get_cli_class()(args)
|
2017-11-16 15:21:19 +00:00
|
|
|
|
2019-02-18 23:28:51 +00:00
|
|
|
cli.run()
|
2017-11-16 15:21:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2018-12-31 14:16:23 +00:00
|
|
|
main(sys.argv)
|