Skip to content

Commit

Permalink
add support for output formats to pcs resource/stonith config commands
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmular committed Apr 5, 2022
1 parent 6f6a2ba commit bb01e77
Show file tree
Hide file tree
Showing 66 changed files with 4,827 additions and 1,252 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,8 @@
clusters can get a UUID by running the new `pcs cluster config uuid generate`
command ([rhbz#1950551])
- Add warning regarding move constraints to `pcs status` ([rhbz#1730232])
- Support for output formats `json` and `cmd` to `pcs resource config` and `pcs
stonith config` commands ([rhbz#1874624], [rhbz#1909904])

### Fixed
- Agents not conforming to OCF standard are processed as if they conformed to
Expand All @@ -34,6 +36,8 @@
[rhbz#1730232]: https://bugzilla.redhat.com/show_bug.cgi?id=1730232
[rhbz#1786964]: https://bugzilla.redhat.com/show_bug.cgi?id=1786964
[rhbz#1791661]: https://bugzilla.redhat.com/show_bug.cgi?id=1791661
[rhbz#1874624]: https://bugzilla.redhat.com/show_bug.cgi?id=1874624
[rhbz#1909904]: https://bugzilla.redhat.com/show_bug.cgi?id=1909904
[rhbz#1950551]: https://bugzilla.redhat.com/show_bug.cgi?id=1950551
[rhbz#1954099]: https://bugzilla.redhat.com/show_bug.cgi?id=1954099
[rhbz#2023845]: https://bugzilla.redhat.com/show_bug.cgi?id=2023845
Expand Down
6 changes: 6 additions & 0 deletions pcs/Makefile.am
Expand Up @@ -105,7 +105,12 @@ EXTRA_DIST = \
common/pacemaker/defaults.py \
common/pacemaker/nvset.py \
common/pacemaker/resource/__init__.py \
common/pacemaker/resource/bundle.py \
common/pacemaker/resource/clone.py \
common/pacemaker/resource/list.py \
common/pacemaker/resource/group.py \
common/pacemaker/resource/operations.py \
common/pacemaker/resource/primitive.py \
common/pacemaker/resource/relations.py \
common/pacemaker/role.py \
common/pacemaker/rule.py \
Expand Down Expand Up @@ -184,6 +189,7 @@ EXTRA_DIST = \
lib/booth/sync.py \
lib/cib/acl.py \
lib/cib/alert.py \
lib/cib/const.py \
lib/cib/constraint/colocation.py \
lib/cib/constraint/constraint.py \
lib/cib/constraint/__init__.py \
Expand Down
1 change: 1 addition & 0 deletions pcs/cli/common/lib_wrapper.py
Expand Up @@ -383,6 +383,7 @@ def load_module(env, middleware_factory, name):
"disable_safe": resource.disable_safe,
"disable_simulate": resource.disable_simulate,
"enable": resource.enable,
"get_configured_resources": resource.get_configured_resources,
"get_failcounts": resource.get_failcounts,
"group_add": resource.group_add,
"manage": resource.manage,
Expand Down
59 changes: 54 additions & 5 deletions pcs/cli/common/output.py
Expand Up @@ -3,14 +3,63 @@
from shutil import get_terminal_size
from typing import List

INDENT_STEP = 2
SUBSEQUENT_INDENT_STEP = 4

def format_with_indentation(
text: str, indentation: int = 0, max_length_trim: int = 0

def _smart_wrap(
text: str, subsequent_indent: int = SUBSEQUENT_INDENT_STEP
) -> List[str]:
initial_indent = len(text) - len(text.lstrip(" "))
return format_wrap_for_terminal(
text, subsequent_indent=subsequent_indent + initial_indent
)


def smart_wrap_text(
lines: List[str], subsequent_indent: int = SUBSEQUENT_INDENT_STEP
) -> List[str]:
output = []
for line in lines:
if not line:
output.append("")
continue
output.extend(_smart_wrap(line, subsequent_indent=subsequent_indent))
return output


def format_wrap_for_terminal(
text: str,
subsequent_indent: int = SUBSEQUENT_INDENT_STEP,
trim: int = 0,
) -> List[str]:
"""
Returns text as a list of lines. Length of a line is determined by a
terminal size if not explicitely specified.
text -- string to format
subsequent_indent -- number of spaces all subsequent lines will be indented
compared to the first one.
trim -- number which will be substracted from terminal size. Can be used in
cases lines will be indented later by this number of spaces.
"""
if any((sys.stdout.isatty(), sys.stderr.isatty())):
return textwrap.wrap(
return format_wrap(
text,
max(get_terminal_size()[0] - max_length_trim, 40),
subsequent_indent=" " * indentation,
# minimal line length is 40
max(get_terminal_size()[0] - trim, 40),
subsequent_indent=subsequent_indent,
)
return [text]


def format_wrap(
text: str,
max_length: int,
subsequent_indent: int = SUBSEQUENT_INDENT_STEP,
) -> List[str]:
return textwrap.wrap(
text,
max_length,
subsequent_indent=" " * subsequent_indent,
)
50 changes: 40 additions & 10 deletions pcs/cli/common/parse_args.py
@@ -1,7 +1,9 @@
from typing import (
AbstractSet,
Iterable,
Mapping,
Union,
cast,
)

from pcs.cli.common.errors import (
Expand All @@ -16,6 +18,11 @@

ModifierValueType = Union[None, bool, str]

_OUTPUT_FORMAT_OPTION_STR = "output-format"
_OUTPUT_FORMAT_OPTION = f"--{_OUTPUT_FORMAT_OPTION_STR}"
OUTPUT_FORMAT_VALUE_TEXT = "text"
_OUTPUT_FORMAT_VALUES = frozenset((OUTPUT_FORMAT_VALUE_TEXT, "cmd", "json"))

ARG_TYPE_DELIMITER = "%"

# h = help, f = file,
Expand Down Expand Up @@ -82,7 +89,7 @@
# allow overwriting existing files, currently meant for / used in CLI only
"overwrite",
# output format of commands, e.g: json, cmd, text, ...
"output-format=",
f"{_OUTPUT_FORMAT_OPTION_STR}=",
# auth token
"token=",
]
Expand Down Expand Up @@ -509,7 +516,9 @@ def __init__(self, options: Mapping[str, ModifierValueType]):
"--group": options.get("--group", None),
"--name": options.get("--name", None),
"--node": options.get("--node", None),
"--output-format": options.get("--output-format", "text"),
_OUTPUT_FORMAT_OPTION: options.get(
_OUTPUT_FORMAT_OPTION, OUTPUT_FORMAT_VALUE_TEXT
),
"--request-timeout": options.get("--request-timeout", None),
"--to": options.get("--to", None),
"--token": options.get("--token", None),
Expand All @@ -528,14 +537,16 @@ def get_subset(self, *options, **custom_options):
return InputModifiers(opt_dict)

def ensure_only_supported(
self, *supported_options, hint_syntax_changed: bool = False
):
unsupported_options = (
# --debug is supported in all commands
self._defined_options
- set(supported_options)
- set(["--debug"])
)
self,
*supported_options: str,
hint_syntax_changed: bool = False,
output_format_supported: bool = False,
) -> None:
# --debug is supported in all commands
supported_options_set = set(supported_options) | {"--debug"}
if output_format_supported:
supported_options_set.add(_OUTPUT_FORMAT_OPTION)
unsupported_options = self._defined_options - supported_options_set
if unsupported_options:
pluralize = lambda word: format_plural(unsupported_options, word)
raise CmdLineInputError(
Expand Down Expand Up @@ -613,3 +624,22 @@ def get(
if option in self._options:
return self._options[option]
raise AssertionError(f"Non existing default value for '{option}'")

def get_output_format(
self, supported_formats: AbstractSet[str] = _OUTPUT_FORMAT_VALUES
) -> str:
output_format = self.get(_OUTPUT_FORMAT_OPTION)
if output_format in supported_formats:
return cast(str, output_format)
raise CmdLineInputError(
(
"Unknown value '{value}' for '{option}' option. Supported "
"{value_pl} {is_pl}: {supported}"
).format(
value=output_format,
option=_OUTPUT_FORMAT_OPTION,
value_pl=format_plural(supported_formats, "value"),
is_pl=format_plural(supported_formats, "is"),
supported=format_list(list(supported_formats)),
)
)
4 changes: 2 additions & 2 deletions pcs/cli/nvset.py
Expand Up @@ -47,10 +47,10 @@ def nvset_dto_to_lines(
) -> List[str]:
in_effect_label = get_in_effect_label(nvset.rule) if nvset.rule else None
heading_parts = [
"{label}{in_effect}: {id}".format(
"{label}{in_effect}:{id}".format(
label=nvset_label,
in_effect=format_optional(in_effect_label, " ({})"),
id=nvset.id,
id=format_optional(nvset.id, " {}"),
)
]
if nvset.options:
Expand Down
4 changes: 3 additions & 1 deletion pcs/cli/reports/output.py
Expand Up @@ -13,10 +13,12 @@
def warn(message: str, stderr: bool = False) -> None:
stream = sys.stderr if stderr else sys.stdout
stream.write(f"Warning: {message}\n")
stream.flush()


def error(message: str) -> SystemExit:
sys.stderr.write(f"Error: {message}\n")
sys.stderr.flush()
return SystemExit(1)


Expand Down Expand Up @@ -45,7 +47,7 @@ def process_library_reports(report_item_list: ReportItemList) -> None:
continue

if severity != ReportItemSeverity.ERROR:
print(msg)
print(msg, flush=True)
continue

error(
Expand Down
4 changes: 2 additions & 2 deletions pcs/cli/reports/processor.py
Expand Up @@ -30,7 +30,7 @@ def _do_report(self, report_item: ReportItem) -> None:
if severity in self._ignore_severities:
# DEBUG overrides ignoring severities for debug reports
if msg and self.debug and severity == ReportItemSeverity.DEBUG:
print(msg)
print(msg, flush=True)
return

if severity == ReportItemSeverity.ERROR:
Expand All @@ -43,7 +43,7 @@ def _do_report(self, report_item: ReportItem) -> None:
elif severity == ReportItemSeverity.WARNING:
warn(msg)
elif msg and (self.debug or severity != ReportItemSeverity.DEBUG):
print(msg)
print(msg, flush=True)

def suppress_reports_of_severity(
self, severity_list: List[ReportItemSeverity]
Expand Down

0 comments on commit bb01e77

Please sign in to comment.