112 lines
3.0 KiB
Python
112 lines
3.0 KiB
Python
import os
|
|
import asyncio
|
|
from time import perf_counter
|
|
import subprocess
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("playbook", nargs="+")
|
|
return parser.parse_known_args()
|
|
|
|
|
|
async def run_playbook(playbook, args, results):
|
|
await results.put(("START", playbook, ""))
|
|
process = await asyncio.create_subprocess_exec(
|
|
"ansible-playbook",
|
|
playbook,
|
|
*args,
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
env={**os.environ, "ANSIBLE_FORCE_COLOR": "1"},
|
|
)
|
|
task = []
|
|
while line := (await process.stdout.readline()).decode():
|
|
if line == "\n":
|
|
chunk = "".join(task) + line
|
|
await results.put(
|
|
("RECAP" if "PLAY RECAP" in chunk else "TASK", playbook, chunk)
|
|
)
|
|
task = []
|
|
else:
|
|
task.append(line)
|
|
if task:
|
|
chunk = "".join(task)
|
|
await results.put(
|
|
("RECAP" if "PLAY RECAP" in chunk else "TASK", playbook, chunk)
|
|
)
|
|
|
|
await process.wait()
|
|
await results.put(("DONE", playbook, ""))
|
|
|
|
|
|
FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
|
|
DISABLE_CURSOR = "\033[?25l"
|
|
ENABLE_CURSOR = "\033[?25h"
|
|
|
|
|
|
def truncate(string, max_width=120):
|
|
if len(string) < max_width:
|
|
return string
|
|
return string[:max_width] + "…"
|
|
|
|
|
|
async def show_progression(results):
|
|
recaps = {}
|
|
starts = {}
|
|
ends = {}
|
|
currently_running = []
|
|
frameno = 0
|
|
print(DISABLE_CURSOR, end="")
|
|
try:
|
|
while result := await results.get():
|
|
frameno += 1
|
|
msgtype, playbook, msg = result
|
|
if msgtype == "START":
|
|
starts[playbook] = perf_counter()
|
|
currently_running.append(playbook)
|
|
if msgtype == "DONE":
|
|
currently_running.remove(playbook)
|
|
ends[playbook] = perf_counter()
|
|
if msgtype == "RECAP":
|
|
recaps[playbook] = msg
|
|
print(
|
|
FRAMES[frameno % len(FRAMES)],
|
|
f"{len(currently_running)} playbook{'s' if len(currently_running) > 1 else ''} running:",
|
|
f"{truncate(', '.join(currently_running))}",
|
|
end="\r",
|
|
)
|
|
finally:
|
|
print(ENABLE_CURSOR, end="")
|
|
for playbook, recap in recaps.items():
|
|
print(
|
|
f"# Playbook {playbook}, ran in {ends[playbook] - starts[playbook]:.0f}s",
|
|
end="\n\n",
|
|
)
|
|
for line in recap.split("\n"):
|
|
if "PLAY RECAP" not in line:
|
|
print(line)
|
|
|
|
|
|
async def amain():
|
|
args, remaining_args = parse_args()
|
|
results = asyncio.Queue()
|
|
asyncio.create_task(show_progression(results))
|
|
await asyncio.gather(
|
|
*[run_playbook(playbook, remaining_args, results) for playbook in args.playbook]
|
|
)
|
|
await results.put(None)
|
|
|
|
|
|
def main():
|
|
asyncio.run(amain())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|