• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 19477274756

18 Nov 2025 06:38PM UTC coverage: 80.291% (+0.001%) from 80.29%
19477274756

Pull #22889

github

web-flow
Merge f14bb9a80 into a217a6c34
Pull Request #22889: Java fix bug with inner class inference

21 of 27 new or added lines in 2 files covered. (77.78%)

267 existing lines in 10 files now uncovered.

78098 of 97269 relevant lines covered (80.29%)

3.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.49
/src/python/pants/backend/helm/subsystems/post_renderer.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
7✔
5

6
import logging
7✔
7
import os
7✔
8
import pkgutil
7✔
9
import shlex
7✔
10
from collections.abc import Iterable, Mapping
7✔
11
from dataclasses import dataclass
7✔
12
from pathlib import PurePath
7✔
13
from textwrap import dedent  # noqa: PNT20
7✔
14
from typing import Any
7✔
15

16
import yaml
7✔
17

18
from pants.backend.helm.utils.yaml import FrozenYamlIndex
7✔
19
from pants.backend.python.subsystems.python_tool_base import PythonToolRequirementsBase
7✔
20
from pants.backend.python.target_types import EntryPoint
7✔
21
from pants.backend.python.util_rules import pex
7✔
22
from pants.backend.python.util_rules.pex import (
7✔
23
    VenvPex,
24
    VenvPexProcess,
25
    create_venv_pex,
26
    setup_venv_pex_process,
27
)
28
from pants.core.goals.run import RunFieldSet, RunRequest, generate_run_request
7✔
29
from pants.core.util_rules.system_binaries import CatBinary
7✔
30
from pants.engine.addresses import UnparsedAddressInputs
7✔
31
from pants.engine.engine_aware import EngineAwareParameter, EngineAwareReturnType
7✔
32
from pants.engine.fs import CreateDigest, Digest, FileContent
7✔
33
from pants.engine.internals.graph import find_valid_field_sets, resolve_targets
7✔
34
from pants.engine.internals.native_engine import MergeDigests
7✔
35
from pants.engine.intrinsics import create_digest, merge_digests
7✔
36
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
7✔
37
from pants.engine.target import FieldSetsPerTargetRequest
7✔
38
from pants.util.frozendict import FrozenDict
7✔
39
from pants.util.logging import LogLevel
7✔
40
from pants.util.strutil import bullet_list, pluralize, softwrap
7✔
41

42
logger = logging.getLogger(__name__)
7✔
43

44
_HELM_POSTRENDERER_SOURCE = "post_renderer_main.py"
7✔
45
_HELM_POSTRENDERER_PACKAGE = "pants.backend.helm.subsystems"
7✔
46

47

48
class HelmPostRendererSubsystem(PythonToolRequirementsBase):
7✔
49
    options_scope = "helm-post-renderer"
7✔
50
    help_short = "Used perform modifications to the final output produced by Helm charts when they've been fully rendered."
7✔
51

52
    default_requirements = [
7✔
53
        "yamlpath>=3.6.0,<4",
54
        "ruamel.yaml>=0.15.96,!=0.17.0,!=0.17.1,!=0.17.2,!=0.17.5,<=0.17.21",
55
    ]
56

57
    register_interpreter_constraints = True
7✔
58

59
    default_lockfile_resource = (_HELM_POSTRENDERER_PACKAGE, "post_renderer.lock")
7✔
60

61

62
_HELM_POST_RENDERER_TOOL = "__pants_helm_post_renderer.py"
7✔
63

64

65
@dataclass(frozen=True)
7✔
66
class _HelmPostRendererTool:
7✔
67
    pex: VenvPex
7✔
68

69

70
@rule(desc="Setup Helm post renderer binaries", level=LogLevel.DEBUG)
7✔
71
async def setup_post_renderer_tool(
7✔
72
    post_renderer: HelmPostRendererSubsystem,
73
) -> _HelmPostRendererTool:
UNCOV
74
    post_renderer_sources = pkgutil.get_data(_HELM_POSTRENDERER_PACKAGE, _HELM_POSTRENDERER_SOURCE)
×
75
    if not post_renderer_sources:
×
76
        raise ValueError(
×
77
            f"Unable to find source to {_HELM_POSTRENDERER_SOURCE!r} in {_HELM_POSTRENDERER_PACKAGE}"
78
        )
79

UNCOV
80
    post_renderer_content = FileContent(
×
81
        path=_HELM_POST_RENDERER_TOOL, content=post_renderer_sources, is_executable=True
82
    )
