Skip to content

🎉 [automated-actions-cli] output format #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# Base image with defaults for all stages
FROM registry.access.redhat.com/ubi9/ubi-minimal@sha256:e1c4703364c5cb58f5462575dc90345bcd934ddc45e6c32f9c162f2b5617681c AS base
FROM registry.access.redhat.com/ubi9-minimal@sha256:21ed5a01130d3a77eb4a26a80de70a4433860255b394e95dc2c26817de1da061 AS base

COPY LICENSE /licenses/

Expand All @@ -25,7 +25,7 @@ WORKDIR ${APP_ROOT}/src
#
# Builder image
#
FROM registry.access.redhat.com/ubi9/python-312@sha256:306e4320c559d67c60874e3bb85c9f84afc84b66c350b3d4afe0abd65201d6e6 AS builder
FROM registry.access.redhat.com/ubi9/python-312@sha256:aa2a3c086013ce259af562ddc64c5aa55471c42a2a8a731f185767e86a94283b AS builder
COPY --from=ghcr.io/astral-sh/uv:0.7.3@sha256:87a04222b228501907f487b338ca6fc1514a93369bfce6930eb06c8d576e58a4 /uv /bin/uv
ENV \
# use venv from ubi image
Expand All @@ -52,7 +52,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.3@sha256:87a04222b228501907f487b338ca6fc151

COPY Makefile ./
COPY --from=builder /opt/app-root /opt/app-root
RUN uv sync --frozen
RUN uv sync --frozen --verbose
RUN make test


Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.cli
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi9/python-312@sha256:306e4320c559d67c60874e3bb85c9f84afc84b66c350b3d4afe0abd65201d6e6 AS base
FROM registry.access.redhat.com/ubi9/python-312@sha256:aa2a3c086013ce259af562ddc64c5aa55471c42a2a8a731f185767e86a94283b AS base
COPY --from=ghcr.io/astral-sh/uv:0.7.3@sha256:87a04222b228501907f487b338ca6fc1514a93369bfce6930eb06c8d576e58a4 /uv /bin/uv

COPY LICENSE /licenses/
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.client
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi9/python-312@sha256:306e4320c559d67c60874e3bb85c9f84afc84b66c350b3d4afe0abd65201d6e6 AS base
FROM registry.access.redhat.com/ubi9/python-312@sha256:aa2a3c086013ce259af562ddc64c5aa55471c42a2a8a731f185767e86a94283b AS base
COPY --from=ghcr.io/astral-sh/uv:0.7.3@sha256:87a04222b228501907f487b338ca6fc1514a93369bfce6930eb06c8d576e58a4 /uv /bin/uv

COPY LICENSE /licenses/
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.integration_tests
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# Base image with defaults for all stages
FROM registry.access.redhat.com/ubi9/ubi-minimal@sha256:e1c4703364c5cb58f5462575dc90345bcd934ddc45e6c32f9c162f2b5617681c AS base
FROM registry.access.redhat.com/ubi9-minimal@sha256:21ed5a01130d3a77eb4a26a80de70a4433860255b394e95dc2c26817de1da061 AS base
COPY --from=ghcr.io/astral-sh/uv:0.7.3@sha256:87a04222b228501907f487b338ca6fc1514a93369bfce6930eb06c8d576e58a4 /uv /bin/uv

COPY LICENSE /licenses/
Expand All @@ -25,7 +25,7 @@ WORKDIR ${APP_ROOT}/src
#
# Builder image
#
FROM registry.access.redhat.com/ubi9/python-312@sha256:306e4320c559d67c60874e3bb85c9f84afc84b66c350b3d4afe0abd65201d6e6 AS builder
FROM registry.access.redhat.com/ubi9/python-312@sha256:aa2a3c086013ce259af562ddc64c5aa55471c42a2a8a731f185767e86a94283b AS builder
COPY --from=ghcr.io/astral-sh/uv:0.7.3@sha256:87a04222b228501907f487b338ca6fc1514a93369bfce6930eb06c8d576e58a4 /uv /bin/uv

