Real-time Capture and Display of Ansible Output Logs in HTML via Flask and Python

I am currently experimenting with an Ansible playbook executed from a Flask application, and I’m aiming to capture the output logs in real-time, then send them to my HTML using Socket.IO. Despite attempting options like setting quiet=False and exploring runner.events , I have not achieved the desired results as the logs are only sent after the playbook completes. I am relatively new to the Ansible Python integration. Any guidance or suggestions would be greatly appreciated. Thanks! Don’t mind the passwords ist for a local test machine :slight_smile:

import ansible_runner
@app.route('/generate_ansible_inventory', methods=['GET'])
def generate_ansible_inventory():
    # Load data from TinyDB
    vm_data_list = load_vm_from_db()

    # Create an empty inventory dictionary
    inventory = {"all": {"hosts": {}, "vars": {}}}

    # Loop through VMs in the TinyDB
    for vm_data in vm_data_list:
        # Add an inventory host
        inventory["all"]["hosts"][vm_data["vm_name"]] = {
            "ansible_host": vm_data["vm_ipv4_address"],
            "ansible_user": "bart",
            "ansible_ssh_pass": "bart",
            "ansible_become_pass": "bart"
        }

    # Save the inventory dictionary to a json file in the Ansible directory
    with open('ansible/inventory.json', 'w') as file:
        json.dump(inventory, file, indent=4)

    return jsonify(inventory)

# Run Ansible playbook asynchronously
def run_ansible_playbook_async(playbook_path, inventory_path):
    try:
        # Read the inventory from the JSON file
        with open(inventory_path, 'r') as file:
            inventory = json.load(file)

        # Run the playbook
        runner = ansible_runner.run(
            playbook=playbook_path,
            inventory=inventory,
            quiet=False,  # Set quiet to False to capture logs
            stdout_callback=log_callback,  # Set the callback for log messages
        )

        # Emit each log line to connected clients
        for event in runner.events:
            if event['event'] == 'runner_on_ok' and 'stdout' in event['stdout']:
                socketio.emit('ansible_output', {'log_message': event['stdout']['stdout_lines'][0]})
            elif event['event'] == 'runner_on_failed' and 'stderr' in event['stdout']:
                socketio.emit('ansible_output', {'log_message': event['stdout']['stdout_lines'][0]})

        # Print the output
        print("Playbook run status:", runner.status)
        print("Playbook run stats:", runner.stats)

    except Exception as e:
        print("Error running the Ansible playbook:", e)


# Route for running Ansible playbook
@app.route('/run_ansible_playbook', methods=['GET'])
def run_ansible_playbook():
    try:
        # Define playbook and inventory paths
        playbook_path = os.path.join(os.getcwd(), 'ansible', 'playbook.yml')
        inventory_path = os.path.join(os.getcwd(), 'ansible', 'inventory.json')

        # Start a new thread for running Ansible playbook asynchronously
        ansible_thread = Thread(target=run_ansible_playbook_async, args=(playbook_path, inventory_path))
        ansible_thread.start()

        return jsonify({"status": "Ansible playbook execution started in the background"})

    except Exception as e:
        return jsonify({"status": f"Error running Ansible playbook: {str(e)}"})

@app.route('/ansible_logs', methods=['GET'])
def ansible_logs():
    return render_template('ansible_logs.html')
```

I have achived it next way:

    @staticmethod
    def _run_process(command_line):
        logging.info(f'command_line: {command_line}')
        try:
            process = subprocess.Popen(command_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
            for line in iter(process.stdout.readline, b''):
                logging.info(line.decode(sys.stdout.encoding))

            process.wait()
            return True if process.returncode == 0 else False
        except Exception as err:
            logging.error(f'Process exception: {err}')
            return False