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

pantsbuild / pants / 23559966294

25 Mar 2026 07:27PM UTC coverage: 92.882% (-0.04%) from 92.918%
23559966294

Pull #23133

github

web-flow
Merge b6f2381e0 into 9b3c1562e
Pull Request #23133: Add buildctl engine

289 of 337 new or added lines in 13 files covered. (85.76%)

2 existing lines in 2 files now uncovered.

91640 of 98663 relevant lines covered (92.88%)

4.06 hits per line

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

92.68
/src/python/pants/backend/docker/goals/publish.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
6✔
5

6
import logging
6✔
7
from collections import defaultdict
6✔
8
from dataclasses import dataclass
6✔
9
from itertools import chain
6✔
10
from typing import DefaultDict, cast
6✔
11

12
from pants.backend.docker.engine_types import DockerPushEngine
6✔
13
from pants.backend.docker.goals.package_image import (
6✔
14
    DockerPackageFieldSet,
15
    GetImageRefsRequest,
16
    get_docker_image_build_process,
17
    get_image_refs,
18
)
19
from pants.backend.docker.package_types import BuiltDockerImage
6✔
20
from pants.backend.docker.registries import DockerRegistryOptions
6✔
21
from pants.backend.docker.subsystems.docker_options import DockerOptions
6✔
22
from pants.backend.docker.target_types import DockerImageRegistriesField, DockerImageSkipPushField
6✔
23
from pants.backend.docker.util_rules.binaries import (
6✔
24
    DockerBinary,
25
    PodmanBinary,
26
    get_docker,
27
    get_podman,
28
)
29
from pants.core.goals.package import PackageFieldSet
6✔
30
from pants.core.goals.publish import (
6✔
31
    CheckSkipRequest,
32
    CheckSkipResult,
33
    PublishFieldSet,
34
    PublishOutputData,
35
    PublishPackages,
36
    PublishProcesses,
37
    PublishRequest,
38
)
39
from pants.core.util_rules.env_vars import environment_vars_subset
6✔
40
from pants.engine.env_vars import EnvironmentVarsRequest
6✔
41
from pants.engine.process import InteractiveProcess, Process
6✔
42
from pants.engine.rules import collect_rules, implicitly, rule
6✔
43
from pants.engine.unions import UnionRule
6✔
44

45
logger = logging.getLogger(__name__)
6✔
46

47

48
class PublishDockerImageRequest(PublishRequest):
6✔
49
    pass
6✔
50

51

52
@dataclass(frozen=True)
6✔
53
class PublishDockerImageFieldSet(PublishFieldSet, DockerPackageFieldSet):
6✔
54
    publish_request_type = PublishDockerImageRequest
6✔
55
    required_fields = (  # type: ignore[assignment]
6✔
56
        *DockerPackageFieldSet.required_fields,
57
        DockerImageRegistriesField,
58
    )
59

60
    skip_push: DockerImageSkipPushField
6✔
61

62
    def make_skip_request(
6✔
63
        self, package_fs: PackageFieldSet
64
    ) -> PublishDockerImageSkipRequest | None:
65
        return (
×
66
            PublishDockerImageSkipRequest(publish_fs=self, package_fs=package_fs)
67
            if isinstance(package_fs, DockerPackageFieldSet)
68
            else None
69
        )
70

71
    def get_output_data(self) -> PublishOutputData:
6✔
72
        return PublishOutputData(
1✔
73
            {
74
                "publisher": "docker",
75
                "registries": self.registries.value or (),
76
                **super().get_output_data(),
77
            }
78
        )
79

80

81
class PublishDockerImageSkipRequest(CheckSkipRequest[PublishDockerImageFieldSet]):
6✔
82
    package_fs: DockerPackageFieldSet
6✔
83

84

85
@rule
6✔
86
async def check_if_skip_push(
6✔
87
    request: PublishDockerImageSkipRequest, options: DockerOptions
88
) -> CheckSkipResult:
89
    skip_registries = {
1✔
90
        registry for registry in options.registries().registries.values() if registry.skip_push
91
    }
92
    if skip_registries or request.publish_fs.skip_push.value:
1✔
93
        image_refs = await get_image_refs(
1✔
94
            GetImageRefsRequest(field_set=request.package_fs, build_upstream_images=False),
95
            **implicitly(),
96
        )
97
        if request.publish_fs.skip_push.value:
