Skip to content

Commit 8aad334

Browse files
authored
feat: Add build_arg_defaults support for execution environment projects (#584)
* clean build_args_default * remove scm changes from this PR * fix tests * Try to fix lint
1 parent e62c535 commit 8aad334

8 files changed

Lines changed: 352 additions & 127 deletions

File tree

cspell.config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ ignoreWords:
2828
- adoc
2929
- argparser
3030
- dellos
31+
- dictsort
3132
- endfor
3233
- envsubst
3334
- endif
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""Custom argparse formatter and parser used by ``arg_parser``."""
2+
3+
from __future__ import annotations
4+
5+
import argparse
6+
import sys
7+
8+
from argparse import HelpFormatter
9+
from operator import attrgetter
10+
from typing import TYPE_CHECKING
11+
12+
13+
if TYPE_CHECKING:
14+
from collections.abc import Iterable
15+
from typing import Any
16+
17+
18+
class CustomHelpFormatter(HelpFormatter): # pragma: no cover py>=3.14
19+
"""A custom help formatter."""
20+
21+
def __init__(self, prog: str) -> None:
22+
"""Initialize the help formatter.
23+
24+
Args:
25+
prog: The program name
26+
"""
27+
long_string = "--abc --really_really_really_log"
28+
# 3 here accounts for the spaces in the ljust(6) below
29+
HelpFormatter.__init__(
30+
self,
31+
prog=prog,
32+
indent_increment=1,
33+
max_help_position=len(long_string) + 3,
34+
)
35+
36+
def _format_action_invocation(
37+
self,
38+
action: argparse.Action,
39+
) -> str:
40+
"""Format the action invocation.
41+
42+
Args:
43+
action: The action to format
44+
45+
Raises:
46+
ValueError: If more than 2 options are given
47+
48+
Returns:
49+
The formatted action invocation
50+
"""
51+
if not action.option_strings: # pragma: no branch
52+
default = self._get_default_metavar_for_positional(action)
53+
(metavar,) = self._metavar_formatter(action, default)(1)
54+
return metavar
55+
56+
if len(action.option_strings) == 1:
57+
return action.option_strings[0]
58+
59+
max_variations = 2
60+
if len(action.option_strings) == max_variations:
61+
# Account for a --1234 --long-option-name
62+
return f"{action.option_strings[0].ljust(6)} {action.option_strings[1]}"
63+
msg = "Too many option strings"
64+
raise ValueError(msg)
65+
66+
def add_arguments(self, actions: Iterable[argparse.Action]) -> None:
67+
"""Add arguments sorted by option strings.
68+
69+
Args:
70+
actions: The actions to add
71+
"""
72+
actions = sorted(actions, key=attrgetter("option_strings"))
73+
super().add_arguments(actions)
74+
75+
76+
class CustomArgumentParser(argparse.ArgumentParser):
77+
"""A custom argument parser."""
78+
79+
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
80+
"""Initialize the argument parser.
81+
82+
Args:
83+
*args: The arguments
84+
**kwargs: The keyword arguments
85+
"""
86+
super().__init__(*args, **kwargs)
87+
if sys.version_info < (3, 14): # pragma: no cover py>=3.14
88+
self.formatter_class = CustomHelpFormatter
89+
90+
def add_argument( # type: ignore[override]
91+
self,
92+
*args: Any, # noqa: ANN401
93+
**kwargs: Any, # noqa: ANN401
94+
) -> None:
95+
"""Add an argument.
96+
97+
Args:
98+
*args: The arguments
99+
**kwargs: The keyword arguments
100+
"""
101+
if sys.version_info < (3, 14): # pragma: no cover py>=3.14
102+
if "choices" in kwargs: # pragma: no cover py>=3.14
103+
kwargs["help"] += f" (choices: {', '.join(kwargs['choices'])})"
104+
if "default" in kwargs and kwargs["default"] != "==SUPPRESS==":
105+
kwargs["help"] += f" (default: {kwargs['default']})"
106+
super().add_argument(*args, **kwargs)
107+
108+
def add_argument_group(
109+
self,
110+
*args: Any, # noqa: ANN401
111+
**kwargs: Any, # noqa: ANN401
112+
) -> argparse._ArgumentGroup:
113+
"""Add an argument group.
114+
115+
Args:
116+
*args: The arguments
117+
**kwargs: The keyword arguments
118+
119+
Returns:
120+
The argument group
121+
"""
122+
group = super().add_argument_group(*args, **kwargs)
123+
if group.title: # pragma: no cover
124+
group.title = group.title.capitalize()
125+
return group

src/ansible_creator/arg_parser.py

Lines changed: 12 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,12 @@
77
import re
88
import sys
99

10-
from argparse import HelpFormatter
11-
from operator import attrgetter
1210
from pathlib import Path
1311
from typing import TYPE_CHECKING, TypeAlias
1412

1513
from ansible_creator.output import Level, Msg
1614

17-
18-
if TYPE_CHECKING:
19-
from collections.abc import Iterable
20-
from typing import Any
15+
from ._arg_parser_custom import CustomArgumentParser
2116

2217

2318
try:
@@ -40,64 +35,6 @@
4035
SubParser: TypeAlias = argparse._SubParsersAction # noqa: SLF001
4136

4237

43-
class CustomHelpFormatter(HelpFormatter): # pragma: no cover py>=3.14
44-
"""A custom help formatter."""
45-
46-
def __init__(self, prog: str) -> None:
47-
"""Initialize the help formatter.
48-
49-
Args:
50-
prog: The program name
51-
"""
52-
long_string = "--abc --really_really_really_log"
53-
# 3 here accounts for the spaces in the ljust(6) below
54-
HelpFormatter.__init__(
55-
self,
56-
prog=prog,
57-
indent_increment=1,
58-
max_help_position=len(long_string) + 3,
59-
)
60-
61-
def _format_action_invocation(
62-
self,
63-
action: argparse.Action,
64-
) -> str:
65-
"""Format the action invocation.
66-
67-
Args:
68-
action: The action to format
69-
70-
Raises:
71-
ValueError: If more than 2 options are given
72-
73-
Returns:
74-
The formatted action invocation
75-
"""
76-
if not action.option_strings: # pragma: no branch
77-
default = self._get_default_metavar_for_positional(action)
78-
(metavar,) = self._metavar_formatter(action, default)(1)
79-
return metavar
80-
81-
if len(action.option_strings) == 1:
82-
return action.option_strings[0]
83-
84-
max_variations = 2
85-
if len(action.option_strings) == max_variations:
86-
# Account for a --1234 --long-option-name
87-
return f"{action.option_strings[0].ljust(6)} {action.option_strings[1]}"
88-
msg = "Too many option strings"
89-
raise ValueError(msg)
90-
91-
def add_arguments(self, actions: Iterable[argparse.Action]) -> None:
92-
"""Add arguments sorted by option strings.
93-
94-
Args:
95-
actions: The actions to add
96-
"""
97-
actions = sorted(actions, key=attrgetter("option_strings"))
98-
super().add_arguments(actions)
99-
100-
10138
class Parser:
10239
"""A parser for the command line arguments."""
10340

@@ -835,6 +772,16 @@ def _init_ee_project(self, subparser: SubParser[argparse.ArgumentParser]) -> Non
835772
"Must end with .yml or .yaml. Default: execution-environment.yml",
836773
)
837774

775+
parser.add_argument(
776+
"--ee-build-arg-default",
777+
dest="ee_build_arg_defaults",
778+
action="append",
779+
default=[],
780+
metavar="KEY=VALUE",
781+
help="Default build ARG for the EE image (repeatable). "
782+
"Example: --ee-build-arg-default ANSIBLE_GALAXY_CLI_COLLECTION_OPTS=--pre",
783+
)
784+
838785
parser.add_argument(
839786
"--registry-tls-verify",
840787
dest="registry_tls_verify",
@@ -943,53 +890,4 @@ def handle_deprecations(self) -> bool: # noqa: C901
943890
return True
944891

945892

946-
class CustomArgumentParser(argparse.ArgumentParser):
947-
"""A custom argument parser."""
948-
949-
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
950-
"""Initialize the argument parser.
951-
952-
Args:
953-
*args: The arguments
954-
**kwargs: The keyword arguments
955-
"""
956-
super().__init__(*args, **kwargs)
957-
if sys.version_info < (3, 14): # pragma: no cover py>=3.14
958-
self.formatter_class = CustomHelpFormatter
959-
960-
def add_argument( # type: ignore[override]
961-
self,
962-
*args: Any, # noqa: ANN401
963-
**kwargs: Any, # noqa: ANN401
964-
) -> None:
965-
"""Add an argument.
966-
967-
Args:
968-
*args: The arguments
969-
**kwargs: The keyword arguments
970-
"""
971-
if sys.version_info < (3, 14): # pragma: no cover py>=3.14
972-
if "choices" in kwargs: # pragma: no cover py>=3.14
973-
kwargs["help"] += f" (choices: {', '.join(kwargs['choices'])})"
974-
if "default" in kwargs and kwargs["default"] != "==SUPPRESS==":
975-
kwargs["help"] += f" (default: {kwargs['default']})"
976-
super().add_argument(*args, **kwargs)
977-
978-
def add_argument_group(
979-
self,
980-
*args: Any, # noqa: ANN401
981-
**kwargs: Any, # noqa: ANN401
982-
) -> argparse._ArgumentGroup:
983-
"""Add an argument group.
984-
985-
Args:
986-
*args: The arguments
987-
**kwargs: The keyword arguments
988-
989-
Returns:
990-
The argument group
991-
"""
992-
group = super().add_argument_group(*args, **kwargs)
993-
if group.title: # pragma: no cover
994-
group.title = group.title.capitalize()
995-
return group
893+
__all__ = ["CustomArgumentParser", "Parser"]

src/ansible_creator/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Config:
4747
ee_system_packages: List of system packages for execution environment.
4848
ee_name: Name/tag for the execution environment image.
4949
ee_file_name: Name of the EE definition file.
50+
ee_build_arg_defaults: EE build ARG defaults as KEY=VALUE strings (from CLI).
5051
registry_tls_verify: Whether to verify TLS for container registry operations
5152
(login, pull, push, and image builds). None means the user did not
5253
explicitly set this flag, so the EE config file value is preserved.
@@ -80,6 +81,7 @@ class Config:
8081
ee_system_packages: Sequence[str] = field(default_factory=list)
8182
ee_name: str = "ansible_sample_ee"
8283
ee_file_name: str = "execution-environment.yml"
84+
ee_build_arg_defaults: Sequence[str] = field(default_factory=list)
8385
registry_tls_verify: bool | None = None
8486
scm_provider: str = "github"
8587

src/ansible_creator/resources/execution_env_project/execution-environment.yml.j2

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ version: 3
44
images:
55
base_image:
66
name: {{ ee_base_image }}
7+
{%- if ee_build_arg_defaults %}
8+
9+
build_arg_defaults:
10+
{%- for key, val in ee_build_arg_defaults | dictsort %}
11+
{{ key }}: {{ val | json }}
12+
{%- endfor %}
13+
{%- endif %}
714

815
dependencies:
916
{%- if not is_official_ee %}

0 commit comments

Comments
 (0)