ENV \
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.opa
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi9-minimal@sha256:e1c4703364c5cb58f5462575dc90345bcd934ddc45e6c32f9c162f2b5617681c AS base
FROM registry.access.redhat.com/ubi9-minimal@sha256:21ed5a01130d3a77eb4a26a80de70a4433860255b394e95dc2c26817de1da061 AS base
COPY --from=openpolicyagent/opa:1.4.2-static@sha256:3c995dc8a59f6ddfd92eb7404d2f7ff9fe71cd025d9251199957a8a6afbfd76e /opa /opa

ENV PATH=${PATH}:/
Expand Down
46 changes: 35 additions & 11 deletions packages/automated_actions_cli/automated_actions_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from rich.console import Console

from automated_actions_cli.config import config
from automated_actions_cli.formatter import JsonFormatter, OutputFormat, YamlFormatter
from automated_actions_cli.utils import (
blend_text,
kerberos_available,
Expand Down Expand Up @@ -82,12 +83,29 @@ def __exit__(self, *args: object, **kwargs: Any) -> None:
def main(
ctx: typer.Context,
*,
debug: Annotated[bool, typer.Option(help="Enable debug")] = False,
screen_capture_file: Annotated[Path | None, typer.Option(writable=True)] = None,
debug: Annotated[
bool, typer.Option(help="Enable debug", envvar="AA_DEBUG")
] = False,
screen_capture_file: Annotated[
Path | None,
typer.Option(
help="Capture screen recording as SVG",
writable=True,
envvar="AA_SCREEN_CAPTURE_FILE",
),
] = None,
version: Annotated[ # noqa: ARG001
bool | None, typer.Option(callback=version_callback, help="Display version")
] = None,
quiet: bool = typer.Option(default=False, help="Don't print anything"),
quiet: Annotated[
bool, typer.Option(help="Don't print anything", envvar="AA_QUIET")
] = False,
output: Annotated[
OutputFormat, typer.Option(help="Output format", envvar="AA_OUTPUT")
] = OutputFormat.yaml,
color: Annotated[
bool, typer.Option(help="Use colored output", envvar="AA_COLOR")
] = True,
) -> None:
if "--help" in sys.argv:
rich_print(
Expand All @@ -96,7 +114,7 @@ def main(
# do not initialize the client and everything else if --help is passed
return

if not quiet:
if not quiet and not screen_capture_file:
progress = progress_spinner(console=console)
progress.start()
progress.add_task(description="Processing...", total=None)
Expand All @@ -115,8 +133,7 @@ def main(
token=token,
raise_on_unexpected_status=True,
follow_redirects=True,
),
"console": console,
)
}

elif kerberos_available():
Expand All @@ -129,28 +146,35 @@ def main(
httpx_args={
"auth": HTTPSPNEGOAuth(mutual_authentication=OPTIONAL),
},
),
"console": console,
)
}
else:
logger.error(
"No bearer token or Kerberos authentication available. Please set AA_TOKEN or install and configure Kerberos."
)
raise typer.Exit(1)

printer = console.print if color else print
match output:
case OutputFormat.json:
ctx.obj["formatter"] = JsonFormatter(printer=printer)
case OutputFormat.yaml:
ctx.obj["formatter"] = YamlFormatter(printer=printer)
case _:
raise ValueError("Invalid output format")

# enforce the user to login
api_v1_me(client=ctx.obj["client"])

if screen_capture_file is not None:
screen_capture_file = screen_capture_file.with_suffix(".svg")
rich_print(f"Screen recording: {screen_capture_file}")
# strip $0 and screen_capture_file option
args = sys.argv[3:]
console.print(f"$ automated-actions {' '.join(args)}")
# title = command sub_command
title = " ".join(args[0:2])
atexit.register(
console.save_svg, str(screen_capture_file.with_suffix(".svg")), title=title
)
atexit.register(console.save_svg, str(screen_capture_file), title=title)


def initialize_client_actions() -> None:
Expand Down
34 changes: 34 additions & 0 deletions packages/automated_actions_cli/automated_actions_cli/formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import json
from collections.abc import Callable
from enum import StrEnum

import yaml


class OutputFormat(StrEnum):
"""Output format for the command line interface."""

json = "json"
yaml = "yaml"


class JsonFormatter:
def __init__(self, printer: Callable[[str], None], indent: int = 4) -> None:
self._printer = printer
self._indent = indent

def __call__(self, data: dict) -> None:
"""Format the data as JSON."""
self._printer(json.dumps(data, indent=self._indent, sort_keys=True))


