Skip to content

Commit

Permalink
Merge pull request #2 from whytewolf/outputters
Browse files Browse the repository at this point in the history
Outputters
  • Loading branch information
whytewolf authored Nov 25, 2024
2 parents 20ee99a + 0e86766 commit 0a41acd
Show file tree
Hide file tree
Showing 16 changed files with 271 additions and 115 deletions.
5 changes: 0 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ repos:
# Run the formatter.
- id: ruff-format
types_or: [python, pyi]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
additional_dependencies: [types-PyYAML]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
Expand Down
10 changes: 10 additions & 0 deletions .vim/projector.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"name": "guajillo test",
"justMyCode": false,
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/src/guajillo/main.py",
"args": ["salt", "salt00", "test.ping"]
}
]
Binary file modified images/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 68 additions & 68 deletions pdm.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/guajillo/outputs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import importlib
import pkgutil

for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
module = importlib.import_module(f"{__name__}.{module_name}")
globals()[module_name] = getattr(module, "render")
25 changes: 25 additions & 0 deletions src/guajillo/outputs/boolean.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Any

from rich.console import Group
from rich.table import Table

import guajillo.outputs


async def render(event: dict[str, Any], console) -> None:
result = Table(title="Results")
result.add_column("Minion")
result.add_column("Result")
for item in event["info"][0]["Result"]:
if (
"success" in event["info"][0]["Result"][item]
and event["info"][0]["Result"][item]["success"]
) or (
"success" in event["info"][0]["Result"][item]["return"]
and event["info"][0]["Result"][item]["return"]["success"]
):
result.add_row(item, "[green]✔[/green]")
else:
result.add_row(item, "[red]✘[/red]")
unknown = await guajillo.outputs.non_returns(event, console)
return Group(result, unknown)
43 changes: 43 additions & 0 deletions src/guajillo/outputs/highstate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Any

from rich.console import Group
from rich.table import Table
from rich.text import Text

import guajillo.outputs


async def render(event: dict[str, Any], console) -> None:
output = event["info"][0]["Result"]
if "Minions" in event["info"][0]:
isMinion = True
minions = Table(title="minion highstate", show_header=True)
minions.add_column("Minion", style="cyan")
minions.add_column("Highstate")
for minion, returned in output.items():
highstate = Table(expand=True)
highstate.add_column("id", style="magenta")
highstate.add_column("name", style="magenta")
highstate.add_column("function", style="magenta")
highstate.add_column("result", style="magenta")
highstate.add_column("changes", style="magenta")
for id, results in returned["return"].items():
module, id, name, fun = id.split("_|-")
if results["result"]:
label = Text("✔", style="green")
else:
label = Text("✘", style="red")
changes = await guajillo.outputs.yaml(
{"return": [results.get("changes", "")]}, console
)
highstate.add_row(
id,
name,
f"{module}.{fun}",
label,
changes,
)
minions.add_row(minion, highstate)

nonreturned = await guajillo.outputs.non_returns(event, console)
return Group(minions, nonreturned)
8 changes: 8 additions & 0 deletions src/guajillo/outputs/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import json
from typing import Any

from rich.json import JSON


async def render(event: dict["str", Any], console) -> None:
return JSON(json.dumps(event))
15 changes: 15 additions & 0 deletions src/guajillo/outputs/non_returns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from rich.console import Group
from rich.text import Text


async def render(event, console):
output = event["info"][0]["Result"]
items = event["info"][0]["Minions"]
nonreturned = [x for x in items if x not in output]
if len(nonreturned) > 0:
header = Text("[red]Minions that did not return[/red]\n")
minions = Text(
", ".join(list(map(lambda item: f"[red]✘ {item}[/red]", nonreturned)))
)
return Group(header, minions)
return Text("All minions returned")
55 changes: 55 additions & 0 deletions src/guajillo/outputs/profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from typing import Any

from rich.console import Group
from rich.table import Table
from rich.text import Text

