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

pantsbuild / pants / 18252174847

05 Oct 2025 01:36AM UTC coverage: 43.382% (-36.9%) from 80.261%
18252174847

push

github

web-flow
run tests on mac arm (#22717)

Just doing the minimal to pull forward the x86_64 pattern.

ref #20993

25776 of 59416 relevant lines covered (43.38%)

1.3 hits per line

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

51.09
/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).
3
from __future__ import annotations
3✔
4

5
from dataclasses import dataclass
3✔
6

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

39

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

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

51
    def to_args(self) -> TerraformCommand:
3✔
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

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

77

78
@rule
3✔
79
async def get_terraform_providers(
3✔
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

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

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

115

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

120
    terraform_sources: SourceFiles
3✔
121
    dependencies_files: SourceFiles
3✔
122
    init_cmd: TerraformDependenciesRequest
3✔
123
    chdir: str
3✔
124

125

126
def terraform_fieldset_to_init_request(
3✔
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

145
@rule
3✔
146
async def prepare_terraform_invocation(
3✔
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

235
def rules():
3✔
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