UNCOV
83
    post_renderer_digest = await create_digest(CreateDigest([post_renderer_content]))
×
84

UNCOV
85
    post_renderer_pex = await create_venv_pex(
×
86
        **implicitly(
87
            post_renderer.to_pex_request(
88
                main=EntryPoint(PurePath(post_renderer_content.path).stem),
89
                sources=post_renderer_digest,
90
            )
91
        )
92
    )
UNCOV
93
    return _HelmPostRendererTool(post_renderer_pex)
×
94

95

96
HELM_POST_RENDERER_CFG_FILENAME = "post_renderer.cfg.yaml"
7✔
97
_HELM_POST_RENDERER_WRAPPER_SCRIPT = "post_renderer_wrapper.sh"
7✔
98

99

100
@dataclass(frozen=True)
7✔
101
class SetupHelmPostRenderer(EngineAwareParameter):
7✔
102
    """Request for a post-renderer process that will perform a series of replacements in the
103
    generated files."""
104

105
    replacements: FrozenYamlIndex[str]
7✔
106
    description_of_origin: str
7✔
107
    extra_post_renderers: UnparsedAddressInputs | None = None
7✔
108

109
    def debug_hint(self) -> str | None:
7✔
UNCOV
110
        return self.description_of_origin
×
111

112

113
@dataclass(frozen=True)
7✔
114
class HelmPostRenderer(EngineAwareReturnType):
7✔
115
    exe: str
7✔
116
    digest: Digest
7✔
117
    immutable_input_digests: FrozenDict[str, Digest]
7✔
118
    env: FrozenDict[str, str]
7✔
119
    append_only_caches: FrozenDict[str, str]
7✔
120
    description_of_origin: str
7✔
121

122
    def __init__(
7✔
123
        self,
124
        *,
125
        exe: str,
126
        digest: Digest,
127
        description_of_origin: str,
128
        env: Mapping[str, str] | None = None,
129
        immutable_input_digests: Mapping[str, Digest] | None = None,
130
        append_only_caches: Mapping[str, str] | None = None,
131
    ) -> None:
UNCOV
132
        object.__setattr__(self, "exe", exe)
×
133
        object.__setattr__(self, "digest", digest)
×
134
        object.__setattr__(self, "description_of_origin", description_of_origin)
×
135
        object.__setattr__(self, "env", FrozenDict(env or {}))
×
136
        object.__setattr__(self, "append_only_caches", FrozenDict(append_only_caches or {}))
×
137
        object.__setattr__(
×
138
            self, "immutable_input_digests", FrozenDict(immutable_input_digests or {})
139
        )
140

141
    def level(self) -> LogLevel | None:
7✔
UNCOV
142
        return LogLevel.DEBUG
×
143

144
    def message(self) -> str | None:
7✔
UNCOV
145
        return f"runnable {self.exe} for {self.description_of_origin} is ready."
×
146

147
    def metadata(self) -> dict[str, Any] | None:
7✔
UNCOV
148
        return {
×
149
            "exe": self.exe,
150
            "env": self.env,
151
            "append_only_caches": self.append_only_caches,
152
            "description_of_origin": self.description_of_origin,
153
        }
154

155

156
async def _resolve_post_renderers(
7✔
157
    address_inputs: UnparsedAddressInputs,
158
) -> Iterable[RunRequest]:
UNCOV
159
    logger.debug(
×
160
        softwrap(
161
            f"""
162
            Resolving {pluralize(len(address_inputs.values), "post-renderer")} from {address_inputs.description_of_origin}:
163

164
            {bullet_list(address_inputs.values, 5)}
165
            """
166
        )
167
    )
168

UNCOV
169
    targets = await resolve_targets(**implicitly({address_inputs: UnparsedAddressInputs}))
×
170
    field_sets_per_target = await find_valid_field_sets(
×
171
        FieldSetsPerTargetRequest(RunFieldSet, targets),
172
        **implicitly(),
173
    )
UNCOV
174
    return await concurrently(
×
175
        generate_run_request(**implicitly({field_set: RunFieldSet}))
176
        for field_set in field_sets_per_target.field_sets
177
    )
178

179

180
@rule(desc="Configure Helm post-renderer", level=LogLevel.DEBUG)
7✔
181
async def setup_post_renderer_launcher(
7✔
182
    request: SetupHelmPostRenderer,
183
    post_renderer_tool: _HelmPostRendererTool,
184
    cat_binary: CatBinary,
185
) -> HelmPostRenderer:
186
    # Build post-renderer configuration file and create a digest containing it.
