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

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

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

0.0
/src/python/pants/backend/terraform/dependencies.py
1
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
UNCOV
3
from __future__ import annotations
×
4

UNCOV
5
from dataclasses import dataclass
×
6

UNCOV
7
from pants.backend.terraform.dependency_inference import (
×
8
    TerraformDeploymentInvocationFilesRequest,
9
    get_terraform_backend_and_vars,
10
)
UNCOV
11
from pants.backend.terraform.target_types import (
×
12
    LockfileSourceField,
13
    TerraformBackendConfigField,
14
    TerraformDependenciesField,
15
    TerraformDeploymentFieldSet,
16
    TerraformFieldSet,
17
    TerraformModuleSourcesField,
18
    TerraformRootModuleField,
19
    TerraformVarFileSourceField,
20
)
UNCOV
21
from pants.backend.terraform.tool import TerraformCommand, TerraformProcess
×
UNCOV
22
from pants.backend.terraform.utils import terraform_arg, terraform_relpath
×
UNCOV
23
from pants.core.target_types import FileSourceField, ResourceSourceField
×
UNCOV
24
from pants.core.util_rules.source_files import (
×
25
    SourceFiles,
26
    SourceFilesRequest,
27
    determine_source_files,
28
)
UNCOV
29
from pants.engine.internals.build_files import resolve_address
×
UNCOV
30
from pants.engine.internals.graph import resolve_target, transitive_targets
×
UNCOV
31
from pants.engine.internals.native_engine import Digest, MergeDigests
×
UNCOV
32
from pants.engine.internals.selectors import concurrently
×
UNCOV
33
from pants.engine.intrinsics import execute_process, merge_digests
×
UNCOV
34
from pants.engine.process import ProcessExecutionFailure
×
UNCOV
35
from pants.engine.rules import collect_rules, implicitly, rule
×
UNCOV
36
from pants.engine.target import SourcesField, TransitiveTargetsRequest, WrappedTargetRequest
×
UNCOV
37
from pants.option.global_options import KeepSandboxes
×
38

39

UNCOV
40
@dataclass(frozen=True)
×
UNCOV
41
class TerraformDependenciesRequest:
×
UNCOV
42
    chdir: str
×
UNCOV
43
    backend_config: str | None
×
UNCOV
44
    lockfile: bool
×
UNCOV
45
    dependencies_files: Digest
×
46

47
    # Not initialising the backend means we won't access remote state. Useful for `validate`
UNCOV
48
    initialise_backend: bool = False
×
UNCOV
49
    upgrade: bool = False
×
50

UNCOV
51
    def to_args(self) -> TerraformCommand:
×
52
        args = ["init"]
×
53
        if self.backend_config:
×
54
            args.append(
×
55
                terraform_arg(
56
                    "-backend-config",
57
                    terraform_relpath(self.chdir, self.backend_config),
58
                )
59
            )
60

61
        # If we have a lockfile and aren't regenerating it, don't modify it
62
        if self.lockfile and not self.upgrade:
×
63
            args.append("-lockfile=readonly")
×
64

65
        if self.upgrade:
×
66
            args.append("-upgrade")
×
67

68
        args.append(terraform_arg("-backend", str(self.initialise_backend)))
×
69

70
        return TerraformCommand(tuple(args))
×
71

72

UNCOV
73
@dataclass(frozen=True)
×
UNCOV
74
class TerraformDependenciesResponse:
×
UNCOV
75
    digest: Digest
×
76

77

UNCOV
78
@rule
×
UNCOV
79
async def get_terraform_providers(
×
80
    req: TerraformDependenciesRequest,
81
    keep_sandboxes: KeepSandboxes,
82
) -> TerraformDependenciesResponse:
83
    init_process_description = (
×
84
        f"Running `init` on Terraform module at `{req.chdir}` to fetch dependencies"
85
    )
86
    fetched_deps = await execute_process(
×
87
        **implicitly(
88
            TerraformProcess(
89
                cmds=(req.to_args(),),
90
                input_digest=req.dependencies_files,
91
                output_files=(".terraform.lock.hcl",),
92
                output_directories=(".terraform",),
93
                description=init_process_description,
94
                chdir=req.chdir,
95
            )
96
        )
97
    )
98
    if fetched_deps.exit_code != 0:
×
99
        raise ProcessExecutionFailure.from_result(
×
100
            fetched_deps, init_process_description, keep_sandboxes
101
        )
102

103
    return TerraformDependenciesResponse(fetched_deps.output_digest)
×
104

105

UNCOV
106
@dataclass(frozen=True)
×
UNCOV
107
class TerraformInitRequest:
×
UNCOV
108
    root_module: TerraformRootModuleField
×
UNCOV
109
    dependencies: TerraformDependenciesField
×
110

111
    # Not initialising the backend means we won't access remote state. Useful for `validate`
UNCOV
112
    initialise_backend: bool = False
×
UNCOV
113
    upgrade: bool = False
×
114

115

UNCOV
116
@dataclass(frozen=True)
×
UNCOV
117
class TerraformInvocationRequirements:
×
118
    """The things you need to run a terraform command."""