1✔
98
            return CheckSkipResult.skip(
1✔
99
                names=[tag.full_name for registry in image_refs for tag in registry.tags],
100
                description=f"(by `{request.publish_fs.skip_push.alias}` on {request.address})",
101
                data=request.publish_fs.get_output_data(),
102
            )
103
        if all(image_ref.registry in skip_registries for image_ref in image_refs):
1✔
104
            output_data = request.publish_fs.get_output_data()
1✔
105
            return CheckSkipResult(
1✔
106
                PublishPackages(
107
                    names=tuple(tag.full_name for tag in image_ref.tags),
108
                    description=f"(by skip_push on @{cast(DockerRegistryOptions, image_ref.registry).alias})",
109
                    data=output_data,
110
                )
111
                for image_ref in image_refs
112
            )
113
    return (
1✔
114
        CheckSkipResult.skip(skip_packaging_only=True)
115
        if request.package_fs.pushes_on_package()
116
        else CheckSkipResult.no_skip()
117
    )
118

119

120
@rule
6✔
121
async def push_docker_images(
6✔
122
    request: PublishDockerImageRequest,
123
    options: DockerOptions,
124
    options_env_aware: DockerOptions.EnvironmentAware,
125
) -> PublishProcesses:
126
    if cast(DockerPackageFieldSet, request.field_set).pushes_on_package():
1✔
127
        build_process = await get_docker_image_build_process(request.field_set, **implicitly())
1✔
128
        return PublishProcesses(
1✔
129
            [
130
                PublishPackages(
131
                    names=build_process.tags,
132
                    process=build_process.process
133
                    if options.publish_noninteractively
134
                    else InteractiveProcess.from_process(build_process.process),
135
                )
136
            ]
137
        )
138

139
    tags = tuple(
1✔
140
        chain.from_iterable(
141
            cast(BuiltDockerImage, image).tags
142
            for pkg in request.packages
143
            for image in pkg.artifacts
144
        )
145
    )
146

147
    env = await environment_vars_subset(
1✔
148
        EnvironmentVarsRequest(options_env_aware.env_vars), **implicitly()
149
    )
150
    skip_push_reasons: DefaultDict[str, DefaultDict[str, set[str]]] = defaultdict(
1✔
151
        lambda: defaultdict(set)
152
    )
153
    jobs: list[PublishPackages] = []
1✔
154
    refs: list[str] = []
1✔
155
    processes: list[Process | InteractiveProcess] = []
1✔
156
    binary: DockerBinary | PodmanBinary
157
    match options.push_engine:
1✔
158
        case DockerPushEngine.DOCKER:
1✔
159
            binary = await get_docker(**implicitly())
1✔
NEW
160
        case DockerPushEngine.PODMAN:
×
NEW
161
            binary = await get_podman(**implicitly())
×
162

163
    for tag in tags:
1✔
164
        for registry in options.registries().registries.values():
1✔
165
            if registry.skip_push and tag.startswith(f"{registry.address}/"):
1✔
166
                skip_push_reasons["skip_push"][registry.alias].add(tag)
1✔
167
                break
1✔
168
            if registry.use_local_alias and tag.startswith(f"{registry.alias}/"):
1✔
169
                skip_push_reasons["use_local_alias"][registry.alias].add(tag)
×
170
                break
×
171
        else:
172
            refs.append(tag)
1✔
173
            push_process = binary.push_image(tag, env)
1✔
174
            if options.publish_noninteractively:
1✔
175
                processes.append(push_process)
×
176
            else:
177
                processes.append(InteractiveProcess.from_process(push_process))
1✔
178

179
    for ref, process in zip(refs, processes):
1✔
180
        jobs.append(
1✔
181
            PublishPackages(
182
                names=(ref,),
183
                process=process,
184
            )
185
        )
186

187
    for reason, skip_push in skip_push_reasons.items():
1✔
188
        for name, skip_tags in skip_push.items():
1✔
189
            jobs.append(
1✔
190
                PublishPackages(
191
                    names=tuple(skip_tags),
192
                    description=f"(by `{reason}` on registry @{name})",
193
                ),
194
            )
195

196
    return PublishProcesses(jobs)
1✔
197

198

199
def rules():
6✔
200
    return (
5✔
201
        *collect_rules(),
202
        *PublishDockerImageFieldSet.rules(),
203
        UnionRule(CheckSkipRequest, PublishDockerImageSkipRequest),
204
    )
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