UNCOV
187
    post_renderer_config = yaml.safe_dump(
×
188
        request.replacements.to_json_dict(), explicit_start=True, sort_keys=True
189
    )
UNCOV
190
    post_renderer_cfg_digest = await create_digest(
×
191
        CreateDigest(
192
            [
193
                FileContent(HELM_POST_RENDERER_CFG_FILENAME, post_renderer_config.encode("utf-8")),
194
            ]
195
        )
196
    )
197

198
    # Generate a temporary PEX process that uses the previously created configuration file.
UNCOV
199
    post_renderer_cfg_file = os.path.join(".", HELM_POST_RENDERER_CFG_FILENAME)
×
200
    post_renderer_input_file = os.path.join(".", "__helm_stdout.yaml")
×
201
    post_renderer_process = await setup_venv_pex_process(
×
202
        VenvPexProcess(
203
            post_renderer_tool.pex,
204
            argv=[post_renderer_cfg_file, post_renderer_input_file],
205
            input_digest=post_renderer_cfg_digest,
206
            description="",
207
        ),
208
        **implicitly(),
209
    )
210

UNCOV
211
    def shell_cmd(args: Iterable[str]) -> str:
×
212
        return shlex.join(args)
×
213

214
    # Build a shell wrapper script which will be the actual entry-point sent to Helm as the post-renderer.
215
    # Extra post-renderers are plugged by piping the output of one into the next one in the order they
216
    # have been defined.
UNCOV
217
    extra_post_renderers = (
×
218
        await _resolve_post_renderers(request.extra_post_renderers)
219
        if request.extra_post_renderers
220
        else []
221
    )
UNCOV
222
    post_renderer_process_cli = " | ".join(
×
223
        [
224
            shell_cmd(post_renderer_process.argv),
225
            *[shell_cmd(post_renderer.args) for post_renderer in extra_post_renderers],
226
        ]
227
    )
UNCOV
228
    logger.debug(
×
229
        f'Using post-renderer pipeline "{post_renderer_process_cli}" in {request.description_of_origin}.'
230
    )
231

UNCOV
232
    postrenderer_wrapper_script = dedent(
×
233
        f"""\
234
        #!/bin/bash
235

236
        # Output stdin into a file in disk
237
        {cat_binary.path} <&0 > {post_renderer_input_file}
238

239
        {post_renderer_process_cli}
240
        """
241
    )
UNCOV
242
    wrapper_digest = await create_digest(
×
243
        CreateDigest(
244
            [
245
                FileContent(
246
                    _HELM_POST_RENDERER_WRAPPER_SCRIPT,
247
                    postrenderer_wrapper_script.encode("utf-8"),
248
                    is_executable=True,
249
                ),
250
            ]
251
        ),
252
    )
253

254
    # Combine all required settings for the internal and extra post-renderers
UNCOV
255
    launcher_digest = await merge_digests(
×
256
        MergeDigests(
257
            [
258
                wrapper_digest,
259
                post_renderer_process.input_digest,
260
                *[post_renderer.digest for post_renderer in extra_post_renderers],
261
            ]
262
        ),
263
    )
UNCOV
264
    launcher_env = {
×
265
        **post_renderer_process.env,
266
        **{
267
            k: v
268
            for post_renderer in extra_post_renderers
269
            for k, v in post_renderer.extra_env.items()
270
        },
271
    }
UNCOV
272
    launcher_append_only_caches = {
×
273
        **post_renderer_process.append_only_caches,
274
        **{
275
            k: v
276
            for post_renderer in extra_post_renderers
277
            for k, v in (post_renderer.append_only_caches or {}).items()
278
        },
279
    }
UNCOV
280
    launcher_immutable_input_digests = {
×
281
        **post_renderer_process.immutable_input_digests,
282
        **{
283
            k: v
284
            for post_renderer in extra_post_renderers
285
            for k, v in (post_renderer.immutable_input_digests or {}).items()
286
        },
287
    }
288

UNCOV
289
    return HelmPostRenderer(
×
290
        exe=_HELM_POST_RENDERER_WRAPPER_SCRIPT,
291
        digest=launcher_digest,
292
        env=launcher_env,
293
        append_only_caches=launcher_append_only_caches,
294
        immutable_input_digests=launcher_immutable_input_digests,
295
        description_of_origin=request.description_of_origin,
296
    )
297

298

299
def rules():
7✔
300
    return [
7✔
301
        *collect_rules(),
302
        *pex.rules(),
303
    ]
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc