We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Make the search path for Docker configurable
Currently, the `docker` binary is searched for in a hard coded set of paths. If your binary is elsewhere, you're out of luck.

Introduce a new option to specify search path. Model after https://www.pantsbuild.org/docs/reference-shell-setup#section-executable-search-paths


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/python/pants/backend/docker/util_rules/docker_binary.py`
Content:
```
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
5 
6 from dataclasses import dataclass
7 from typing import Mapping
8 
9 from pants.backend.docker.util_rules.docker_build_args import DockerBuildArgs
10 from pants.engine.fs import Digest
11 from pants.engine.process import (
12     BinaryNotFoundError,
13     BinaryPath,
14     BinaryPathRequest,
15     BinaryPaths,
16     BinaryPathTest,
17     Process,
18     ProcessCacheScope,
19     SearchPath,
20 )
21 from pants.engine.rules import Get, collect_rules, rule
22 from pants.util.logging import LogLevel
23 from pants.util.strutil import pluralize
24 
25 
26 class DockerBinary(BinaryPath):
27     """The `docker` binary."""
28 
29     DEFAULT_SEARCH_PATH = SearchPath(("/usr/bin", "/bin", "/usr/local/bin"))
30 
31     def build_image(
32         self,
33         tags: tuple[str, ...],
34         digest: Digest,
35         dockerfile: str,
36         build_args: DockerBuildArgs,
37         context_root: str,
38         env: Mapping[str, str],
39         extra_args: tuple[str, ...] = (),
40     ) -> Process:
41         args = [self.path, "build", *extra_args]
42 
43         for tag in tags:
44             args.extend(["--tag", tag])
45 
46         for build_arg in build_args:
47             args.extend(["--build-arg", build_arg])
48 
49         args.extend(["--file", dockerfile])
50 
51         # Docker context root.
52         args.append(context_root)
53 
54         return Process(
55             argv=tuple(args),
56             description=(
57                 f"Building docker image {tags[0]}"
58                 + (f" +{pluralize(len(tags)-1, 'additional tag')}." if len(tags) > 1 else "")
59             ),
60             env=env,
61             input_digest=digest,
62             cache_scope=ProcessCacheScope.PER_SESSION,
63         )
64 
65     def push_image(
66         self, tags: tuple[str, ...], env: Mapping[str, str] | None = None
67     ) -> tuple[Process, ...]:
68         return tuple(
69             Process(
70                 argv=(self.path, "push", tag),
71                 cache_scope=ProcessCacheScope.PER_SESSION,
72                 description=f"Pushing docker image {tag}",
73                 env=env,
74             )
75             for tag in tags
76         )
77 
78     def run_image(
79         self,
80         tag: str,
81         *,
82         docker_run_args: tuple[str, ...] | None = None,
83         image_args: tuple[str, ...] | None = None,
84         env: Mapping[str, str] | None = None,
85     ) -> Process:
86         return Process(
87             argv=(self.path, "run", *(docker_run_args or []), tag, *(image_args or [])),
88             cache_scope=ProcessCacheScope.PER_SESSION,
89             description=f"Running docker image {tag}",
90             env=env,
91         )
92 
93 
94 @dataclass(frozen=True)
95 class DockerBinaryRequest:
96     search_path: SearchPath = DockerBinary.DEFAULT_SEARCH_PATH
97 
98 
99 @rule(desc="Finding the `docker` binary", level=LogLevel.DEBUG)
100 async def find_docker(docker_request: DockerBinaryRequest) -> DockerBinary:
101     request = BinaryPathRequest(
102         binary_name="docker",
103         search_path=docker_request.search_path,
104         test=BinaryPathTest(args=["-v"]),
105     )
106     paths = await Get(BinaryPaths, BinaryPathRequest, request)
107     first_path = paths.first_path
108     if not first_path:
109         raise BinaryNotFoundError.from_request(request, rationale="interact with the docker daemon")
110     return DockerBinary(first_path.path, first_path.fingerprint)
111 
112 
113 @rule
114 async def get_docker() -> DockerBinary:
115     return await Get(DockerBinary, DockerBinaryRequest())
116 
117 
118 def rules():
119     return collect_rules()
120
```

Path: `src/python/pants/backend/docker/subsystems/docker_options.py`
Content:
```
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
5 
6 import sys
7 from textwrap import dedent
8 from typing import cast
9 
10 from pants.backend.docker.registries import DockerRegistries
11 from pants.option.custom_types import shell_str
12 from pants.option.errors import ParseError
13 from pants.option.subsystem import Subsystem
14 from pants.util.memo import memoized_method
15 from pants.util.strutil import bullet_list
16 
17 doc_links = {
18     "docker_env_vars": (
19         "https://docs.docker.com/engine/reference/commandline/cli/#environment-variables"
20     ),
21 }
22 
23 
24 def workspace_path(s: str) -> str:
25     """Same type as 'str', but indicates string represents a directory path that is relative to
26     either the build root, or a BUILD file if prefix with `./`.
27 
28     :API: public
29     """
30     if s.startswith("/"):
31         raise ParseError(
32             f"Invalid value: `{s}`. Expected a relative path, optionally in the form "
33             "`./relative/path` to make it relative to the BUILD files rather than the build root."
34         )
35     return s
36 
37 
38 class DockerOptions(Subsystem):
39     options_scope = "docker"
40     help = "Options for interacting with Docker."
41 
42     @classmethod
43     def register_options(cls, register):
44         registries_help = (
45             dedent(
46                 """\
47                 Configure Docker registries. The schema for a registry entry is as follows:
48 
49                     {
50                         "registry-alias": {
51                             "address": "registry-domain:port",
52                             "default": bool,
53                         },
54                         ...
55                     }
56 
57                 """
58             )
59             + (
60                 "If no registries are provided in a `docker_image` target, then all default "
61                 "addresses will be used, if any.\n"
62                 "The `docker_image.registries` may be provided with a list of registry addresses "
63                 "and registry aliases prefixed with `@` to be used instead of the defaults.\n"
64                 "A configured registry is marked as default either by setting `default = true` "
65                 'or with an alias of `"default"`.'
66             )
67         )
68         default_repository_help = (
69             "Configure the default repository name used in the Docker image tag.\n\n"
70             "The value is formatted and may reference these variables (in addition to the normal "
71             "placeheolders derived from the Dockerfile and build args etc):\n\n"
72             + bullet_list(["name", "directory", "parent_directory"])
73             + "\n\n"
74             'Example: `--default-repository="{directory}/{name}"`.\n\n'
75             "The `name` variable is the `docker_image`'s target name, `directory` and "
76             "`parent_directory` are the name of the directory in which the BUILD file is for the "
77             "target, and its parent directory respectively.\n\n"
78             "Use the `repository` field to set this value directly on a `docker_image` "
79             "target.\nAny registries or tags are added to the image name as required, and should "
80             "not be part of the repository name."
81         )
82         super().register_options(register)
83         register("--registries", type=dict, fromfile=True, help=registries_help)
84         register(
85             "--default-repository",
86             type=str,
87             help=default_repository_help,
88             default="{name}",
89         )
90 
91         register(
92             "--default-context-root",
93             type=workspace_path,
94             default="",
95             help=(
96                 "Provide a default Docker build context root path for `docker_image` targets that "
97                 "does not specify their own `context_root` field.\n\n"
98                 "The context root is relative to the build root by default, but may be prefixed "
99                 "with `./` to be relative to the directory of the BUILD file of the `docker_image`."
100                 "\n\nExamples:\n\n"
101                 "    --default-context-root=src/docker\n"
102                 "    --default-context-root=./relative_to_the_build_file\n"
103             ),
104         )
105 
106         register(
107             "--build-args",
108             type=list,
109             member_type=shell_str,
110             default=[],
111             help=(
112                 "Global build arguments (for Docker `--build-arg` options) to use for all "
113                 "`docker build` invocations.\n\n"
114                 "Entries are either strings in the form `ARG_NAME=value` to set an explicit value; "
115                 "or just `ARG_NAME` to copy the value from Pants's own environment.\n\n"
116                 + dedent(
117                     f"""\
118                     Example:
119 
120                         [{cls.options_scope}]
121                         build_args = ["VAR1=value", "VAR2"]
122 
123                     """
124                 )
125                 + "Use the `extra_build_args` field on a `docker_image` target for additional "
126                 "image specific build arguments."
127             ),
128         )
129 
130         register(
131             "--build-target-stage",
132             type=str,
133             help=(
134                 "Global default value for `target_stage` on `docker_image` targets, overriding "
135                 "the field value on the targets, if there is a matching stage in the `Dockerfile`."
136                 "\n\n"
137                 "This is useful to provide from the command line, to specify the target stage to "
138                 "build for at execution time."
139             ),
140         )
141 
142         register(
143             "--env-vars",
144             type=list,
145             member_type=shell_str,
146             default=[],
147             advanced=True,
148             help=(
149                 "Environment variables to set for `docker` invocations.\n\n"
150                 "Entries are either strings in the form `ENV_VAR=value` to set an explicit value; "
151                 "or just `ENV_VAR` to copy the value from Pants's own environment."
152             ),
153         )
154 
155         register(
156             "--run-args",
157             type=list,
158             member_type=shell_str,
159             default=["--interactive", "--tty"] if sys.stdout.isatty() else [],
160             help=(
161                 "Additional arguments to use for `docker run` invocations.\n\n"
162                 "Example:\n\n"
163                 f'    $ ./pants run --{cls.options_scope}-run-args="-p 127.0.0.1:80:8080/tcp '
164                 '--name demo" src/example:image -- [image entrypoint args]\n\n'
165                 "To provide the top-level options to the `docker` client, use "
166                 f"`[{cls.options_scope}].env_vars` to configure the [Environment variables]("
167                 f"{doc_links['docker_env_vars']}) as appropriate.\n\n"
168                 "The arguments for the image entrypoint may be passed on the command line after a "
169                 "double dash (`--`), or using the `--run-args` option.\n\n"
170                 "Defaults to `--interactive --tty` when stdout is connected to a terminal."
171             ),
172         )
173 
174     @property
175     def build_args(self) -> tuple[str, ...]:
176         return tuple(sorted(set(self.options.build_args)))
177 
178     @property
179     def build_target_stage(self) -> str | None:
180         return cast("str | None", self.options.build_target_stage)
181 
182     @property
183     def run_args(self) -> tuple[str, ...]:
184         return tuple(self.options.run_args)
185 
186     @property
187     def env_vars(self) -> tuple[str, ...]:
188         return tuple(sorted(set(self.options.env_vars)))
189 
190     @property
191     def default_context_root(self) -> str:
192         return cast(str, self.options.default_context_root)
193 
194     @property
195     def default_repository(self) -> str:
196         return cast(str, self.options.default_repository)
197 
198     @memoized_method
199     def registries(self) -> DockerRegistries:
200         return DockerRegistries.from_dict(self.options.registries)
201
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/python/pants/backend/docker/subsystems/docker_options.py b/src/python/pants/backend/docker/subsystems/docker_options.py
--- a/src/python/pants/backend/docker/subsystems/docker_options.py
+++ b/src/python/pants/backend/docker/subsystems/docker_options.py
@@ -3,15 +3,18 @@
 
 from __future__ import annotations
 
