prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Failure to get the container id
  With #1888 in place, there's a regression when running inside a container. There's an assumption in https://github.com/pre-commit/pre-commit/blob/master/pre_commit/languages/docker.py#L32 that the hostname is the container ID which is not always the case (it's easy enough to set a different hostname with `docker run --hostname foo`). It causes the `docker inspect` command that follows to fail. A more reliable way to get the container id is from `/proc/1/cpuset` or from the first line in `/proc/1/cgroup` which is already checked in `_is_in_docker`.
  
  Thanks @asottile and @okainov for your work on #1387 and pre-commit in general.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `pre_commit/languages/docker.py`
  Content:
  ```
  1 import hashlib
  2 import json
  3 import os
  4 import socket
  5 from typing import Sequence
  6 from typing import Tuple
  7 
  8 import pre_commit.constants as C
  9 from pre_commit.hook import Hook
  10 from pre_commit.languages import helpers
  11 from pre_commit.prefix import Prefix
  12 from pre_commit.util import clean_path_on_failure
  13 from pre_commit.util import cmd_output_b
  14 
  15 ENVIRONMENT_DIR = 'docker'
  16 PRE_COMMIT_LABEL = 'PRE_COMMIT'
  17 get_default_version = helpers.basic_get_default_version
  18 healthy = helpers.basic_healthy
  19 
  20 
  21 def _is_in_docker() -> bool:
  22     try:
  23         with open('/proc/1/cgroup', 'rb') as f:
  24             return b'docker' in f.read()
  25     except FileNotFoundError:
  26         return False
  27 
  28 
  29 def _get_docker_path(path: str) -> str:
  30     if not _is_in_docker():
  31         return path
  32     hostname = socket.gethostname()
  33 
  34     _, out, _ = cmd_output_b('docker', 'inspect', hostname)
  35 
  36     container, = json.loads(out)
  37     for mount in container['Mounts']:
  38         src_path = mount['Source']
  39         to_path = mount['Destination']
  40         if os.path.commonpath((path, to_path)) == to_path:
  41             # So there is something in common,
  42             # and we can proceed remapping it
  43             return path.replace(to_path, src_path)
  44     # we're in Docker, but the path is not mounted, cannot really do anything,
  45     # so fall back to original path
  46     return path
  47 
  48 
  49 def md5(s: str) -> str:  # pragma: win32 no cover
  50     return hashlib.md5(s.encode()).hexdigest()
  51 
  52 
  53 def docker_tag(prefix: Prefix) -> str:  # pragma: win32 no cover
  54     md5sum = md5(os.path.basename(prefix.prefix_dir)).lower()
  55     return f'pre-commit-{md5sum}'
  56 
  57 
  58 def build_docker_image(
  59         prefix: Prefix,
  60         *,
  61         pull: bool,
  62 ) -> None:  # pragma: win32 no cover
  63     cmd: Tuple[str, ...] = (
  64         'docker', 'build',
  65         '--tag', docker_tag(prefix),
  66         '--label', PRE_COMMIT_LABEL,
  67     )
  68     if pull:
  69         cmd += ('--pull',)
  70     # This must come last for old versions of docker.  See #477
  71     cmd += ('.',)
  72     helpers.run_setup_cmd(prefix, cmd)
  73 
  74 
  75 def install_environment(
  76         prefix: Prefix, version: str, additional_dependencies: Sequence[str],
  77 ) -> None:  # pragma: win32 no cover
  78     helpers.assert_version_default('docker', version)
  79     helpers.assert_no_additional_deps('docker', additional_dependencies)
  80 
  81     directory = prefix.path(
  82         helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),
  83     )
  84 
  85     # Docker doesn't really have relevant disk environment, but pre-commit
  86     # still needs to cleanup its state files on failure
  87     with clean_path_on_failure(directory):
  88         build_docker_image(prefix, pull=True)
  89         os.mkdir(directory)
  90 
  91 
  92 def get_docker_user() -> Tuple[str, ...]:  # pragma: win32 no cover
  93     try:
  94         return ('-u', f'{os.getuid()}:{os.getgid()}')
  95     except AttributeError:
  96         return ()
  97 
  98 
  99 def docker_cmd() -> Tuple[str, ...]:  # pragma: win32 no cover
  100     return (
  101         'docker', 'run',
  102         '--rm',
  103         *get_docker_user(),
  104         # https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from
  105         # The `Z` option tells Docker to label the content with a private
  106         # unshared label. Only the current container can use a private volume.
  107         '-v', f'{_get_docker_path(os.getcwd())}:/src:rw,Z',
  108         '--workdir', '/src',
  109     )
  110 
  111 
  112 def run_hook(
  113         hook: Hook,
  114         file_args: Sequence[str],
  115         color: bool,
  116 ) -> Tuple[int, bytes]:  # pragma: win32 no cover
  117     # Rebuild the docker image in case it has gone missing, as many people do
  118     # automated cleanup of docker images.
  119     build_docker_image(hook.prefix, pull=False)
  120 
  121     entry_exe, *cmd_rest = hook.cmd
  122 
  123     entry_tag = ('--entrypoint', entry_exe, docker_tag(hook.prefix))
  124     cmd = (*docker_cmd(), *entry_tag, *cmd_rest)
  125     return helpers.run_xargs(hook, cmd, file_args, color=color)
  126
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py
  --- a/pre_commit/languages/docker.py
  +++ b/pre_commit/languages/docker.py
  @@ -1,7 +1,6 @@
   import hashlib
   import json
   import os
  -import socket
   from typing import Sequence
   from typing import Tuple
   
  @@ -26,12 +25,24 @@
           return False
   
   
  +def _get_container_id() -> str:
  +    # It's assumed that we already check /proc/1/cgroup in _is_in_docker. The
  +    # cpuset cgroup controller existed since cgroups were introduced so this
  +    # way of getting the container ID is pretty reliable.
  +    with open('/proc/1/cgroup', 'rb') as f:
  +        for line in f.readlines():
  +            if line.split(b':')[1] == b'cpuset':
  +                return os.path.basename(line.split(b':')[2]).strip().decode()
  +    raise RuntimeError('Failed to find the container ID in /proc/1/cgroup.')
  +
  +
   def _get_docker_path(path: str) -> str:
       if not _is_in_docker():
           return path
  -    hostname = socket.gethostname()
   
  -    _, out, _ = cmd_output_b('docker', 'inspect', hostname)
  +    container_id = _get_container_id()
  +
  +    _, out, _ = cmd_output_b('docker', 'inspect', container_id)
   
       container, = json.loads(out)
       for mount in container['Mounts']:
  
verification_info: {"golden_diff": "diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py\n--- a/pre_commit/languages/docker.py\n+++ b/pre_commit/languages/docker.py\n@@ -1,7 +1,6 @@\n import hashlib\n import json\n import os\n-import socket\n from typing import Sequence\n from typing import Tuple\n \n@@ -26,12 +25,24 @@\n         return False\n \n \n+def _get_container_id() -> str:\n+    # It's assumed that we already check /proc/1/cgroup in _is_in_docker. The\n+    # cpuset cgroup controller existed since cgroups were introduced so this\n+    # way of getting the container ID is pretty reliable.\n+    with open('/proc/1/cgroup', 'rb') as f:\n+        for line in f.readlines():\n+            if line.split(b':')[1] == b'cpuset':\n+                return os.path.basename(line.split(b':')[2]).strip().decode()\n+    raise RuntimeError('Failed to find the container ID in /proc/1/cgroup.')\n+\n+\n def _get_docker_path(path: str) -> str:\n     if not _is_in_docker():\n         return path\n-    hostname = socket.gethostname()\n \n-    _, out, _ = cmd_output_b('docker', 'inspect', hostname)\n+    container_id = _get_container_id()\n+\n+    _, out, _ = cmd_output_b('docker', 'inspect', container_id)\n \n     container, = json.loads(out)\n     for mount in container['Mounts']:\n", "issue": "Failure to get the container id\nWith #1888 in place, there's a regression when running inside a container. There's an assumption in https://github.com/pre-commit/pre-commit/blob/master/pre_commit/languages/docker.py#L32 that the hostname is the container ID which is not always the case (it's easy enough to set a different hostname with `docker run --hostname foo`). It causes the `docker inspect` command that follows to fail. A more reliable way to get the container id is from `/proc/1/cpuset` or from the first line in `/proc/1/cgroup` which is already checked in `_is_in_docker`.\r\n\r\nThanks @asottile and @okainov for your work on #1387 and pre-commit in general.\n", "before_files": [{"content": "import hashlib\nimport json\nimport os\nimport socket\nfrom typing import Sequence\nfrom typing import Tuple\n\nimport pre_commit.constants as C\nfrom pre_commit.hook import Hook\nfrom pre_commit.languages import helpers\nfrom pre_commit.prefix import Prefix\nfrom pre_commit.util import clean_path_on_failure\nfrom pre_commit.util import cmd_output_b\n\nENVIRONMENT_DIR = 'docker'\nPRE_COMMIT_LABEL = 'PRE_COMMIT'\nget_default_version = helpers.basic_get_default_version\nhealthy = helpers.basic_healthy\n\n\ndef _is_in_docker() -> bool:\n    try:\n        with open('/proc/1/cgroup', 'rb') as f:\n            return b'docker' in f.read()\n    except FileNotFoundError:\n        return False\n\n\ndef _get_docker_path(path: str) -> str:\n    if not _is_in_docker():\n        return path\n    hostname = socket.gethostname()\n\n    _, out, _ = cmd_output_b('docker', 'inspect', hostname)\n\n    container, = json.loads(out)\n    for mount in container['Mounts']:\n        src_path = mount['Source']\n        to_path = mount['Destination']\n        if os.path.commonpath((path, to_path)) == to_path:\n            # So there is something in common,\n            # and we can proceed remapping it\n            return path.replace(to_path, src_path)\n    # we're in Docker, but the path is not mounted, cannot really do anything,\n    # so fall back to original path\n    return path\n\n\ndef md5(s: str) -> str:  # pragma: win32 no cover\n    return hashlib.md5(s.encode()).hexdigest()\n\n\ndef docker_tag(prefix: Prefix) -> str:  # pragma: win32 no cover\n    md5sum = md5(os.path.basename(prefix.prefix_dir)).lower()\n    return f'pre-commit-{md5sum}'\n\n\ndef build_docker_image(\n        prefix: Prefix,\n        *,\n        pull: bool,\n) -> None:  # pragma: win32 no cover\n    cmd: Tuple[str, ...] = (\n        'docker', 'build',\n        '--tag', docker_tag(prefix),\n        '--label', PRE_COMMIT_LABEL,\n    )\n    if pull:\n        cmd += ('--pull',)\n    # This must come last for old versions of docker.  See #477\n    cmd += ('.',)\n    helpers.run_setup_cmd(prefix, cmd)\n\n\ndef install_environment(\n        prefix: Prefix, version: str, additional_dependencies: Sequence[str],\n) -> None:  # pragma: win32 no cover\n    helpers.assert_version_default('docker', version)\n    helpers.assert_no_additional_deps('docker', additional_dependencies)\n\n    directory = prefix.path(\n        helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),\n    )\n\n    # Docker doesn't really have relevant disk environment, but pre-commit\n    # still needs to cleanup its state files on failure\n    with clean_path_on_failure(directory):\n        build_docker_image(prefix, pull=True)\n        os.mkdir(directory)\n\n\ndef get_docker_user() -> Tuple[str, ...]:  # pragma: win32 no cover\n    try:\n        return ('-u', f'{os.getuid()}:{os.getgid()}')\n    except AttributeError:\n        return ()\n\n\ndef docker_cmd() -> Tuple[str, ...]:  # pragma: win32 no cover\n    return (\n        'docker', 'run',\n        '--rm',\n        *get_docker_user(),\n        # https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from\n        # The `Z` option tells Docker to label the content with a private\n        # unshared label. Only the current container can use a private volume.\n        '-v', f'{_get_docker_path(os.getcwd())}:/src:rw,Z',\n        '--workdir', '/src',\n    )\n\n\ndef run_hook(\n        hook: Hook,\n        file_args: Sequence[str],\n        color: bool,\n) -> Tuple[int, bytes]:  # pragma: win32 no cover\n    # Rebuild the docker image in case it has gone missing, as many people do\n    # automated cleanup of docker images.\n    build_docker_image(hook.prefix, pull=False)\n\n    entry_exe, *cmd_rest = hook.cmd\n\n    entry_tag = ('--entrypoint', entry_exe, docker_tag(hook.prefix))\n    cmd = (*docker_cmd(), *entry_tag, *cmd_rest)\n    return helpers.run_xargs(hook, cmd, file_args, color=color)\n", "path": "pre_commit/languages/docker.py"}], "after_files": [{"content": "import hashlib\nimport json\nimport os\nfrom typing import Sequence\nfrom typing import Tuple\n\nimport pre_commit.constants as C\nfrom pre_commit.hook import Hook\nfrom pre_commit.languages import helpers\nfrom pre_commit.prefix import Prefix\nfrom pre_commit.util import clean_path_on_failure\nfrom pre_commit.util import cmd_output_b\n\nENVIRONMENT_DIR = 'docker'\nPRE_COMMIT_LABEL = 'PRE_COMMIT'\nget_default_version = helpers.basic_get_default_version\nhealthy = helpers.basic_healthy\n\n\ndef _is_in_docker() -> bool:\n    try:\n        with open('/proc/1/cgroup', 'rb') as f:\n            return b'docker' in f.read()\n    except FileNotFoundError:\n        return False\n\n\ndef _get_container_id() -> str:\n    # It's assumed that we already check /proc/1/cgroup in _is_in_docker. The\n    # cpuset cgroup controller existed since cgroups were introduced so this\n    # way of getting the container ID is pretty reliable.\n    with open('/proc/1/cgroup', 'rb') as f:\n        for line in f.readlines():\n            if line.split(b':')[1] == b'cpuset':\n                return os.path.basename(line.split(b':')[2]).strip().decode()\n    raise RuntimeError('Failed to find the container ID in /proc/1/cgroup.')\n\n\ndef _get_docker_path(path: str) -> str:\n    if not _is_in_docker():\n        return path\n\n    container_id = _get_container_id()\n\n    _, out, _ = cmd_output_b('docker', 'inspect', container_id)\n\n    container, = json.loads(out)\n    for mount in container['Mounts']:\n        src_path = mount['Source']\n        to_path = mount['Destination']\n        if os.path.commonpath((path, to_path)) == to_path:\n            # So there is something in common,\n            # and we can proceed remapping it\n            return path.replace(to_path, src_path)\n    # we're in Docker, but the path is not mounted, cannot really do anything,\n    # so fall back to original path\n    return path\n\n\ndef md5(s: str) -> str:  # pragma: win32 no cover\n    return hashlib.md5(s.encode()).hexdigest()\n\n\ndef docker_tag(prefix: Prefix) -> str:  # pragma: win32 no cover\n    md5sum = md5(os.path.basename(prefix.prefix_dir)).lower()\n    return f'pre-commit-{md5sum}'\n\n\ndef build_docker_image(\n        prefix: Prefix,\n        *,\n        pull: bool,\n) -> None:  # pragma: win32 no cover\n    cmd: Tuple[str, ...] = (\n        'docker', 'build',\n        '--tag', docker_tag(prefix),\n        '--label', PRE_COMMIT_LABEL,\n    )\n    if pull:\n        cmd += ('--pull',)\n    # This must come last for old versions of docker.  See #477\n    cmd += ('.',)\n    helpers.run_setup_cmd(prefix, cmd)\n\n\ndef install_environment(\n        prefix: Prefix, version: str, additional_dependencies: Sequence[str],\n) -> None:  # pragma: win32 no cover\n    helpers.assert_version_default('docker', version)\n    helpers.assert_no_additional_deps('docker', additional_dependencies)\n\n    directory = prefix.path(\n        helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT),\n    )\n\n    # Docker doesn't really have relevant disk environment, but pre-commit\n    # still needs to cleanup its state files on failure\n    with clean_path_on_failure(directory):\n        build_docker_image(prefix, pull=True)\n        os.mkdir(directory)\n\n\ndef get_docker_user() -> Tuple[str, ...]:  # pragma: win32 no cover\n    try:\n        return ('-u', f'{os.getuid()}:{os.getgid()}')\n    except AttributeError:\n        return ()\n\n\ndef docker_cmd() -> Tuple[str, ...]:  # pragma: win32 no cover\n    return (\n        'docker', 'run',\n        '--rm',\n        *get_docker_user(),\n        # https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from\n        # The `Z` option tells Docker to label the content with a private\n        # unshared label. Only the current container can use a private volume.\n        '-v', f'{_get_docker_path(os.getcwd())}:/src:rw,Z',\n        '--workdir', '/src',\n    )\n\n\ndef run_hook(\n        hook: Hook,\n        file_args: Sequence[str],\n        color: bool,\n) -> Tuple[int, bytes]:  # pragma: win32 no cover\n    # Rebuild the docker image in case it has gone missing, as many people do\n    # automated cleanup of docker images.\n    build_docker_image(hook.prefix, pull=False)\n\n    entry_exe, *cmd_rest = hook.cmd\n\n    entry_tag = ('--entrypoint', entry_exe, docker_tag(hook.prefix))\n    cmd = (*docker_cmd(), *entry_tag, *cmd_rest)\n    return helpers.run_xargs(hook, cmd, file_args, color=color)\n", "path": "pre_commit/languages/docker.py"}]}