From 547711d83e3d7d0d2ba33854c0d3530f72421355 Mon Sep 17 00:00:00 2001 From: Ivan Gromov Date: Fri, 9 Sep 2022 01:16:51 +0500 Subject: [PATCH] Show variables from event logs --- app/playbook.py | 213 ---------------------------------- app/server.py | 32 +++-- app/static/index.html | 14 +++ app/static/status-running.vue | 67 +++++++---- 4 files changed, 82 insertions(+), 244 deletions(-) delete mode 100644 app/playbook.py diff --git a/app/playbook.py b/app/playbook.py deleted file mode 100644 index f99e5c0..0000000 --- a/app/playbook.py +++ /dev/null @@ -1,213 +0,0 @@ -# (c) 2012, Michael DeHaan -# Copyright: (c) 2018, Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import os -import stat - -from ansible import context -from ansible.cli import CLI -from ansible.cli.arguments import option_helpers as opt_help -from ansible.errors import AnsibleError -from ansible.executor.playbook_executor import PlaybookExecutor -from ansible.module_utils._text import to_bytes -from ansible.playbook.block import Block -from ansible.utils.display import Display -from ansible.utils.collection_loader import AnsibleCollectionLoader, get_collection_name_from_path, set_collection_playbook_paths -from ansible.plugins.loader import add_all_plugin_dirs - - -display = Display() - - -class PlaybookCLI(CLI): - ''' the tool to run *Ansible playbooks*, which are a configuration and multinode deployment system. - See the project home page (https://docs.ansible.com) for more information. ''' - - def init_parser(self): - - # create parser for CLI options - super(PlaybookCLI, self).init_parser( - usage="%prog [options] playbook.yml [playbook2 ...]", - desc="Runs Ansible playbooks, executing the defined tasks on the targeted hosts.") - - opt_help.add_connect_options(self.parser) - opt_help.add_meta_options(self.parser) - opt_help.add_runas_options(self.parser) - opt_help.add_subset_options(self.parser) - opt_help.add_check_options(self.parser) - opt_help.add_inventory_options(self.parser) - opt_help.add_runtask_options(self.parser) - opt_help.add_vault_options(self.parser) - opt_help.add_fork_options(self.parser) - opt_help.add_module_options(self.parser) - - # ansible playbook specific opts - self.parser.add_argument('--list-tasks', dest='listtasks', action='store_true', - help="list all tasks that would be executed") - self.parser.add_argument('--list-tags', dest='listtags', action='store_true', - help="list all available tags") - self.parser.add_argument('--step', dest='step', action='store_true', - help="one-step-at-a-time: confirm each task before running") - self.parser.add_argument('--start-at-task', dest='start_at_task', - help="start the playbook at the task matching this name") - self.parser.add_argument('args', help='Playbook(s)', metavar='playbook', nargs='+') - - def post_process_args(self, options): - options = super(PlaybookCLI, self).post_process_args(options) - - display.verbosity = options.verbosity - self.validate_conflicts(options, runas_opts=True, fork_opts=True) - - return options - - def run(self): - - super(PlaybookCLI, self).run() - - # Note: slightly wrong, this is written so that implicit localhost - # manages passwords - sshpass = None - becomepass = None - passwords = {} - - # initial error check, to make sure all specified playbooks are accessible - # before we start running anything through the playbook executor - - b_playbook_dirs = [] - for playbook in context.CLIARGS['args']: - if not os.path.exists(playbook): - raise AnsibleError("the playbook: %s could not be found" % playbook) - if not (os.path.isfile(playbook) or stat.S_ISFIFO(os.stat(playbook).st_mode)): - raise AnsibleError("the playbook: %s does not appear to be a file" % playbook) - - b_playbook_dir = os.path.dirname(os.path.abspath(to_bytes(playbook, errors='surrogate_or_strict'))) - # load plugins from all playbooks in case they add callbacks/inventory/etc - add_all_plugin_dirs(b_playbook_dir) - - b_playbook_dirs.append(b_playbook_dir) - - set_collection_playbook_paths(b_playbook_dirs) - - playbook_collection = get_collection_name_from_path(b_playbook_dirs[0]) - - if playbook_collection: - display.warning("running playbook inside collection {0}".format(playbook_collection)) - AnsibleCollectionLoader().set_default_collection(playbook_collection) - - # don't deal with privilege escalation or passwords when we don't need to - if not (context.CLIARGS['listhosts'] or context.CLIARGS['listtasks'] or - context.CLIARGS['listtags'] or context.CLIARGS['syntax']): - (sshpass, becomepass) = self.ask_passwords() - passwords = {'conn_pass': sshpass, 'become_pass': becomepass} - - # create base objects - loader, inventory, variable_manager = self._play_prereqs() - - # (which is not returned in list_hosts()) is taken into account for - # warning if inventory is empty. But it can't be taken into account for - # checking if limit doesn't match any hosts. Instead we don't worry about - # limit if only implicit localhost was in inventory to start with. - # - # Fix this when we rewrite inventory by making localhost a real host (and thus show up in list_hosts()) - CLI.get_host_list(inventory, context.CLIARGS['subset']) - - # flush fact cache if requested - if context.CLIARGS['flush_cache']: - self._flush_cache(inventory, variable_manager) - - # create the playbook executor, which manages running the plays via a task queue manager - pbex = PlaybookExecutor(playbooks=context.CLIARGS['args'], inventory=inventory, - variable_manager=variable_manager, loader=loader, - passwords=passwords) - - results = pbex.run() - - if isinstance(results, list): - for p in results: - - display.display('\nplaybook: %s' % p['playbook']) - for idx, play in enumerate(p['plays']): - if play._included_path is not None: - loader.set_basedir(play._included_path) - else: - pb_dir = os.path.realpath(os.path.dirname(p['playbook'])) - loader.set_basedir(pb_dir) - - msg = "\n play #%d (%s): %s" % (idx + 1, ','.join(play.hosts), play.name) - mytags = set(play.tags) - msg += '\tTAGS: [%s]' % (','.join(mytags)) - - if context.CLIARGS['listhosts']: - playhosts = set(inventory.get_hosts(play.hosts)) - msg += "\n pattern: %s\n hosts (%d):" % (play.hosts, len(playhosts)) - for host in playhosts: - msg += "\n %s" % host - - display.display(msg) - - all_tags = set() - if context.CLIARGS['listtags'] or context.CLIARGS['listtasks']: - taskmsg = '' - if context.CLIARGS['listtasks']: - taskmsg = ' tasks:\n' - - def _process_block(b): - taskmsg = '' - for task in b.block: - if isinstance(task, Block): - taskmsg += _process_block(task) - else: - if task.action == 'meta': - continue - - all_tags.update(task.tags) - if context.CLIARGS['listtasks']: - cur_tags = list(mytags.union(set(task.tags))) - cur_tags.sort() - if task.name: - taskmsg += " %s" % task.get_name() - else: - taskmsg += " %s" % task.action - taskmsg += "\tTAGS: [%s]\n" % ', '.join(cur_tags) - - return taskmsg - - all_vars = variable_manager.get_vars(play=play) - for block in play.compile(): - block = block.filter_tagged_tasks(all_vars) - if not block.has_tasks(): - continue - taskmsg += _process_block(block) - - if context.CLIARGS['listtags']: - cur_tags = list(mytags.union(all_tags)) - cur_tags.sort() - taskmsg += " TASK TAGS: [%s]\n" % ', '.join(cur_tags) - - display.display(taskmsg) - - if 'vpn-host' not in inventory.groups: - raise ValueError('no_vpn_host') - else: - host = inventory.groups['vpn-host'].hosts[0].name - host_vars = variable_manager.get_vars()['hostvars'][host] - return { - 'CA_password': host_vars.get('CA_password'), - 'p12_export_password': host_vars.get('p12_export_password'), - 'algo_server_name': host_vars.get('server_name'), - 'ipv6_support': host_vars.get('ipv6_support'), - 'local_service_ip': host_vars.get('ansible_lo') and - host_vars.get('ansible_lo').get('ipv4_secondaries') and - host_vars.get('ansible_lo').get('ipv4_secondaries')[0]['address'], - 'ansible_ssh_host': host, - } - - @staticmethod - def _flush_cache(inventory, variable_manager): - for host in inventory.list_hosts(): - hostname = host.get_name() - variable_manager.clear_facts(hostname) diff --git a/app/server.py b/app/server.py index ab4f405..060b144 100644 --- a/app/server.py +++ b/app/server.py @@ -1,14 +1,9 @@ -import asyncio -import base64 -import concurrent.futures import configparser -import hashlib -import hmac import json import os +import re import sys from os.path import join, dirname, expanduser -from urllib.parse import quote, urlencode import ansible_runner import yaml @@ -59,7 +54,7 @@ task_program = '' class Status: - RUNNING = 'run' + RUNNING = 'running' ERROR = 'error' CANCELLED = 'cancelled' DONE = 'done' @@ -72,10 +67,30 @@ class Playbook: self.status = Status.NEW self.want_cancel = False self.events = [] + self.config_vars = {} self._runner = None def event_handler(self, data: dict) -> None: - self.events.append(data) + if data['event'] == 'runner_on_ok': + # Looking for '-passout pass:"{{ CA_password }}"' + if 'Build the CA pair' in data['event_data']['task']: + m = re.match(r'-passout pass:\"(?P.*)\"', data['event_data']['cmd']) + if m: + self.config_vars['CA_password'] = m.group('password') + + # Looking for '-passout pass:"{{ p12_export_password }}"' + if "Build the client's p12" in data['event_data']['task']: + m = re.match(r'-passout pass:\"(?P.*)\"', data['event_data']['cmd']) + if m: + self.config_vars['p12_export_password'] = m.group('password') + + # Looking for 'DNS = {{ wireguard_dns_servers }}' + if "Generate QR codes" in data['event_data']['task']: + self.config_vars['host'] = data['event_data']['host'] + m = re.match(r'DNS = (?P.*)\n\n', data['event_data']['cmd']) + if m: + self.config_vars['local_service_ip'] = m.group('dns') + self.events.append(data) def cancel_handler(self) -> bool: if self.want_cancel: @@ -113,6 +128,7 @@ async def handle_index(_): async def playbook_get_handler(_): return web.json_response({ 'status': playbook.status, + 'result': playbook.config_vars if playbook.status == Status.DONE else {}, 'events': playbook.events, }) diff --git a/app/static/index.html b/app/static/index.html index 74c4822..ee73afd 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -18,6 +18,20 @@ .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { opacity: 0; } + .console-item { + display: block; + max-height: 10em; + } + .console-enter-active, .console-leave-active { + transition: all 500ms; + } + .console-leave-to { + opacity: 0; + max-height: 0; + } + .console-enter { + opacity: 0; + } .back-button { position: absolute; border-radius: 50%; diff --git a/app/static/status-running.vue b/app/static/status-running.vue index 2daa05c..c25ec19 100644 --- a/app/static/status-running.vue +++ b/app/static/status-running.vue @@ -2,46 +2,67 @@

Set up usually takes 5-15 minutes

You can close tab and open it again

-

You can try to setup and run it again

+

You can try to + + setup and run it again +

Don’t close terminal!

+ + [{{ event.counter }}]: {{ event.stdout }} +