We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Use of relative PATH for docker-tool shims prevents use of credential helpers
**Describe the bug**
I'm trying to set up [tools](https://www.pantsbuild.org/docs/reference-docker#section-tools) in my repo's `docker` subsystem, to plug in the [ECR credential helper](https://github.com/awslabs/amazon-ecr-credential-helper). To do so I added the following to `pants.toml`:
```toml
[docker]
tools = ["docker-credential-ecr-login", "sh"]
```
When I run `./pants package path/to/Dockerfile`, I get the error:
```
failed to solve with frontend dockerfile.v0: failed to create LLB definition: rpc error: code = Unknown desc = error getting credentials - err: docker-credential-ecr-login resolves to executable in current directory (./.shims/bin/docker-credential-ecr-login), out: ``
```
If I run the above with `--no-process-cleanup` and `cd` into the tmpdir, I see:
1. There are shims for both tools under `.shims/bin`
2. The shims behave as expected when I use them directly
3. `__run.sh` sets `PATH=.shims/bin`

If I edit `__run.sh` to instead set `PATH=<absolute-path-to-tmpdir>/.shims/bin`, the build works.

**Pants version**
2.11.0+git9ac327d4

**OS**
MacOS

**Additional info**
Docker Desktop v4.7.1 (77678)
Docker Engine v20.10.14


--- 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 import os
7 from dataclasses import dataclass
8 from typing import Mapping
9 
10 from pants.backend.docker.subsystems.docker_options import DockerOptions
11 from pants.backend.docker.util_rules.docker_build_args import DockerBuildArgs
12 from pants.core.util_rules.system_binaries import (
13     BinaryPath,
14     BinaryPathRequest,
15     BinaryPaths,
16     BinaryPathTest,
17     BinaryShims,
18     BinaryShimsRequest,
19 )
20 from pants.engine.environment import Environment, EnvironmentRequest
21 from pants.engine.fs import Digest
22 from pants.engine.process import Process, ProcessCacheScope
23 from pants.engine.rules import Get, collect_rules, rule
24 from pants.util.logging import LogLevel
25 from pants.util.strutil import pluralize
26 
27 
28 # The base class is decorated with `frozen_after_init`.
29 @dataclass
30 class DockerBinary(BinaryPath):
31     """The `docker` binary."""
32 
33     extra_env: Mapping[str, str]
34     extra_input_digests: Mapping[str, Digest] | None
35 
36     def __init__(
37         self,
38         path: str,
39         fingerprint: str | None = None,
40         extra_env: Mapping[str, str] | None = None,
41         extra_input_digests: Mapping[str, Digest] | None = None,
42     ) -> None:
43         self.extra_env = {} if extra_env is None else extra_env
44         self.extra_input_digests = extra_input_digests
45         super().__init__(path, fingerprint)
46 
47     def _get_process_environment(self, env: Mapping[str, str]) -> Mapping[str, str]:
48         if not self.extra_env:
49             return env
50 
51         res = {**self.extra_env, **env}
52 
53         # Merge the PATH entries, in case they are present in both `env` and `self.extra_env`.
54         res["PATH"] = os.pathsep.join(
55             p for p in (m.get("PATH") for m in (self.extra_env, env)) if p
56         )
57         return res
58 
59     def build_image(
60         self,
61         tags: tuple[str, ...],
62         digest: Digest,
63         dockerfile: str,
64         build_args: DockerBuildArgs,
65         context_root: str,
66         env: Mapping[str, str],
67         extra_args: tuple[str, ...] = (),
68     ) -> Process:
69         args = [self.path, "build", *extra_args]
70 
71         for tag in tags:
72             args.extend(["--tag", tag])
73 
74         for build_arg in build_args:
75             args.extend(["--build-arg", build_arg])
76 
77         args.extend(["--file", dockerfile])
78 
79         # Docker context root.
80         args.append(context_root)
81 
82         return Process(
83             argv=tuple(args),
84             description=(
85                 f"Building docker image {tags[0]}"
86                 + (f" +{pluralize(len(tags)-1, 'additional tag')}." if len(tags) > 1 else "")
87             ),
88             env=self._get_process_environment(env),
89             input_digest=digest,
90             immutable_input_digests=self.extra_input_digests,
91             cache_scope=ProcessCacheScope.PER_SESSION,
92         )
93 
94     def push_image(self, tag: str, env: Mapping[str, str] | None = None) -> Process:
95         return Process(
96             argv=(self.path, "push", tag),
97             cache_scope=ProcessCacheScope.PER_SESSION,
98             description=f"Pushing docker image {tag}",
99             env=self._get_process_environment(env or {}),
100             immutable_input_digests=self.extra_input_digests,
101         )
102 
103     def run_image(
104         self,
105         tag: str,
106         *,
107         docker_run_args: tuple[str, ...] | None = None,
108         image_args: tuple[str, ...] | None = None,
109         env: Mapping[str, str] | None = None,
110     ) -> Process:
111         return Process(
112             argv=(self.path, "run", *(docker_run_args or []), tag, *(image_args or [])),
113             cache_scope=ProcessCacheScope.PER_SESSION,
114             description=f"Running docker image {tag}",
115             env=self._get_process_environment(env or {}),
116             immutable_input_digests=self.extra_input_digests,
117         )
118 
119 
120 @dataclass(frozen=True)
121 class DockerBinaryRequest:
122     pass
123 
124 
125 @rule(desc="Finding the `docker` binary and related tooling", level=LogLevel.DEBUG)
126 async def find_docker(
127     docker_request: DockerBinaryRequest, docker_options: DockerOptions
128 ) -> DockerBinary:
129     env = await Get(Environment, EnvironmentRequest(["PATH"]))
130     search_path = docker_options.executable_search_path(env)
131     request = BinaryPathRequest(
132         binary_name="docker",
133         search_path=search_path,
134         test=BinaryPathTest(args=["-v"]),
135     )
136     paths = await Get(BinaryPaths, BinaryPathRequest, request)
137     first_path = paths.first_path_or_raise(request, rationale="interact with the docker daemon")
138 
139     if not docker_options.tools:
140         return DockerBinary(first_path.path, first_path.fingerprint)
141 
142     tools = await Get(
143         BinaryShims,
144         BinaryShimsRequest,
145         BinaryShimsRequest.for_binaries(
146             *docker_options.tools,
147             rationale="use docker",
148             output_directory="bin",
149             search_path=search_path,
150         ),
151     )
152     tools_path = ".shims"
153     extra_env = {"PATH": os.path.join(tools_path, tools.bin_directory)}
154     extra_input_digests = {tools_path: tools.digest}
155 
156     return DockerBinary(
157         first_path.path,
158         first_path.fingerprint,
159         extra_env=extra_env,
160         extra_input_digests=extra_input_digests,
161     )
162 
163 
164 @rule
165 async def get_docker() -> DockerBinary:
166     return await Get(DockerBinary, DockerBinaryRequest())
167 
168 
169 def rules():
170     return collect_rules()
171
```
--- 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/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
@@ -150,7 +150,7 @@
         ),
     )
     tools_path = ".shims"
-    extra_env = {"PATH": os.path.join(tools_path, tools.bin_directory)}
+    extra_env = {"PATH": os.path.join("{chroot}", tools_path, tools.bin_directory)}
     extra_input_digests = {tools_path: tools.digest}
 
     return DockerBinary(

{"golden_diff": "diff --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@@ -150,7 +150,7 @@\n         ),\n     )\n     tools_path = \".shims\"\n-    extra_env = {\"PATH\": os.path.join(tools_path, tools.bin_directory)}\n+    extra_env = {\"PATH\": os.path.join(\"{chroot}\", tools_path, tools.bin_directory)}\n     extra_input_digests = {tools_path: tools.digest}\n \n     return DockerBinary(\n", "issue": "Use of relative PATH for docker-tool shims prevents use of credential helpers\n**Describe the bug**\r\nI'm trying to set up [tools](https://www.pantsbuild.org/docs/reference-docker#section-tools) in my repo's `docker` subsystem, to plug in the [ECR credential helper](https://github.com/awslabs/amazon-ecr-credential-helper). To do so I added the following to `pants.toml`:\r\n```toml\r\n[docker]\r\ntools = [\"docker-credential-ecr-login\", \"sh\"]\r\n```\r\nWhen I run `./pants package path/to/Dockerfile`, I get the error:\r\n```\r\nfailed to solve with frontend dockerfile.v0: failed to create LLB definition: rpc error: code = Unknown desc = error getting credentials - err: docker-credential-ecr-login resolves to executable in current directory (./.shims/bin/docker-credential-ecr-login), out: ``\r\n```\r\nIf I run the above with `--no-process-cleanup` and `cd` into the tmpdir, I see:\r\n1. There are shims for both tools under `.shims/bin`\r\n2. The shims behave as expected when I use them directly\r\n3. `__run.sh` sets `PATH=.shims/bin`\r\n\r\nIf I edit `__run.sh` to instead set `PATH=<absolute-path-to-tmpdir>/.shims/bin`, the build works.\r\n\r\n**Pants version**\r\n2.11.0+git9ac327d4\r\n\r\n**OS**\r\nMacOS\r\n\r\n**Additional info**\r\nDocker Desktop v4.7.1 (77678)\r\nDocker Engine v20.10.14\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\nimport os\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.core.util_rules.system_binaries import (\n    BinaryPath,\n    BinaryPathRequest,\n    BinaryPaths,\n    BinaryPathTest,\n    BinaryShims,\n    BinaryShimsRequest,\n)\nfrom pants.engine.environment import Environment, EnvironmentRequest\nfrom pants.engine.fs import Digest\nfrom pants.engine.process import Process, ProcessCacheScope\nfrom pants.engine.rules import Get, collect_rules, rule\nfrom pants.util.logging import LogLevel\nfrom pants.util.strutil import pluralize\n\n\n# The base class is decorated with `frozen_after_init`.\n@dataclass\nclass DockerBinary(BinaryPath):\n    \"\"\"The `docker` binary.\"\"\"\n\n    extra_env: Mapping[str, str]\n    extra_input_digests: Mapping[str, Digest] | None\n\n    def __init__(\n        self,\n        path: str,\n        fingerprint: str | None = None,\n        extra_env: Mapping[str, str] | None = None,\n        extra_input_digests: Mapping[str, Digest] | None = None,\n    ) -> None:\n        self.extra_env = {} if extra_env is None else extra_env\n        self.extra_input_digests = extra_input_digests\n        super().__init__(path, fingerprint)\n\n    def _get_process_environment(self, env: Mapping[str, str]) -> Mapping[str, str]:\n        if not self.extra_env:\n            return env\n\n        res = {**self.extra_env, **env}\n\n        # Merge the PATH entries, in case they are present in both `env` and `self.extra_env`.\n        res[\"PATH\"] = os.pathsep.join(\n            p for p in (m.get(\"PATH\") for m in (self.extra_env, env)) if p\n        )\n        return res\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=self._get_process_environment(env),\n            input_digest=digest,\n            immutable_input_digests=self.extra_input_digests,\n            cache_scope=ProcessCacheScope.PER_SESSION,\n        )\n\n    def push_image(self, tag: str, env: Mapping[str, str] | None = None) -> Process:\n        return Process(\n            argv=(self.path, \"push\", tag),\n            cache_scope=ProcessCacheScope.PER_SESSION,\n            description=f\"Pushing docker image {tag}\",\n            env=self._get_process_environment(env or {}),\n            immutable_input_digests=self.extra_input_digests,\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=self._get_process_environment(env or {}),\n            immutable_input_digests=self.extra_input_digests,\n        )\n\n\n@dataclass(frozen=True)\nclass DockerBinaryRequest:\n    pass\n\n\n@rule(desc=\"Finding the `docker` binary and related tooling\", 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,\n        test=BinaryPathTest(args=[\"-v\"]),\n    )\n    paths = await Get(BinaryPaths, BinaryPathRequest, request)\n    first_path = paths.first_path_or_raise(request, rationale=\"interact with the docker daemon\")\n\n    if not docker_options.tools:\n        return DockerBinary(first_path.path, first_path.fingerprint)\n\n    tools = await Get(\n        BinaryShims,\n        BinaryShimsRequest,\n        BinaryShimsRequest.for_binaries(\n            *docker_options.tools,\n            rationale=\"use docker\",\n            output_directory=\"bin\",\n            search_path=search_path,\n        ),\n    )\n    tools_path = \".shims\"\n    extra_env = {\"PATH\": os.path.join(tools_path, tools.bin_directory)}\n    extra_input_digests = {tools_path: tools.digest}\n\n    return DockerBinary(\n        first_path.path,\n        first_path.fingerprint,\n        extra_env=extra_env,\n        extra_input_digests=extra_input_digests,\n    )\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"}], "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\nimport os\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.core.util_rules.system_binaries import (\n    BinaryPath,\n    BinaryPathRequest,\n    BinaryPaths,\n    BinaryPathTest,\n    BinaryShims,\n    BinaryShimsRequest,\n)\nfrom pants.engine.environment import Environment, EnvironmentRequest\nfrom pants.engine.fs import Digest\nfrom pants.engine.process import Process, ProcessCacheScope\nfrom pants.engine.rules import Get, collect_rules, rule\nfrom pants.util.logging import LogLevel\nfrom pants.util.strutil import pluralize\n\n\n# The base class is decorated with `frozen_after_init`.\n@dataclass\nclass DockerBinary(BinaryPath):\n    \"\"\"The `docker` binary.\"\"\"\n\n    extra_env: Mapping[str, str]\n    extra_input_digests: Mapping[str, Digest] | None\n\n    def __init__(\n        self,\n        path: str,\n        fingerprint: str | None = None,\n        extra_env: Mapping[str, str] | None = None,\n        extra_input_digests: Mapping[str, Digest] | None = None,\n    ) -> None:\n        self.extra_env = {} if extra_env is None else extra_env\n        self.extra_input_digests = extra_input_digests\n        super().__init__(path, fingerprint)\n\n    def _get_process_environment(self, env: Mapping[str, str]) -> Mapping[str, str]:\n        if not self.extra_env:\n            return env\n\n        res = {**self.extra_env, **env}\n\n        # Merge the PATH entries, in case they are present in both `env` and `self.extra_env`.\n        res[\"PATH\"] = os.pathsep.join(\n            p for p in (m.get(\"PATH\") for m in (self.extra_env, env)) if p\n        )\n        return res\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=self._get_process_environment(env),\n            input_digest=digest,\n            immutable_input_digests=self.extra_input_digests,\n            cache_scope=ProcessCacheScope.PER_SESSION,\n        )\n\n    def push_image(self, tag: str, env: Mapping[str, str] | None = None) -> Process:\n        return Process(\n            argv=(self.path, \"push\", tag),\n            cache_scope=ProcessCacheScope.PER_SESSION,\n            description=f\"Pushing docker image {tag}\",\n            env=self._get_process_environment(env or {}),\n            immutable_input_digests=self.extra_input_digests,\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=self._get_process_environment(env or {}),\n            immutable_input_digests=self.extra_input_digests,\n        )\n\n\n@dataclass(frozen=True)\nclass DockerBinaryRequest:\n    pass\n\n\n@rule(desc=\"Finding the `docker` binary and related tooling\", 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,\n        test=BinaryPathTest(args=[\"-v\"]),\n    )\n    paths = await Get(BinaryPaths, BinaryPathRequest, request)\n    first_path = paths.first_path_or_raise(request, rationale=\"interact with the docker daemon\")\n\n    if not docker_options.tools:\n        return DockerBinary(first_path.path, first_path.fingerprint)\n\n    tools = await Get(\n        BinaryShims,\n        BinaryShimsRequest,\n        BinaryShimsRequest.for_binaries(\n            *docker_options.tools,\n            rationale=\"use docker\",\n            output_directory=\"bin\",\n            search_path=search_path,\n        ),\n    )\n    tools_path = \".shims\"\n    extra_env = {\"PATH\": os.path.join(\"{chroot}\", tools_path, tools.bin_directory)}\n    extra_input_digests = {tools_path: tools.digest}\n\n    return DockerBinary(\n        first_path.path,\n        first_path.fingerprint,\n        extra_env=extra_env,\n        extra_input_digests=extra_input_digests,\n    )\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"}]}