class YamlFormatter:
def __init__(self, printer: Callable[[str], None], indent: int = 2) -> None:
self._printer = printer
self._indent = indent

def __call__(self, data: dict) -> None:
"""Format the data as yaml."""
self._printer(
yaml.dump(data, indent=self._indent, sort_keys=True, explicit_start=True)
)
4 changes: 4 additions & 0 deletions packages/automated_actions_cli/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ requires-python = "~= 3.12.0"
dependencies = [
"appdirs==1.4.4",
"automated-actions-client",
# https://github.com/fastapi/typer/pull/1145
"click<8.2.0",
"httpx-gssapi==0.4",
"pydantic-settings==2.9.1",
"pyyaml==6.0.2",
"rich==14.0.0",
"typer==0.15.3",
]
Expand All @@ -30,6 +33,7 @@ dev = [
"mypy==1.15.0",
"pytest==8.3.5",
"pytest-cov==6.1.1",
"types-pyyaml==6.0.12.20250402",
]

[project.scripts]
Expand Down
21 changes: 21 additions & 0 deletions packages/automated_actions_cli/tests/test_formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from automated_actions_cli.formatter import JsonFormatter, YamlFormatter


def test_formatter_json() -> None:
def check_output(output: str) -> None:
assert output.replace("\n", "") == '{"key": "value"}'

formatter = JsonFormatter(printer=check_output, indent=0)
formatter({"key": "value"})


def test_formatter_yaml() -> None:
def check_output(output: str) -> None:
assert (
output
== """---
key: value\n"""
)

formatter = YamlFormatter(printer=check_output, indent=0)
formatter({"key": "value"})
2 changes: 0 additions & 2 deletions packages/automated_actions_cli/tests/test_stub.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,5 @@ def action_cancel(
action_id: Annotated[str, typer.Option(help="", show_default=False)],
) -> None:
result = sync(action_id=action_id, client=ctx.obj["client"])
if "console" in ctx.obj:
ctx.obj["console"].print(result)
if "formatter" in ctx.obj and result:
ctx.obj["formatter"](result.to_dict() if hasattr(result, "to_dict") else result)
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,5 @@ def action_detail(
action_id: Annotated[str, typer.Option(help="", show_default=False)],
) -> None:
result = sync(action_id=action_id, client=ctx.obj["client"])
if "console" in ctx.obj:
ctx.obj["console"].print(result)
if "formatter" in ctx.obj and result:
ctx.obj["formatter"](result.to_dict() if hasattr(result, "to_dict") else result)
Original file line number Diff line number Diff line change
Expand Up @@ -203,5 +203,5 @@ def action_list(
] = ActionStatus.RUNNING,
) -> None:
result = sync(status=status, client=ctx.obj["client"])
if "console" in ctx.obj:
ctx.obj["console"].print(result)
if "formatter" in ctx.obj and result:
ctx.obj["formatter"](result.to_dict() if hasattr(result, "to_dict") else result)
Original file line number Diff line number Diff line change
Expand Up @@ -200,5 +200,5 @@ def create_token(
),
client=ctx.obj["client"],
)
if "console" in ctx.obj:
ctx.obj["console"].print(result)
if "formatter" in ctx.obj and result:
ctx.obj["formatter"](result.to_dict() if hasattr(result, "to_dict") else result)
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,5 @@ def me(
ctx: typer.Context,
) -> None:
result = sync(client=ctx.obj["client"])
if "console" in ctx.obj:
ctx.obj["console"].print(result)
if "formatter" in ctx.obj and result:
ctx.obj["formatter"](result.to_dict() if hasattr(result, "to_dict") else result)
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,5 @@ def openshift_workload_restart(
name=name,
client=ctx.obj["client"],
)
if "console" in ctx.obj:
ctx.obj["console"].print(result)
if "formatter" in ctx.obj and result:
ctx.obj["formatter"](result.to_dict() if hasattr(result, "to_dict") else result)
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ def {{endpoint.name | replace("-", "_") }}(

client=ctx.obj["client"]
)
if "console" in ctx.obj:
ctx.obj["console"].print(result)
if "formatter" in ctx.obj and result:
ctx.obj["formatter"](result.to_dict() if hasattr(result, "to_dict") else result)
{% endif %}
Loading