import guajillo.outputs


async def render(event: dict[str, Any], console) -> None:
output = event["info"][0]["Result"]
isMinion = False
if "Minions" in event["info"][0]:
isMinion = True
for minion, returned in output.items():
state = Table(title=f"{minion}", width=120, highlight=True)
state.add_column("State", style="cyan")
state.add_column("name", style="cyan")
state.add_column("Function", style="cyan")
state.add_column("Result")
state.add_column("Duration", style="magenta")
duration = ""
total_duration = 0
good = 0
bad = 0
if isMinion:
vexed = returned["return"]
else:
vexed = returned["return"]["return"]["data"][minion]

for id, results in vexed.items():
module, id, name, fun = id.split("_|-")
if results["result"]:
result = Text("✔ ", style="green")
good += 1
else:
result = Text("✘", style="red")
bad += 1
if "duration" in results:
duration = Text(
f"{results['duration']} ms",
style="bold magenta",
)
total_duration += results["duration"]
state.add_row(id, name, f"{module}.{fun}", result, duration)
state.add_section()
state.add_row(
"Final Total",
"",
"",
f"[red]{bad}[/red]/[green]{good}[/green] ({bad + good})",
f"[bold magenta]{total_duration/1000:.4f} s[/bold magenta]",
)
nonreturned = await guajillo.outputs.non_returns(event, console)
return Group(state, nonreturned)
9 changes: 9 additions & 0 deletions src/guajillo/outputs/yaml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import Any

import yaml
from rich.syntax import Syntax


async def render(event: dict[str, Any], console) -> None:
yaml_output = yaml.dump(event["return"][0])
return Syntax(yaml_output, "yaml")
10 changes: 8 additions & 2 deletions src/guajillo/utils/cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import argparse
import operator
import sys
from inspect import getmembers, isfunction

from rich.console import Console
from rich.table import Table

import guajillo.outputs
from guajillo.utils.console import stderr_console


Expand All @@ -16,6 +19,9 @@ def __init__(self, console: Console = stderr_console) -> None:
add_help=False,
)
self.console = console
self.outputs = list(
map(operator.itemgetter(0), getmembers(guajillo.outputs, isfunction))
)