+import os
 import sys
 from textwrap import dedent
 from typing import cast
 
 from pants.backend.docker.registries import DockerRegistries
+from pants.engine.environment import Environment
 from pants.option.custom_types import shell_str
 from pants.option.errors import ParseError
 from pants.option.subsystem import Subsystem
 from pants.util.memo import memoized_method
+from pants.util.ordered_set import OrderedSet
 from pants.util.strutil import bullet_list
 
 doc_links = {
@@ -171,6 +174,19 @@
             ),
         )
 
+        register(
+            "--executable-search-paths",
+            advanced=True,
+            type=list,
+            default=["<PATH>"],
+            metavar="<binary-paths>",
+            help=(
+                "The PATH value that will be used to find the Docker client and any tools required."
+                "\n\n"
+                'The special string `"<PATH>"` will expand to the contents of the PATH env var.'
+            ),
+        )
+
     @property
     def build_args(self) -> tuple[str, ...]:
         return tuple(sorted(set(self.options.build_args)))
@@ -198,3 +214,16 @@
     @memoized_method
     def registries(self) -> DockerRegistries:
         return DockerRegistries.from_dict(self.options.registries)
+
+    @memoized_method
+    def executable_search_path(self, env: Environment) -> tuple[str, ...]:
+        def iter_path_entries():
+            for entry in self.options.executable_search_paths:
+                if entry == "<PATH>":
+                    path = env.get("PATH")
+                    if path:
+                        yield from path.split(os.pathsep)
+                else:
+                    yield entry
+
+        return tuple(OrderedSet(iter_path_entries()))
diff --git a/src/python/pants/backend/docker/util_rules/docker_binary.py b/src/python/pants/backend/docker/util_rules/docker_binary.py
--- a/src/python/pants/backend/docker/util_rules/docker_binary.py
+++ b/src/python/pants/backend/docker/util_rules/docker_binary.py
@@ -6,7 +6,9 @@
 from dataclasses import dataclass
 from typing import Mapping
 
