Skip to content

Commit d5dc4ec

Browse files
committed
Allow to set ansible-output-data templates in the current file.
1 parent 37af393 commit d5dc4ec

File tree

6 files changed

+157
-32
lines changed

6 files changed

+157
-32
lines changed

changelogs/fragments/409-meta.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
minor_changes:
2-
- "Add ``ansible-output-meta`` directive that allows to apply meta actions, like resetting previous code blocks for variable references (https://github.com/ansible-community/antsibull-docs/pull/409)."
2+
- "Add ``ansible-output-meta`` directive that allows to apply meta actions, like resetting previous code blocks for variable references, or defining templates for ``ansible-output-data`` (https://github.com/ansible-community/antsibull-docs/pull/409)."

docs/ansible-output.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,59 @@ above ``yaml`` block as the ``yaml`` block with index 0:
240240
...
241241
```
242242

243+
### Define template for `ansible-output-data`
244+
245+
The `set-template` action defines a template for all following `ansible-output-data` directives.
246+
You can use all fields that you can also use for `ansible-output-data` in the template:
247+
```rst
248+
.. ansible-output-meta::
249+
250+
actions:
251+
- name: set-template
252+
template:
253+
# The environment variables will be merged. If a variable is provided here,
254+
# you do not have to provide it again in the directive - only if you want to
255+
# override its value.
256+
env:
257+
ANSIBLE_STDOUT_CALLBACK: community.general.tasks_only
258+
ANSIBLE_COLLECTIONS_TASKS_ONLY_NUMBER_OF_COLUMNS: "90"
259+
260+
# Will use this value if not specified in the directive.
261+
# If no language is provided in both the template and the directive,
262+
# 'ansible-output' will be used.
263+
language: console
264+
265+
# Will use this value if not specified in the directive.
266+
prepend_lines: |
267+
$ ansible-playbook playbook.yml
268+
269+
# Will use this value if not specified in the directive.
270+
skip_first_lines: 3
271+
272+
# Will use this value if not specified in the directive.
273+
skip_last_lines: 0
274+
275+
# The variables will be merged. If a varibale is provided here,
276+
# you can override it in the directive by specifying a variable
277+
# of the same name.
278+
variables:
279+
hosts:
280+
value: localhost
281+
tasks:
282+
previous_code_block: yaml+jinja
283+
previous_code_block_index: -1
284+
285+
# Will use this value if not specified in the directive.
286+
postprocessors: []
287+
288+
# Will use this value if explicitly set to null/~ in the directive.
289+
playbook: |-
290+
(some Ansible playbook)
291+
```
292+
293+
This can be useful to avoid repeating some definitions for multiple code blocks.
294+
If another `ansible-output-meta` action sets a new template, the previous templates will be thrown away.
295+
243296
## Post-processing ansible-playbook output
244297

245298
Out of the box, you can post-process the `ansible-playbook` output in some ways:

src/antsibull_docs/ansible_output/load.py

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
from sphinx_antsibull_ext.directive_helper import YAMLDirective
2828
from sphinx_antsibull_ext.schemas.ansible_output_data import (
2929
AnsibleOutputData,
30+
AnsibleOutputTemplate,
3031
NonRefPostprocessor,
3132
PostprocessorNameRef,
33+
combine,
3234
)
3335
from sphinx_antsibull_ext.schemas.ansible_output_meta import (
3436
ActionResetPreviousBlocks,
37+
ActionSetTemplate,
3538
AnsibleOutputMeta,
3639
)
3740

@@ -188,12 +191,16 @@ def __init__(
188191
self.blocks: list[Block] = []
189192
self.data: _AnsibleOutputDataExt | None = None
190193
self.previous_blocks: list[CodeBlockInfo] = []
194+
self.template = AnsibleOutputTemplate()
191195

192196
def _process_reset_previous_blocks(
193197
self, action: ActionResetPreviousBlocks # pylint: disable=unused-argument
194198
) -> None:
195199
self.previous_blocks.clear()
196200

201+
def _process_set_template(self, action: ActionSetTemplate) -> None:
202+
self.template = action.template
203+
197204
def process_meta(
198205
self,
199206
meta: AnsibleOutputMeta,
@@ -204,6 +211,8 @@ def process_meta(
204211
for action in meta.actions:
205212
if isinstance(action, ActionResetPreviousBlocks):
206213
self._process_reset_previous_blocks(action)
214+
elif isinstance(action, ActionSetTemplate):
215+
self._process_set_template(action)
207216
else:
208217
raise AssertionError("Unknown action") # pragma: no cover
209218

@@ -227,11 +236,25 @@ def process_special_block(
227236
self.errors.append(Error(self.path, line, col, message))
228237
if "antsibull-other-data" in block.attributes:
229238
if directive == "ansible-output-data":
230-
self.data = _AnsibleOutputDataExt(
231-
data=block.attributes["antsibull-other-data"],
232-
line=block.row_offset + 1,
233-
col=block.col_offset + 1,
234-
)
239+
try:
240+
data = combine(
241+
data=block.attributes["antsibull-other-data"],
242+
template=self.template,
243+
)
244+
self.data = _AnsibleOutputDataExt(
245+
data=data,
246+
line=block.row_offset + 1,
247+
col=block.col_offset + 1,
248+
)
249+
except ValueError as exc:
250+
self.errors.append(
251+
Error(
252+
self.path,
253+
block.row_offset + 1,
254+
block.col_offset + 1,
255+
str(exc),
256+
)
257+
)
235258
if directive == "ansible-output-meta":
236259
self.process_meta(
237260
block.attributes["antsibull-other-data"],
@@ -249,23 +272,24 @@ def _add_block(
249272
env.update(data.data.env)
250273
postprocessors = []
251274
error = False
252-
for postprocessor in data.data.postprocessors:
253-
if isinstance(postprocessor, PostprocessorNameRef):
254-
ref = postprocessor.name
255-
try:
256-
postprocessor = self.environment.global_postprocessors[ref]
257-
except KeyError:
258-
self.errors.append(
259-
Error(
260-
self.path,
261-
data.line,
262-
data.col,
263-
f"No global postprocessor of name {ref!r} defined",
275+
if data.data.postprocessors:
276+
for postprocessor in data.data.postprocessors:
277+
if isinstance(postprocessor, PostprocessorNameRef):
278+
ref = postprocessor.name
279+
try:
280+
postprocessor = self.environment.global_postprocessors[ref]
281+
except KeyError:
282+
self.errors.append(
283+
Error(
284+
self.path,
285+
data.line,
286+
data.col,
287+
f"No global postprocessor of name {ref!r} defined",
288+
)
264289
)
265-
)
266-
error = True
267-
continue
268-
postprocessors.append(postprocessor)
290+
error = True
291+
continue
292+
postprocessors.append(postprocessor)
269293
if error:
270294
return
271295
self.blocks.append(

src/antsibull_docs/ansible_output/process.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def _get_variable_value(
6161
def _compose_playbook(
6262
data: AnsibleOutputData, *, previous_blocks: list[CodeBlockInfo]
6363
) -> str:
64+
if data.playbook is None:
65+
raise AssertionError("playbook cannot be None at this point")
66+
6467
if all(s not in data.playbook for s in ("@{%", "@{{", "@{#")):
6568
return data.playbook
6669

@@ -221,8 +224,8 @@ async def _compute_code_block_content(
221224
flog.notice("Post-process result")
222225
lines = _massage_stdout(
223226
stdout,
224-
skip_first_lines=block.data.skip_first_lines,
225-
skip_last_lines=block.data.skip_last_lines,
227+
skip_first_lines=block.data.skip_first_lines or 0,
228+
skip_last_lines=block.data.skip_last_lines or 0,
226229
prepend_lines=block.data.prepend_lines,
227230
)
228231
for postprocessor in block.merged_postprocessors:

src/sphinx_antsibull_ext/schemas/ansible_output_data.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ class PostprocessorNameRef(p.BaseModel):
5050
NonRefPostprocessor = t.Union[PostprocessorCLI]
5151

5252

53-
class AnsibleOutputData(p.BaseModel):
54-
playbook: str
53+
class AnsibleOutputTemplate(p.BaseModel):
54+
playbook: t.Optional[str] = None
5555
env: dict[str, str] = {}
56-
prepend_lines: str = ""
57-
language: str = "ansible-output"
56+
prepend_lines: t.Optional[str] = None
57+
language: t.Optional[str] = None
5858
variables: dict[str, VariableSource] = {}
59-
skip_first_lines: int = 0
60-
skip_last_lines: int = 0
61-
postprocessors: list[Postprocessor] = []
59+
skip_first_lines: t.Optional[int] = None
60+
skip_last_lines: t.Optional[int] = None
61+
postprocessors: t.Optional[list[Postprocessor]] = None
6262

6363
@p.field_validator("env", mode="before")
6464
@classmethod
@@ -73,3 +73,39 @@ def convert_dict_values(cls, obj):
7373
if isinstance(v, int):
7474
obj[k] = str(v)
7575
return obj
76+
77+
78+
class AnsibleOutputData(AnsibleOutputTemplate):
79+
playbook: t.Optional[str] # no default
80+
81+
82+
_T = t.TypeVar("_T")
83+
84+
85+
def _coalesce(*values: _T | None) -> _T | None:
86+
for value in values:
87+
if value is not None:
88+
return value
89+
return None
90+
91+
92+
def combine(
93+
*, template: AnsibleOutputTemplate, data: AnsibleOutputData
94+
) -> AnsibleOutputData:
95+
playbook = _coalesce(data.playbook, template.playbook)
96+
if playbook is None:
97+
raise ValueError("Cannot use template's playbook since that is not set")
98+
env = template.env.copy()
99+
env.update(data.env)
100+
variables = template.variables.copy()
101+
variables.update(data.variables)
102+
return AnsibleOutputData(
103+
playbook=playbook,
104+
env=env,
105+
prepend_lines=data.prepend_lines or template.prepend_lines,
106+
language=data.language or template.language or "ansible-output",
107+
variables=variables,
108+
skip_first_lines=_coalesce(data.skip_first_lines, template.skip_first_lines),
109+
skip_last_lines=_coalesce(data.skip_last_lines, template.skip_last_lines),
110+
postprocessors=_coalesce(data.postprocessors, template.postprocessors),
111+
)

src/sphinx_antsibull_ext/schemas/ansible_output_meta.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,23 @@
1111

1212
import pydantic as p
1313

14+
from .ansible_output_data import AnsibleOutputTemplate
15+
1416

1517
class ActionResetPreviousBlocks(p.BaseModel):
1618
model_config = p.ConfigDict(frozen=True, extra="forbid", validate_default=True)
1719

1820
name: t.Literal["reset-previous-blocks"]
1921

2022

21-
AnsibleOutputAction = t.Union[ActionResetPreviousBlocks]
23+
class ActionSetTemplate(p.BaseModel):
24+
model_config = p.ConfigDict(frozen=True, extra="forbid", validate_default=True)
25+
26+
name: t.Literal["set-template"]
27+
template: AnsibleOutputTemplate
28+
29+
30+
AnsibleOutputAction = t.Union[ActionResetPreviousBlocks, ActionSetTemplate]
2231

2332

2433
class AnsibleOutputMeta(p.BaseModel):

0 commit comments

Comments
 (0)