def build_args(self, args: list["str"]) -> None:
"""
Expand All @@ -38,7 +44,7 @@ def build_args(self, args: list["str"]) -> None:
"-o",
"--out",
dest="output",
choices=["json", "yaml", "boolean"],
choices=self.outputs,
help="Output method will auto detect if possable, use this to force or set to json for json output",
)
self.parser.add_argument(
Expand Down Expand Up @@ -99,7 +105,7 @@ def help(self, doexit: bool = True):
options.add_row(
"-o",
"--out",
"{json, yaml, boolean}",
f"{{ {', '.join(self.outputs)} }}",
"force output style through a known output",
"auto",
)
Expand Down
5 changes: 4 additions & 1 deletion src/guajillo/utils/conn.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ async def job_lookup(self, jid: str):
async def check_outputer(self, fun: str) -> str:
defined_outputers = {
"test.ping": "boolean",
"state.sls": "highstate",
"state.highstate": "highstate",
"state.apply": "highstate",
}
if self.parser.parsed_args.output is not None:
return self.parser.parsed_args.output
Expand All @@ -179,6 +182,7 @@ async def taskMan(self, async_comms: dict["str", Any]) -> None:
"""
async controller for salt-API client.
"""
# TODO: This methed is way to big. need to break it into smaller parts
try:
log.info("Starting Client Task Manager")
self.async_comms = async_comms
Expand Down Expand Up @@ -220,7 +224,6 @@ async def taskMan(self, async_comms: dict["str", Any]) -> None:
self.async_comms["update"].set()
ttl = self.parser.parsed_args.timeout
# TODO: This needs to be split into its own function

# TODO: use streamMon as a way to tell if we need to check the job
while output_event["meta"]["step"] != "final":
step = "normal"
Expand Down
47 changes: 12 additions & 35 deletions src/guajillo/utils/outputs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import asyncio
import json
import logging
from typing import Any

import yaml
from rich.console import Console
from rich.status import Status
from rich.syntax import Syntax

import guajillo.outputs
from guajillo.exceptions import TerminateTaskGroup

log = logging.getLogger(__name__)
Expand All @@ -21,14 +19,6 @@ def __init__(self, console: Console, parser=None, config=None) -> None:
self.cstatus = Status("Waiting ...", console=self.console)
self.cstatus.start()

async def json(self, event: dict["str", Any]) -> None:
self.console.print_json(json.dumps(event))

async def yaml(self, event: dict["str", Any]) -> None:
yaml_output = yaml.dump(event["return"][0])
syntax = Syntax(yaml_output, "yaml", line_numbers=True)
self.console.print(syntax)

async def status(self, event: dict[str, Any]) -> None:
if "Minions" in event["info"][0]:
queued = f"{len(list(event['return'][0].keys()))}/{len(event["info"][0]["Minions"])}"
Expand All @@ -37,28 +27,6 @@ async def status(self, event: dict[str, Any]) -> None:
msg = f"waiting on master for jid: {event['info'][0]['jid']}"
self.cstatus.update(msg)

async def boolean(self, event: dict[str, Any]) -> None:
nonreturned = []
output = event["return"][0]
if "Minions" in event["info"][0]:
items = event["info"][0]["Minions"]
nonreturned = [x for x in items if x not in output]
for item in event["info"][0]["Result"]:
if (
"success" in event["info"][0]["Result"][item]
and event["info"][0]["Result"][item]["success"]
) or (
"success" in event["info"][0]["Result"][item]["return"]
and event["info"][0]["Result"][item]["return"]["success"]
):
self.console.print(f"[green]✔ {item}[/green]")
else:
self.console.print(f"[red]:-1: {item}[/red]")
if len(nonreturned) > 0:
self.console.print("[red]Minions that did not return[/red]")
for item in nonreturned:
self.console.print(f"[red]✘ {item}[/red]")

async def string(self, output: str) -> None:
self.console.print(output)

Expand All @@ -75,9 +43,18 @@ async def taskMan(self, async_comms: dict[str, Any]) -> None:
if event["meta"]["step"] == "final":
self.cstatus.stop()
log.debug(f"calling outputer {event["meta"]["output"]}")
if event["output"]["return"][0] == {}:
if (
not event["output"]["return"][0]
and "info" not in event["output"]
):
await self.string("No known minions matched target")
await getattr(self, event["meta"]["output"])(event["output"])
if event["meta"]["output"] == "status":
await self.status(event["output"])
else:
output = await getattr(
guajillo.outputs, event["meta"]["output"]
)(event["output"], self.console)
self.console.print(output)
await asyncio.sleep(0.5)
self.async_comms["one"] = True
except Exception:
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/utils/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ def test_help():
│ │ │ │ use as login │ │
│ │ │ │ info │ │
│ -o │ --out │ {json, yaml, │ force output │ auto │
│ │ │ boolean} │ style through a │ │
│ │ │ │ known output │ │
│ │ │ boolean, │ style through a │ │
│ │ │ highstate} │ known output │ │
│ │ --output-file │ OUTPUT_FILE │ Not implimented, │ │
│ │ │ │ output file to │ │
│ │ │ │ dump output to │ │
Expand Down
8 changes: 6 additions & 2 deletions vhs/demo.tape
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Output images/demo.gif
Set FontSize 24
Set Width 2400
Set Height 1200
Set Shell zsh
Sleep 5ms
Type "guajillo -t 10 salt '*' test.ping"
Type "guajillo -t 10 --out profile salt salt00 state.apply"
Enter
Sleep 35s
Sleep 15s
Ctrl+D

0 comments on commit 0a41acd

Please sign in to comment.