+from pants.backend.docker.subsystems.docker_options import DockerOptions
 from pants.backend.docker.util_rules.docker_build_args import DockerBuildArgs
+from pants.engine.environment import Environment, EnvironmentRequest
 from pants.engine.fs import Digest
 from pants.engine.process import (
     BinaryNotFoundError,
@@ -97,10 +99,14 @@
 
 
 @rule(desc="Finding the `docker` binary", level=LogLevel.DEBUG)
-async def find_docker(docker_request: DockerBinaryRequest) -> DockerBinary:
+async def find_docker(
+    docker_request: DockerBinaryRequest, docker_options: DockerOptions
+) -> DockerBinary:
+    env = await Get(Environment, EnvironmentRequest(["PATH"]))
+    search_path = docker_options.executable_search_path(env)
     request = BinaryPathRequest(
         binary_name="docker",
-        search_path=docker_request.search_path,
+        search_path=search_path or docker_request.search_path,
         test=BinaryPathTest(args=["-v"]),
     )
     paths = await Get(BinaryPaths, BinaryPathRequest, request)

{"golden_diff": "diff --git a/src/python/pants/backend/docker/subsystems/docker_options.py b/src/python/pants/backend/docker/subsystems/docker_options.py\n--- a/src/python/pants/backend/docker/subsystems/docker_options.py\n+++ b/src/python/pants/backend/docker/subsystems/docker_options.py\n@@ -3,15 +3,18 @@\n \n from __future__ import annotations\n \n+import os\n import sys\n from textwrap import dedent\n from typing import cast\n \n from pants.backend.docker.registries import DockerRegistries\n+from pants.engine.environment import Environment\n from pants.option.custom_types import shell_str\n from pants.option.errors import ParseError\n from pants.option.subsystem import Subsystem\n from pants.util.memo import memoized_method\n+from pants.util.ordered_set import OrderedSet\n from pants.util.strutil import bullet_list\n \n doc_links = {\n@@ -171,6 +174,19 @@\n             ),\n         )\n \n+        register(\n+            \"--executable-search-paths\",\n+            advanced=True,\n+            type=list,\n+            default=[\"<PATH>\"],\n+            metavar=\"<binary-paths>\",\n+            help=(\n+                \"The PATH value that will be used to find the Docker client and any tools required.\"\n+                \"\\n\\n\"\n+                'The special string `\"<PATH>\"` will expand to the contents of the PATH env var.'\n+            ),\n+        )\n+\n     @property\n     def build_args(self) -> tuple[str, ...]:\n         return tuple(sorted(set(self.options.build_args)))\n@@ -198,3 +214,16 @@\n     @memoized_method\n     def registries(self) -> DockerRegistries:\n         return DockerRegistries.from_dict(self.options.registries)\n+\n+    @memoized_method\n+    def executable_search_path(self, env: Environment) -> tuple[str, ...]:\n+        def iter_path_entries():\n+            for entry in self.options.executable_search_paths:\n+                if entry == \"<PATH>\":\n+                    path = env.get(\"PATH\")\n+                    if path:\n+                        yield from path.split(os.pathsep)\n+                else:\n+                    yield entry\n+\n+        return tuple(OrderedSet(iter_path_entries()))\ndiff --git a/src/python/pants/backend/docker/util_rules/docker_binary.py b/src/python/pants/backend/docker/util_rules/docker_binary.py\n--- a/src/python/pants/backend/docker/util_rules/docker_binary.py\n+++ b/src/python/pants/backend/docker/util_rules/docker_binary.py\n@@ -6,7 +6,9 @@\n from dataclasses import dataclass\n from typing import Mapping\n \n+from pants.backend.docker.subsystems.docker_options import DockerOptions\n from pants.backend.docker.util_rules.docker_build_args import DockerBuildArgs\n+from pants.engine.environment import Environment, EnvironmentRequest\n from pants.engine.fs import Digest\n from pants.engine.process import (\n     BinaryNotFoundError,\n@@ -97,10 +99,14 @@\n \n \n @rule(desc=\"Finding the `docker` binary\", level=LogLevel.DEBUG)\n-async def find_docker(docker_request: DockerBinaryRequest) -> DockerBinary:\n+async def find_docker(\n+    docker_request: DockerBinaryRequest, docker_options: DockerOptions\n+) -> DockerBinary:\n+    env = await Get(Environment, EnvironmentRequest([\"PATH\"]))\n+    search_path = docker_options.executable_search_path(env)\n     request = BinaryPathRequest(\n         binary_name=\"docker\",\n-        search_path=docker_request.search_path,\n+        search_path=search_path or docker_request.search_path,\n         test=BinaryPathTest(args=[\"-v\"]),\n     )\n     paths = await Get(BinaryPaths, BinaryPathRequest, request)\n", "issue": "Make the search path for Docker configurable\nCurrently, the `docker` binary is searched for in a hard coded set of paths. If your binary is elsewhere, you're out of luck.\r\n\r\nIntroduce a new option to specify search path. Model after https://www.pantsbuild.org/docs/reference-shell-setup#section-executable-search-paths\r\n\n", "before_files": [{"content": "# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Mapping\n\nfrom pants.backend.docker.util_rules.docker_build_args import DockerBuildArgs\nfrom pants.engine.fs import Digest\nfrom pants.engine.process import (\n    BinaryNotFoundError,\n    BinaryPath,\n    BinaryPathRequest,\n    BinaryPaths,\n    BinaryPathTest,\n    Process,\n    ProcessCacheScope,\n    SearchPath,\n)\nfrom pants.engine.rules import Get, collect_rules, rule\nfrom pants.util.logging import LogLevel\nfrom pants.util.strutil import pluralize\n\n\nclass DockerBinary(BinaryPath):\n    \"\"\"The `docker` binary.\"\"\"\n\n    DEFAULT_SEARCH_PATH = SearchPath((\"/usr/bin\", \"/bin\", \"/usr/local/bin\"))\n\n    def build_image(\n        self,\n        tags: tuple[str, ...],\n        digest: Digest,\n        dockerfile: str,\n        build_args: DockerBuildArgs,\n        context_root: str,\n        env: Mapping[str, str],\n        extra_args: tuple[str, ...] = (),\n    ) -> Process:\n        args = [self.path, \"build\", *extra_args]\n\n        for tag in tags:\n            args.extend([\"--tag\", tag])\n\n        for build_arg in build_args:\n            args.extend([\"--build-arg\", build_arg])\n\n        args.extend([\"--file\", dockerfile])\n\n        # Docker context root.\n        args.append(context_root)\n\n        return Process(\n            argv=tuple(args),\n            description=(\n                f\"Building docker image {tags[0]}\"\n                + (f\" +{pluralize(len(tags)-1, 'additional tag')}.\" if len(tags) > 1 else \"\")\n            ),\n            env=env,\n            input_digest=digest,\n            cache_scope=ProcessCacheScope.PER_SESSION,\n        )\n\n    def push_image(\n        self, tags: tuple[str, ...], env: Mapping[str, str] | None = None\n    ) -> tuple[Process, ...]:\n        return tuple(\n            Process(\n                argv=(self.path, \"push\", tag),\n                cache_scope=ProcessCacheScope.PER_SESSION,\n                description=f\"Pushing docker image {tag}\",\n                env=env,\n            )\n            for tag in tags\n        )\n\n    def run_image(\n        self,\n        tag: str,\n        *,\n        docker_run_args: tuple[str, ...] | None = None,\n        image_args: tuple[str, ...] | None = None,\n        env: Mapping[str, str] | None = None,\n    ) -> Process:\n        return Process(\n            argv=(self.path, \"run\", *(docker_run_args or []), tag, *(image_args or [])),\n            cache_scope=ProcessCacheScope.PER_SESSION,\n            description=f\"Running docker image {tag}\",\n            env=env,\n        )\n\n\n@dataclass(frozen=True)\nclass DockerBinaryRequest:\n    search_path: SearchPath = DockerBinary.DEFAULT_SEARCH_PATH\n\n\n@rule(desc=\"Finding the `docker` binary\", level=LogLevel.DEBUG)\nasync def find_docker(docker_request: DockerBinaryRequest) -> DockerBinary:\n    request = BinaryPathRequest(\n        binary_name=\"docker\",\n        search_path=docker_request.search_path,\n        test=BinaryPathTest(args=[\"-v\"]),\n    )\n    paths = await Get(BinaryPaths, BinaryPathRequest, request)\n    first_path = paths.first_path\n    if not first_path:\n        raise BinaryNotFoundError.from_request(request, rationale=\"interact with the docker daemon\")\n    return DockerBinary(first_path.path, first_path.fingerprint)\n\n\n@rule\nasync def get_docker() -> DockerBinary:\n    return await Get(DockerBinary, DockerBinaryRequest())\n\n\ndef rules():\n    return collect_rules()\n", "path": "src/python/pants/backend/docker/util_rules/docker_binary.py"}, {"content": "# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom __future__ import annotations\n\nimport sys\nfrom textwrap import dedent\nfrom typing import cast\n\nfrom pants.backend.docker.registries import DockerRegistries\nfrom pants.option.custom_types import shell_str\nfrom pants.option.errors import ParseError\nfrom pants.option.subsystem import Subsystem\nfrom pants.util.memo import memoized_method\nfrom pants.util.strutil import bullet_list\n\ndoc_links = {\n    \"docker_env_vars\": (\n        \"https://docs.docker.com/engine/reference/commandline/cli/#environment-variables\"\n    ),\n}\n\n\ndef workspace_path(s: str) -> str:\n    \"\"\"Same type as 'str', but indicates string represents a directory path that is relative to\n    either the build root, or a BUILD file if prefix with `./`.\n\n    :API: public\n    \"\"\"\n    if s.startswith(\"/\"):\n        raise ParseError(\n            f\"Invalid value: `{s}`. Expected a relative path, optionally in the form \"\n            \"`./relative/path` to make it relative to the BUILD files rather than the build root.\"\n        )\n    return s\n\n\nclass DockerOptions(Subsystem):\n    options_scope = \"docker\"\n    help = \"Options for interacting with Docker.\"\n\n    @classmethod\n    def register_options(cls, register):\n        registries_help = (\n            dedent(\n                \"\"\"\\\n                Configure Docker registries. The schema for a registry entry is as follows:\n\n                    {\n                        \"registry-alias\": {\n                            \"address\": \"registry-domain:port\",\n                            \"default\": bool,\n                        },\n                        ...\n                    }\n\n                \"\"\"\n            )\n            + (\n                \"If no registries are provided in a `docker_image` target, then all default \"\n                \"addresses will be used, if any.\\n\"\n                \"The `docker_image.registries` may be provided with a list of registry addresses \"\n                \"and registry aliases prefixed with `@` to be used instead of the defaults.\\n\"\n                \"A configured registry is marked as default either by setting `default = true` \"\n                'or with an alias of `\"default\"`.'\n            )\n        )\n        default_repository_help = (\n            \"Configure the default repository name used in the Docker image tag.\\n\\n\"\n            \"The value is formatted and may reference these variables (in addition to the normal \"\n            \"placeheolders derived from the Dockerfile and build args etc):\\n\\n\"\n            + bullet_list([\"name\", \"directory\", \"parent_directory\"])\n            + \"\\n\\n\"\n            'Example: `--default-repository=\"{directory}/{name}\"`.\\n\\n'\n            \"The `name` variable is the `docker_image`'s target name, `directory` and \"\n            \"`parent_directory` are the name of the directory in which the BUILD file is for the \"\n            \"target, and its parent directory respectively.\\n\\n\"\n            \"Use the `repository` field to set this value directly on a `docker_image` \"\n            \"target.\\nAny registries or tags are added to the image name as required, and should \"\n            \"not be part of the repository name.\"\n        )\n        super().register_options(register)\n        register(\"--registries\", type=dict, fromfile=True, help=registries_help)\n        register(\n            \"--default-repository\",\n            type=str,\n            help=default_repository_help,\n            default=\"{name}\",\n        )\n\n        register(\n            \"--default-context-root\",\n            type=workspace_path,\n            default=\"\",\n            help=(\n                \"Provide a default Docker build context root path for `docker_image` targets that \"\n                \"does not specify their own `context_root` field.\\n\\n\"\n                \"The context root is relative to the build root by default, but may be prefixed \"\n                \"with `./` to be relative to the directory of the BUILD file of the `docker_image`.\"\n                \"\\n\\nExamples:\\n\\n\"\n                \"    --default-context-root=src/docker\\n\"\n                \"    --default-context-root=./relative_to_the_build_file\\n\"\n            ),\n        )\n\n        register(\n            \"--build-args\",\n            type=list,\n            member_type=shell_str,\n            default=[],\n            help=(\n                \"Global build arguments (for Docker `--build-arg` options) to use for all \"\n                \"`docker build` invocations.\\n\\n\"\n                \"Entries are either strings in the form `ARG_NAME=value` to set an explicit value; \"\n                \"or just `ARG_NAME` to copy the value from Pants's own environment.\\n\\n\"\n                + dedent(\n                    f\"\"\"\\\n                    Example:\n\n                        [{cls.options_scope}]\n                        build_args = [\"VAR1=value\", \"VAR2\"]\n\n                    \"\"\"\n                )\n                + \"Use the `extra_build_args` field on a `docker_image` target for additional \"\n                \"image specific build arguments.\"\n            ),\n        )\n\n        register(\n            \"--build-target-stage\",\n            type=str,\n            help=(\n                \"Global default value for `target_stage` on `docker_image` targets, overriding \"\n                \"the field value on the targets, if there is a matching stage in the `Dockerfile`.\"\n                \"\\n\\n\"\n                \"This is useful to provide from the command line, to specify the target stage to \"\n                \"build for at execution time.\"\n            ),\n        )\n\n        register(\n            \"--env-vars\",\n            type=list,\n            member_type=shell_str,\n            default=[],\n            advanced=True,\n            help=(\n                \"Environment variables to set for `docker` invocations.\\n\\n\"\n                \"Entries are either strings in the form `ENV_VAR=value` to set an explicit value; \"\n                \"or just `ENV_VAR` to copy the value from Pants's own environment.\"\n            ),\n        )\n\n        register(\n            \"--run-args\",\n            type=list,\n            member_type=shell_str,\n            default=[\"--interactive\", \"--tty\"] if sys.stdout.isatty() else [],\n            help=(\n                \"Additional arguments to use for `docker run` invocations.\\n\\n\"\n                \"Example:\\n\\n\"\n                f'    $ ./pants run --{cls.options_scope}-run-args=\"-p 127.0.0.1:80:8080/tcp '\n                '--name demo\" src/example:image -- [image entrypoint args]\\n\\n'\n                \"To provide the top-level options to the `docker` client, use \"\n                f\"`[{cls.options_scope}].env_vars` to configure the [Environment variables](\"\n                f\"{doc_links['docker_env_vars']}) as appropriate.\\n\\n\"\n                \"The arguments for the image entrypoint may be passed on the command line after a \"\n                \"double dash (`--`), or using the `--run-args` option.\\n\\n\"\n                \"Defaults to `--interactive --tty` when stdout is connected to a terminal.\"\n            ),\n        )\n\n    @property\n    def build_args(self) -> tuple[str, ...]:\n        return tuple(sorted(set(self.options.build_args)))\n\n    @property\n    def build_target_stage(self) -> str | None:\n        return cast(\"str | None\", self.options.build_target_stage)\n\n    @property\n    def run_args(self) -> tuple[str, ...]:\n        return tuple(self.options.run_args)\n\n    @property\n    def env_vars(self) -> tuple[str, ...]:\n        return tuple(sorted(set(self.options.env_vars)))\n\n    @property\n    def default_context_root(self) -> str:\n        return cast(str, self.options.default_context_root)\n\n    @property\n    def default_repository(self) -> str:\n        return cast(str, self.options.default_repository)\n\n    @memoized_method\n    def registries(self) -> DockerRegistries:\n        return DockerRegistries.from_dict(self.options.registries)\n", "path": "src/python/pants/backend/docker/subsystems/docker_options.py"}], "after_files": [{"content": "# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Mapping\n\nfrom pants.backend.docker.subsystems.docker_options import DockerOptions\nfrom pants.backend.docker.util_rules.docker_build_args import DockerBuildArgs\nfrom pants.engine.environment import Environment, EnvironmentRequest\nfrom pants.engine.fs import Digest\nfrom pants.engine.process import (\n    BinaryNotFoundError,\n    BinaryPath,\n    BinaryPathRequest,\n    BinaryPaths,\n    BinaryPathTest,\n    Process,\n    ProcessCacheScope,\n    SearchPath,\n)\nfrom pants.engine.rules import Get, collect_rules, rule\nfrom pants.util.logging import LogLevel\nfrom pants.util.strutil import pluralize\n\n\nclass DockerBinary(BinaryPath):\n    \"\"\"The `docker` binary.\"\"\"\n\n    DEFAULT_SEARCH_PATH = SearchPath((\"/usr/bin\", \"/bin\", \"/usr/local/bin\"))\n\n    def build_image(\n        self,\n        tags: tuple[str, ...],\n        digest: Digest,\n        dockerfile: str,\n        build_args: DockerBuildArgs,\n        context_root: str,\n        env: Mapping[str, str],\n        extra_args: tuple[str, ...] = (),\n    ) -> Process:\n        args = [self.path, \"build\", *extra_args]\n\n        for tag in tags:\n            args.extend([\"--tag\", tag])\n\n        for build_arg in build_args:\n            args.extend([\"--build-arg\", build_arg])\n\n        args.extend([\"--file\", dockerfile])\n\n        # Docker context root.\n        args.append(context_root)\n\n        return Process(\n            argv=tuple(args),\n            description=(\n                f\"Building docker image {tags[0]}\"\n                + (f\" +{pluralize(len(tags)-1, 'additional tag')}.\" if len(tags) > 1 else \"\")\n            ),\n            env=env,\n            input_digest=digest,\n            cache_scope=ProcessCacheScope.PER_SESSION,\n        )\n\n    def push_image(\n        self, tags: tuple[str, ...], env: Mapping[str, str] | None = None\n    ) -> tuple[Process, ...]:\n        return tuple(\n            Process(\n                argv=(self.path, \"push\", tag),\n                cache_scope=ProcessCacheScope.PER_SESSION,\n                description=f\"Pushing docker image {tag}\",\n                env=env,\n            )\n            for tag in tags\n        )\n\n    def run_image(\n        self,\n        tag: str,\n        *,\n        docker_run_args: tuple[str, ...] | None = None,\n        image_args: tuple[str, ...] | None = None,\n        env: Mapping[str, str] | None = None,\n    ) -> Process:\n        return Process(\n            argv=(self.path, \"run\", *(docker_run_args or []), tag, *(image_args or [])),\n            cache_scope=ProcessCacheScope.PER_SESSION,\n            description=f\"Running docker image {tag}\",\n            env=env,\n        )\n\n\n@dataclass(frozen=True)\nclass DockerBinaryRequest:\n    search_path: SearchPath = DockerBinary.DEFAULT_SEARCH_PATH\n\n\n@rule(desc=\"Finding the `docker` binary\", level=LogLevel.DEBUG)\nasync def find_docker(\n    docker_request: DockerBinaryRequest, docker_options: DockerOptions\n) -> DockerBinary:\n    env = await Get(Environment, EnvironmentRequest([\"PATH\"]))\n    search_path = docker_options.executable_search_path(env)\n    request = BinaryPathRequest(\n        binary_name=\"docker\",\n        search_path=search_path or docker_request.search_path,\n        test=BinaryPathTest(args=[\"-v\"]),\n    )\n    paths = await Get(BinaryPaths, BinaryPathRequest, request)\n    first_path = paths.first_path\n    if not first_path:\n        raise BinaryNotFoundError.from_request(request, rationale=\"interact with the docker daemon\")\n    return DockerBinary(first_path.path, first_path.fingerprint)\n\n\n@rule\nasync def get_docker() -> DockerBinary:\n    return await Get(DockerBinary, DockerBinaryRequest())\n\n\ndef rules():\n    return collect_rules()\n", "path": "src/python/pants/backend/docker/util_rules/docker_binary.py"}, {"content": "# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom __future__ import annotations\n\nimport os\nimport sys\nfrom textwrap import dedent\nfrom typing import cast\n\nfrom pants.backend.docker.registries import DockerRegistries\nfrom pants.engine.environment import Environment\nfrom pants.option.custom_types import shell_str\nfrom pants.option.errors import ParseError\nfrom pants.option.subsystem import Subsystem\nfrom pants.util.memo import memoized_method\nfrom pants.util.ordered_set import OrderedSet\nfrom pants.util.strutil import bullet_list\n\ndoc_links = {\n    \"docker_env_vars\": (\n        \"https://docs.docker.com/engine/reference/commandline/cli/#environment-variables\"\n    ),\n}\n\n\ndef workspace_path(s: str) -> str:\n    \"\"\"Same type as 'str', but indicates string represents a directory path that is relative to\n    either the build root, or a BUILD file if prefix with `./`.\n\n    :API: public\n    \"\"\"\n    if s.startswith(\"/\"):\n        raise ParseError(\n            f\"Invalid value: `{s}`. Expected a relative path, optionally in the form \"\n            \"`./relative/path` to make it relative to the BUILD files rather than the build root.\"\n        )\n    return s\n\n\nclass DockerOptions(Subsystem):\n    options_scope = \"docker\"\n    help = \"Options for interacting with Docker.\"\n\n    @classmethod\n    def register_options(cls, register):\n        registries_help = (\n            dedent(\n                \"\"\"\\\n                Configure Docker registries. The schema for a registry entry is as follows:\n\n                    {\n                        \"registry-alias\": {\n                            \"address\": \"registry-domain:port\",\n                            \"default\": bool,\n                        },\n                        ...\n                    }\n\n                \"\"\"\n            )\n            + (\n                \"If no registries are provided in a `docker_image` target, then all default \"\n                \"addresses will be used, if any.\\n\"\n                \"The `docker_image.registries` may be provided with a list of registry addresses \"\n                \"and registry aliases prefixed with `@` to be used instead of the defaults.\\n\"\n                \"A configured registry is marked as default either by setting `default = true` \"\n                'or with an alias of `\"default\"`.'\n            )\n        )\n        default_repository_help = (\n            \"Configure the default repository name used in the Docker image tag.\\n\\n\"\n            \"The value is formatted and may reference these variables (in addition to the normal \"\n            \"placeheolders derived from the Dockerfile and build args etc):\\n\\n\"\n            + bullet_list([\"name\", \"directory\", \"parent_directory\"])\n            + \"\\n\\n\"\n            'Example: `--default-repository=\"{directory}/{name}\"`.\\n\\n'\n            \"The `name` variable is the `docker_image`'s target name, `directory` and \"\n            \"`parent_directory` are the name of the directory in which the BUILD file is for the \"\n            \"target, and its parent directory respectively.\\n\\n\"\n            \"Use the `repository` field to set this value directly on a `docker_image` \"\n            \"target.\\nAny registries or tags are added to the image name as required, and should \"\n            \"not be part of the repository name.\"\n        )\n        super().register_options(register)\n        register(\"--registries\", type=dict, fromfile=True, help=registries_help)\n        register(\n            \"--default-repository\",\n            type=str,\n            help=default_repository_help,\n            default=\"{name}\",\n        )\n\n        register(\n            \"--default-context-root\",\n            type=workspace_path,\n            default=\"\",\n            help=(\n                \"Provide a default Docker build context root path for `docker_image` targets that \"\n                \"does not specify their own `context_root` field.\\n\\n\"\n                \"The context root is relative to the build root by default, but may be prefixed \"\n                \"with `./` to be relative to the directory of the BUILD file of the `docker_image`.\"\n                \"\\n\\nExamples:\\n\\n\"\n                \"    --default-context-root=src/docker\\n\"\n                \"    --default-context-root=./relative_to_the_build_file\\n\"\n            ),\n        )\n\n        register(\n            \"--build-args\",\n            type=list,\n            member_type=shell_str,\n            default=[],\n            help=(\n                \"Global build arguments (for Docker `--build-arg` options) to use for all \"\n                \"`docker build` invocations.\\n\\n\"\n                \"Entries are either strings in the form `ARG_NAME=value` to set an explicit value; \"\n                \"or just `ARG_NAME` to copy the value from Pants's own environment.\\n\\n\"\n                + dedent(\n                    f\"\"\"\\\n                    Example:\n\n                        [{cls.options_scope}]\n                        build_args = [\"VAR1=value\", \"VAR2\"]\n\n                    \"\"\"\n                )\n                + \"Use the `extra_build_args` field on a `docker_image` target for additional \"\n                \"image specific build arguments.\"\n            ),\n        )\n\n        register(\n            \"--build-target-stage\",\n            type=str,\n            help=(\n                \"Global default value for `target_stage` on `docker_image` targets, overriding \"\n                \"the field value on the targets, if there is a matching stage in the `Dockerfile`.\"\n                \"\\n\\n\"\n                \"This is useful to provide from the command line, to specify the target stage to \"\n                \"build for at execution time.\"\n            ),\n        )\n\n        register(\n            \"--env-vars\",\n            type=list,\n            member_type=shell_str,\n            default=[],\n            advanced=True,\n            help=(\n                \"Environment variables to set for `docker` invocations.\\n\\n\"\n                \"Entries are either strings in the form `ENV_VAR=value` to set an explicit value; \"\n                \"or just `ENV_VAR` to copy the value from Pants's own environment.\"\n            ),\n        )\n\n        register(\n            \"--run-args\",\n            type=list,\n            member_type=shell_str,\n            default=[\"--interactive\", \"--tty\"] if sys.stdout.isatty() else [],\n            help=(\n                \"Additional arguments to use for `docker run` invocations.\\n\\n\"\n                \"Example:\\n\\n\"\n                f'    $ ./pants run --{cls.options_scope}-run-args=\"-p 127.0.0.1:80:8080/tcp '\n                '--name demo\" src/example:image -- [image entrypoint args]\\n\\n'\n                \"To provide the top-level options to the `docker` client, use \"\n                f\"`[{cls.options_scope}].env_vars` to configure the [Environment variables](\"\n                f\"{doc_links['docker_env_vars']}) as appropriate.\\n\\n\"\n                \"The arguments for the image entrypoint may be passed on the command line after a \"\n                \"double dash (`--`), or using the `--run-args` option.\\n\\n\"\n                \"Defaults to `--interactive --tty` when stdout is connected to a terminal.\"\n            ),\n        )\n\n        register(\n            \"--executable-search-paths\",\n            advanced=True,\n            type=list,\n            default=[\"<PATH>\"],\n            metavar=\"<binary-paths>\",\n            help=(\n                \"The PATH value that will be used to find the Docker client and any tools required.\"\n                \"\\n\\n\"\n                'The special string `\"<PATH>\"` will expand to the contents of the PATH env var.'\n            ),\n        )\n\n    @property\n    def build_args(self) -> tuple[str, ...]:\n        return tuple(sorted(set(self.options.build_args)))\n\n    @property\n    def build_target_stage(self) -> str | None:\n        return cast(\"str | None\", self.options.build_target_stage)\n\n    @property\n    def run_args(self) -> tuple[str, ...]:\n        return tuple(self.options.run_args)\n\n    @property\n    def env_vars(self) -> tuple[str, ...]:\n        return tuple(sorted(set(self.options.env_vars)))\n\n    @property\n    def default_context_root(self) -> str:\n        return cast(str, self.options.default_context_root)\n\n    @property\n    def default_repository(self) -> str:\n        return cast(str, self.options.default_repository)\n\n    @memoized_method\n    def registries(self) -> DockerRegistries:\n        return DockerRegistries.from_dict(self.options.registries)\n\n    @memoized_method\n    def executable_search_path(self, env: Environment) -> tuple[str, ...]:\n        def iter_path_entries():\n            for entry in self.options.executable_search_paths:\n                if entry == \"<PATH>\":\n                    path = env.get(\"PATH\")\n                    if path:\n                        yield from path.split(os.pathsep)\n                else:\n                    yield entry\n\n        return tuple(OrderedSet(iter_path_entries()))\n", "path": "src/python/pants/backend/docker/subsystems/docker_options.py"}]}