119

UNCOV
120
    terraform_sources: SourceFiles
×
UNCOV
121
    dependencies_files: SourceFiles
×
UNCOV
122
    init_cmd: TerraformDependenciesRequest
×
UNCOV
123
    chdir: str
×
124

125

UNCOV
126
def terraform_fieldset_to_init_request(
×
127
    terraform_fieldset: TerraformDeploymentFieldSet | TerraformFieldSet,
128
) -> TerraformInitRequest:
129
    """Create a TerraformInitRequest from both Terraform Modules and Deployments."""
130
    if isinstance(terraform_fieldset, TerraformDeploymentFieldSet):
×
131
        deployment = terraform_fieldset
×
132
        return TerraformInitRequest(deployment.root_module, deployment.dependencies)
×
133
    elif isinstance(terraform_fieldset, TerraformFieldSet):
×
134
        module = terraform_fieldset
×
135
        return TerraformInitRequest(
×
136
            TerraformRootModuleField(module.address.spec, module.address),
137
            module.dependencies,
138
        )
139
    else:
140
        raise TypeError(
×
141
            f"Invalid type passed to initialise terraform tpye={type(terraform_fieldset)}"
142
        )
143

144

UNCOV
145
@rule
×
UNCOV
146
async def prepare_terraform_invocation(
×
147
    request: TerraformInitRequest,
148
) -> TerraformInvocationRequirements:
149
    """Prepare a terraform module or deployment to be operated on."""
150
    this_targets_dependencies = await transitive_targets(
×
151
        TransitiveTargetsRequest((request.dependencies.address,)), **implicitly()
152
    )
153

154
    address_input = request.root_module.to_address_input()
×
155
    module_address = await resolve_address(**implicitly(address_input))
×
156

157
    chdir = module_address.spec_path  # TODO: spec_path is wrong, that's to the build file
×
158
    # if the Terraform module is in the root, chdir will be "". Terraform needs a valid dir to change to
159
    if not chdir:
×
160
        chdir = "."
×
161

162
    # TODO: is this still necessary, or do we pull it in with (transitive) dependencies?
163
    module = await resolve_target(
×
164
        WrappedTargetRequest(
165
            module_address, description_of_origin=address_input.description_of_origin
166
        ),
167
        **implicitly(),
168
    )
169

170
    source_files, dependencies_files = await concurrently(
×
171
        determine_source_files(
172
            SourceFilesRequest([module.target.get(SourcesField)])
173
        ),  # TODO: get through transitive deps???
174
        determine_source_files(
175
            SourceFilesRequest(
176
                [tgt.get(SourcesField) for tgt in this_targets_dependencies.dependencies],
177
                for_sources_types=(
178
                    TerraformModuleSourcesField,
179
                    TerraformBackendConfigField,
180
                    TerraformVarFileSourceField,
181
                    LockfileSourceField,
182
                    FileSourceField,
183
                    ResourceSourceField,
184
                ),
185
                enable_codegen=True,
186
            )
187
        ),
188
    )
189
    invocation_files = await get_terraform_backend_and_vars(
×
190
        TerraformDeploymentInvocationFilesRequest(
191
            request.dependencies.address, request.dependencies
192
        )
193
    )
194
    backend_config_tgts = invocation_files.backend_configs
×
195
    if len(backend_config_tgts) == 0:
×
196
        backend_config = None
×
197
    elif len(backend_config_tgts) == 1:
×
198
        backend_config_sources = await determine_source_files(
×
199
            SourceFilesRequest([backend_config_tgts[0].get(SourcesField)])
200
        )
201
        backend_config = backend_config_sources.snapshot.files[0]
×
202
    else:
203
        # We've found multiple backend files, but that's only a problem if we need to initialise the backend.
204
        # For example, we might be `validate`ing a `terraform_module` that has multiple backend files in the same dir,
205
        # so we don't need to init the backend.
206
        # The `terraform_deployment`s will have the references to the correct backends
207

208
        if request.initialise_backend:
×
209
            backend_config_names = [e.address for e in backend_config_tgts]
×
210
            raise ValueError(
×
211
                f"Found more than 1 backend config for a Terraform deployment. identified {backend_config_names}"
212
            )
213
        else:
214
            backend_config = None
×
215

216
    source_for_validate = await merge_digests(
×
217
        MergeDigests([source_files.snapshot.digest, dependencies_files.snapshot.digest])
218
    )
219

220
    has_lockfile = invocation_files.lockfile is not None
×
221

222
    terraform_init_cmd = TerraformDependenciesRequest(
×
223
        chdir,
224
        backend_config,
225
        has_lockfile,
226
        source_for_validate,
227
        initialise_backend=request.initialise_backend,
228
        upgrade=request.upgrade,
229
    )
230
    return TerraformInvocationRequirements(
×
231
        source_files, dependencies_files, terraform_init_cmd, chdir
232
    )
233

234

UNCOV
235
def rules():
×
UNCOV
236
    return collect_rules()
×
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