|
| 1 | +# SPDX-FileCopyrightText: 2024-present Datadog, Inc. <dev@datadoghq.com> |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | +from __future__ import annotations |
| 5 | + |
| 6 | +import contextlib |
| 7 | +import os |
| 8 | +import sys |
| 9 | +from functools import cache |
| 10 | +from typing import TYPE_CHECKING |
| 11 | + |
| 12 | +if TYPE_CHECKING: |
| 13 | + from uuid import UUID |
| 14 | + |
| 15 | + |
| 16 | +if sys.platform == "win32": |
| 17 | + __PLATFORM_ID = "windows" |
| 18 | + __PLATFORM_NAME = "Windows" |
| 19 | + __DEFAULT_SHELL = os.environ.get("SHELL", os.environ.get("COMSPEC", "cmd")) |
| 20 | + |
| 21 | + def __join_command_args(args: list[str]) -> str: |
| 22 | + import subprocess |
| 23 | + |
| 24 | + return subprocess.list2cmdline(args) |
| 25 | + |
| 26 | + def __get_machine_id() -> UUID | None: |
| 27 | + import winreg |
| 28 | + from uuid import UUID |
| 29 | + |
| 30 | + with winreg.OpenKey( |
| 31 | + winreg.HKEY_LOCAL_MACHINE, |
| 32 | + r"SOFTWARE\Microsoft\Cryptography", |
| 33 | + 0, |
| 34 | + winreg.KEY_READ | winreg.KEY_WOW64_64KEY, |
| 35 | + ) as key: |
| 36 | + value, _ = winreg.QueryValueEx(key, "MachineGuid") |
| 37 | + return UUID(value) |
| 38 | + |
| 39 | + |
| 40 | +elif sys.platform == "darwin": |
| 41 | + __PLATFORM_ID = "macos" |
| 42 | + __PLATFORM_NAME = "macOS" |
| 43 | + __DEFAULT_SHELL = os.environ.get("SHELL", "zsh") |
| 44 | + |
| 45 | + def __join_command_args(args: list[str]) -> str: |
| 46 | + import shlex |
| 47 | + |
| 48 | + return shlex.join(args) |
| 49 | + |
| 50 | + def __get_machine_id() -> UUID | None: |
| 51 | + import re |
| 52 | + import subprocess |
| 53 | + from uuid import UUID |
| 54 | + |
| 55 | + process = subprocess.run( |
| 56 | + ["ioreg", "-c", "IOPlatformExpertDevice", "-d2"], # noqa: S607 |
| 57 | + stdout=subprocess.PIPE, |
| 58 | + stderr=subprocess.STDOUT, |
| 59 | + check=True, |
| 60 | + ) |
| 61 | + match = re.search(rb'"IOPlatformUUID"\s*=\s*"([^"]+)"', process.stdout) |
| 62 | + return UUID(match.group(1).decode("utf-8")) if match else None |
| 63 | + |
| 64 | +else: |
| 65 | + __PLATFORM_ID = "linux" |
| 66 | + __PLATFORM_NAME = "Linux" |
| 67 | + __DEFAULT_SHELL = os.environ.get("SHELL", "bash") |
| 68 | + |
| 69 | + def __join_command_args(args: list[str]) -> str: |
| 70 | + import shlex |
| 71 | + |
| 72 | + return shlex.join(args) |
| 73 | + |
| 74 | + def __get_machine_id() -> UUID | None: |
| 75 | + from uuid import UUID |
| 76 | + |
| 77 | + for path in ( |
| 78 | + # https://utcc.utoronto.ca/~cks/space/blog/linux/DMIDataInSysfs |
| 79 | + # Section 7.2.1 of https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf |
| 80 | + # https://cloud.google.com/compute/docs/instances/get-uuid#linux |
| 81 | + "/sys/class/dmi/id/product_uuid", |
| 82 | + # https://www.freedesktop.org/software/systemd/man/latest/machine-id.html |
| 83 | + "/etc/machine-id", |
| 84 | + # https://wiki.debian.org/MachineId |
| 85 | + "/var/lib/dbus/machine-id", |
| 86 | + ): |
| 87 | + with contextlib.suppress(Exception), open(path, encoding="utf-8") as f: |
| 88 | + return UUID(f.read().strip()) |
| 89 | + |
| 90 | + return None |
| 91 | + |
| 92 | + |
| 93 | +PLATFORM_ID = __PLATFORM_ID |
| 94 | +""" |
| 95 | +A short identifier for the current platform. Known values: |
| 96 | +
|
| 97 | +- `linux` |
| 98 | +- `windows` |
| 99 | +- `macos` |
| 100 | +""" |
| 101 | +PLATFORM_NAME = __PLATFORM_NAME |
| 102 | +""" |
| 103 | +The human readable name of the current platform. Known values: |
| 104 | +
|
| 105 | +- Linux |
| 106 | +- Windows |
| 107 | +- macOS |
| 108 | +""" |
| 109 | +DEFAULT_SHELL = __DEFAULT_SHELL |
| 110 | +""" |
| 111 | +The default shell for the current platform. Values are taken from environment variables, with |
| 112 | +platform-specific fallbacks. |
| 113 | +
|
| 114 | +Platform | Environment variables | Fallback |
| 115 | +--- | --- | --- |
| 116 | +`linux` | `SHELL` | `bash` |
| 117 | +`windows` | `SHELL`, `COMSPEC` | `cmd` |
| 118 | +`macos` | `SHELL` | `zsh` |
| 119 | +""" |
| 120 | + |
| 121 | + |
| 122 | +def join_command_args(args: list[str]) -> str: |
| 123 | + """ |
| 124 | + Create a valid shell command from a list of arguments. |
| 125 | +
|
| 126 | + Parameters: |
| 127 | + args: A list of command line arguments. |
| 128 | +
|
| 129 | + Returns: |
| 130 | + A single string of command line arguments. |
| 131 | + """ |
| 132 | + return __join_command_args(args) |
| 133 | + |
| 134 | + |
| 135 | +@cache |
| 136 | +def get_machine_id() -> UUID: |
| 137 | + """ |
| 138 | + Get a unique identifier for the current machine that is consistent across reboots and different |
| 139 | + processes. The following platform-specific methods are given priority: |
| 140 | +
|
| 141 | + Platform | Method |
| 142 | + --- | --- |
| 143 | + `linux` | The [`/sys/class/dmi/id/product_uuid`](https://utcc.utoronto.ca/~cks/space/blog/linux/DMIDataInSysfs), [`/etc/machine-id`](https://www.freedesktop.org/software/systemd/man/latest/machine-id.html) or [`/var/lib/dbus/machine-id`](https://wiki.debian.org/MachineId) files |
| 144 | + `windows` | The `HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\MachineGuid` registry key |
| 145 | + `macos` | The [`IOPlatformUUID`](https://developer.apple.com/documentation/iokit/kioplatformuuidkey/) property of the [`IOPlatformExpertDevice`](https://developer.apple.com/library/archive/technotes/tn1103/_index.html) node in the [I/O Registry](https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/TheRegistry/TheRegistry.html#//apple_ref/doc/uid/TP0000014-TP9) |
| 146 | +
|
| 147 | + As a fallback, the ID will be generated using the MAC address. |
| 148 | + """ |
| 149 | + machine_id: UUID | None = None |
| 150 | + with contextlib.suppress(Exception): |
| 151 | + machine_id = __get_machine_id() |
| 152 | + |
| 153 | + if machine_id is not None: |
| 154 | + return machine_id |
| 155 | + |
| 156 | + import uuid |
| 157 | + |
| 158 | + return uuid.uuid5(uuid.NAMESPACE_DNS, str(uuid.getnode())) |
0 commit comments