We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Warn about wrong values for SCOUT_AGENT_TRIPLE
Follow-on to #239 - it's easy to override `SCOUT_AGENT_TRIPLE` to a wrong value. We should add some validation here with a log message if it looks wrong.

--- 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/scout_apm/core/platform_detection.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import platform
5 import subprocess
6 
7 
8 def get_triple():
9     return "{arch}-{platform}".format(arch=get_arch(), platform=get_platform())
10 
11 
12 def get_arch():
13     """
14     What CPU are we on?
15     """
16     arch = platform.machine()
17     if arch == "i686":
18         return "i686"
19     elif arch == "x86_64":
20         return "x86_64"
21     else:
22         return "unknown"
23 
24 
25 def get_platform():
26     """
27     What Operating System (and sub-system like glibc / musl)
28     """
29     system_name = platform.system()
30     if system_name == "Linux":
31         libc = get_libc()
32         return "unknown-linux-{libc}".format(libc=libc)
33     elif system_name == "Darwin":
34         return "apple-darwin"
35     else:
36         return "unknown"
37 
38 
39 _libc = None
40 
41 
42 def get_libc():
43     """
44     Alpine linux uses a non glibc version of the standard library, it uses
45     the stripped down musl instead. The core agent can be built against it,
46     but which one is running must be detected. Shelling out to `ldd`
47     appears to be the most reliable way to do this.
48     """
49     global _libc
50     if _libc is None:
51         try:
52             output = subprocess.check_output(
53                 ["ldd", "--version"], stderr=subprocess.STDOUT, close_fds=True
54             )
55         except (OSError, subprocess.CalledProcessError):
56             _libc = "gnu"
57         else:
58             if b"musl" in output:
59                 _libc = "musl"
60             else:
61                 _libc = "gnu"
62     return _libc
63
```

Path: `src/scout_apm/core/config.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import logging
5 import os
6 
7 from scout_apm.compat import string_type
8 from scout_apm.core import platform_detection
9 from scout_apm.core.util import octal
10 
11 logger = logging.getLogger(__name__)
12 
13 
14 class ScoutConfig(object):
15     """
16     Configuration object for the ScoutApm agent.
17 
18     Contains a list of configuration "layers". When a configuration key is
19     looked up, each layer is asked in turn if it knows the value. The first one
20     to answer affirmatively returns the value.
21     """
22 
23     def __init__(self):
24         self.layers = [
25             ScoutConfigEnv(),
26             ScoutConfigPython(),
27             ScoutConfigDerived(self),
28             ScoutConfigDefaults(),
29             ScoutConfigNull(),
30         ]
31 
32     def value(self, key):
33         value = self.locate_layer_for_key(key).value(key)
34         if key in CONVERSIONS:
35             return CONVERSIONS[key](value)
36         return value
37 
38     def locate_layer_for_key(self, key):
39         for layer in self.layers:
40             if layer.has_config(key):
41                 return layer
42 
43         # Should be unreachable because ScoutConfigNull returns None for all
44         # keys.
45         raise ValueError("key {!r} not found in any layer".format(key))
46 
47     def log(self):
48         logger.debug("Configuration Loaded:")
49         for key in self.known_keys():
50             layer = self.locate_layer_for_key(key)
51             logger.debug("%-9s: %s = %s", layer.name(), key, layer.value(key))
52 
53     def known_keys(self):
54         return [
55             "app_server",
56             "application_root",
57             "core_agent_dir",
58             "core_agent_download",
59             "core_agent_launch",
60             "core_agent_permissions",
61             "core_agent_version",
62             "disabled_instruments",
63             "download_url",
64             "framework",
65             "framework_version",
66             "hostname",
67             "ignore",
68             "key",
69             "log_level",
70             "monitor",
71             "name",
72             "revision_sha",
73             "scm_subdirectory",
74             "socket_path",
75         ]
76 
77     def core_agent_permissions(self):
78         try:
79             return octal(self.value("core_agent_permissions"))
80         except ValueError:
81             logger.exception(
82                 "Invalid core_agent_permissions value, using default of 0o700"
83             )
84             return 0o700
85 
86     @classmethod
87     def set(cls, **kwargs):
88         """
89         Sets a configuration value for the Scout agent. Values set here will
90         not override values set in ENV.
91         """
92         global SCOUT_PYTHON_VALUES
93         for key, value in kwargs.items():
94             SCOUT_PYTHON_VALUES[key] = value
95 
96     @classmethod
97     def unset(cls, *keys):
98         """
99         Removes a configuration value for the Scout agent.
100         """
101         global SCOUT_PYTHON_VALUES
102         for key in keys:
103             SCOUT_PYTHON_VALUES.pop(key, None)
104 
105     @classmethod
106     def reset_all(cls):
107         """
108         Remove all configuration settings set via `ScoutConfig.set(...)`.
109 
110         This is meant for use in testing.
111         """
112         global SCOUT_PYTHON_VALUES
113         SCOUT_PYTHON_VALUES.clear()
114 
115 
116 # Module-level data, the ScoutConfig.set(key="value") adds to this
117 SCOUT_PYTHON_VALUES = {}
118 
119 
120 class ScoutConfigPython(object):
121     """
122     A configuration overlay that lets other parts of python set values.
123     """
124 
125     def name(self):
126         return "Python"
127 
128     def has_config(self, key):
129         return key in SCOUT_PYTHON_VALUES
130 
131     def value(self, key):
132         return SCOUT_PYTHON_VALUES[key]
133 
134 
135 class ScoutConfigEnv(object):
136     """
137     Reads configuration from environment by prefixing the key
138     requested with "SCOUT_"
139 
140     Example: the `log_level` config looks for SCOUT_LOG_LEVEL
141     environment variable
142     """
143 
144     def name(self):
145         return "ENV"
146 
147     def has_config(self, key):
148         env_key = self.modify_key(key)
149         return env_key in os.environ
150 
151     def value(self, key):
152         env_key = self.modify_key(key)
153         return os.environ[env_key]
154 
155     def modify_key(self, key):
156         env_key = ("SCOUT_" + key).upper()
157         return env_key
158 
159 
160 class ScoutConfigDerived(object):
161     """
162     A configuration overlay that calculates from other values.
163     """
164 
165     def __init__(self, config):
166         """
167         config argument is the overall ScoutConfig var, so we can lookup the
168         components of the derived info.
169         """
170         self.config = config
171 
172     def name(self):
173         return "Derived"
174 
175     def has_config(self, key):
176         return self.lookup_func(key) is not None
177 
178     def value(self, key):
179         return self.lookup_func(key)()
180 
181     def lookup_func(self, key):
182         """
183         Returns the derive_#{key} function, or None if it isn't defined
184         """
185         func_name = "derive_" + key
186         return getattr(self, func_name, None)
187 
188     def derive_socket_path(self):
189         return "{}/{}/scout-agent.sock".format(
190             self.config.value("core_agent_dir"),
191             self.config.value("core_agent_full_name"),
192         )
193 
194     def derive_core_agent_full_name(self):
195         return "{name}-{version}-{triple}".format(
196             name="scout_apm_core",
197             version=self.config.value("core_agent_version"),
198             triple=self.config.value("core_agent_triple"),
199         )
200 
201     def derive_core_agent_triple(self):
202         return platform_detection.get_triple()
203 
204 
205 class ScoutConfigDefaults(object):
206     """
207     Provides default values for important configurations
208     """
209 
210     def name(self):
211         return "Defaults"
212 
213     def __init__(self):
214         self.defaults = {
215             "app_server": "",
216             "application_root": "",
217             "core_agent_dir": "/tmp/scout_apm_core",
218             "core_agent_download": True,
219             "core_agent_launch": True,
220             "core_agent_permissions": 700,
221             "core_agent_version": "v1.2.0",  # can be an exact tag name, or 'latest'
222             "disabled_instruments": [],
223             "download_url": "https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release",  # noqa: E501
224             "framework": "",
225             "framework_version": "",
226             "hostname": None,
227             "key": "",
228             "log_level": "info",
229             "monitor": False,
230             "name": "",
231             "revision_sha": self._git_revision_sha(),
232             "scm_subdirectory": "",
233         }
234 
235     def _git_revision_sha(self):
236         # N.B. The environment variable SCOUT_REVISION_SHA may also be used,
237         # but that will be picked up by ScoutConfigEnv
238         return os.environ.get("HEROKU_SLUG_COMMIT", "")
239 
240     def has_config(self, key):
241         return key in self.defaults
242 
243     def value(self, key):
244         return self.defaults[key]
245 
246 
247 # Always returns None to any key
248 class ScoutConfigNull(object):
249     """
250     Always answers that a key is present, but the value is None
251 
252     Used as the last step of the layered configuration.
253     """
254 
255     def name(self):
256         return "Null"
257 
258     def has_config(self, key):
259         return True
260 
261     def value(self, key):
262         return None
263 
264 
265 def convert_to_bool(value):
266     if isinstance(value, bool):
267         return value
268     if isinstance(value, string_type):
269         return value.lower() in ("yes", "true", "t", "1")
270     # Unknown type - default to false?
271     return False
272 
273 
274 def convert_to_list(value):
275     if isinstance(value, list):
276         return value
277     if isinstance(value, tuple):
278         return list(value)
279     if isinstance(value, string_type):
280         # Split on commas
281         return [item.strip() for item in value.split(",") if item]
282     # Unknown type - default to empty?
283     return []
284 
285 
286 CONVERSIONS = {
287     "core_agent_download": convert_to_bool,
288     "core_agent_launch": convert_to_bool,
289     "monitor": convert_to_bool,
290     "disabled_instruments": convert_to_list,
291     "ignore": convert_to_list,
292 }
293
```
--- 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/scout_apm/core/config.py b/src/scout_apm/core/config.py
--- a/src/scout_apm/core/config.py
+++ b/src/scout_apm/core/config.py
@@ -192,10 +192,13 @@
         )
 
     def derive_core_agent_full_name(self):
+        triple = self.config.value("core_agent_triple")
+        if not platform_detection.is_valid_triple(triple):
+            logger.warning("Invalid value for core_agent_triple: %s", triple)
         return "{name}-{version}-{triple}".format(
             name="scout_apm_core",
             version=self.config.value("core_agent_version"),
-            triple=self.config.value("core_agent_triple"),
+            triple=triple,
         )
 
     def derive_core_agent_triple(self):
diff --git a/src/scout_apm/core/platform_detection.py b/src/scout_apm/core/platform_detection.py
--- a/src/scout_apm/core/platform_detection.py
+++ b/src/scout_apm/core/platform_detection.py
@@ -5,6 +5,16 @@
 import subprocess
 
 
+def is_valid_triple(triple):
+    values = triple.split("-", 1)
+    return (
+        len(values) == 2
+        and values[0] in ("i686", "x86_64", "unknown")
+        and values[1]
+        in ("unknown-linux-gnu", "unknown-linux-musl", "apple-darwin", "unknown")
+    )
+
+
 def get_triple():
     return "{arch}-{platform}".format(arch=get_arch(), platform=get_platform())

{"golden_diff": "diff --git a/src/scout_apm/core/config.py b/src/scout_apm/core/config.py\n--- a/src/scout_apm/core/config.py\n+++ b/src/scout_apm/core/config.py\n@@ -192,10 +192,13 @@\n         )\n \n     def derive_core_agent_full_name(self):\n+        triple = self.config.value(\"core_agent_triple\")\n+        if not platform_detection.is_valid_triple(triple):\n+            logger.warning(\"Invalid value for core_agent_triple: %s\", triple)\n         return \"{name}-{version}-{triple}\".format(\n             name=\"scout_apm_core\",\n             version=self.config.value(\"core_agent_version\"),\n-            triple=self.config.value(\"core_agent_triple\"),\n+            triple=triple,\n         )\n \n     def derive_core_agent_triple(self):\ndiff --git a/src/scout_apm/core/platform_detection.py b/src/scout_apm/core/platform_detection.py\n--- a/src/scout_apm/core/platform_detection.py\n+++ b/src/scout_apm/core/platform_detection.py\n@@ -5,6 +5,16 @@\n import subprocess\n \n \n+def is_valid_triple(triple):\n+    values = triple.split(\"-\", 1)\n+    return (\n+        len(values) == 2\n+        and values[0] in (\"i686\", \"x86_64\", \"unknown\")\n+        and values[1]\n+        in (\"unknown-linux-gnu\", \"unknown-linux-musl\", \"apple-darwin\", \"unknown\")\n+    )\n+\n+\n def get_triple():\n     return \"{arch}-{platform}\".format(arch=get_arch(), platform=get_platform())\n", "issue": "Warn about wrong values for SCOUT_AGENT_TRIPLE\nFollow-on to #239 - it's easy to override `SCOUT_AGENT_TRIPLE` to a wrong value. We should add some validation here with a log message if it looks wrong.\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport platform\nimport subprocess\n\n\ndef get_triple():\n    return \"{arch}-{platform}\".format(arch=get_arch(), platform=get_platform())\n\n\ndef get_arch():\n    \"\"\"\n    What CPU are we on?\n    \"\"\"\n    arch = platform.machine()\n    if arch == \"i686\":\n        return \"i686\"\n    elif arch == \"x86_64\":\n        return \"x86_64\"\n    else:\n        return \"unknown\"\n\n\ndef get_platform():\n    \"\"\"\n    What Operating System (and sub-system like glibc / musl)\n    \"\"\"\n    system_name = platform.system()\n    if system_name == \"Linux\":\n        libc = get_libc()\n        return \"unknown-linux-{libc}\".format(libc=libc)\n    elif system_name == \"Darwin\":\n        return \"apple-darwin\"\n    else:\n        return \"unknown\"\n\n\n_libc = None\n\n\ndef get_libc():\n    \"\"\"\n    Alpine linux uses a non glibc version of the standard library, it uses\n    the stripped down musl instead. The core agent can be built against it,\n    but which one is running must be detected. Shelling out to `ldd`\n    appears to be the most reliable way to do this.\n    \"\"\"\n    global _libc\n    if _libc is None:\n        try:\n            output = subprocess.check_output(\n                [\"ldd\", \"--version\"], stderr=subprocess.STDOUT, close_fds=True\n            )\n        except (OSError, subprocess.CalledProcessError):\n            _libc = \"gnu\"\n        else:\n            if b\"musl\" in output:\n                _libc = \"musl\"\n            else:\n                _libc = \"gnu\"\n    return _libc\n", "path": "src/scout_apm/core/platform_detection.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nimport os\n\nfrom scout_apm.compat import string_type\nfrom scout_apm.core import platform_detection\nfrom scout_apm.core.util import octal\n\nlogger = logging.getLogger(__name__)\n\n\nclass ScoutConfig(object):\n    \"\"\"\n    Configuration object for the ScoutApm agent.\n\n    Contains a list of configuration \"layers\". When a configuration key is\n    looked up, each layer is asked in turn if it knows the value. The first one\n    to answer affirmatively returns the value.\n    \"\"\"\n\n    def __init__(self):\n        self.layers = [\n            ScoutConfigEnv(),\n            ScoutConfigPython(),\n            ScoutConfigDerived(self),\n            ScoutConfigDefaults(),\n            ScoutConfigNull(),\n        ]\n\n    def value(self, key):\n        value = self.locate_layer_for_key(key).value(key)\n        if key in CONVERSIONS:\n            return CONVERSIONS[key](value)\n        return value\n\n    def locate_layer_for_key(self, key):\n        for layer in self.layers:\n            if layer.has_config(key):\n                return layer\n\n        # Should be unreachable because ScoutConfigNull returns None for all\n        # keys.\n        raise ValueError(\"key {!r} not found in any layer\".format(key))\n\n    def log(self):\n        logger.debug(\"Configuration Loaded:\")\n        for key in self.known_keys():\n            layer = self.locate_layer_for_key(key)\n            logger.debug(\"%-9s: %s = %s\", layer.name(), key, layer.value(key))\n\n    def known_keys(self):\n        return [\n            \"app_server\",\n            \"application_root\",\n            \"core_agent_dir\",\n            \"core_agent_download\",\n            \"core_agent_launch\",\n            \"core_agent_permissions\",\n            \"core_agent_version\",\n            \"disabled_instruments\",\n            \"download_url\",\n            \"framework\",\n            \"framework_version\",\n            \"hostname\",\n            \"ignore\",\n            \"key\",\n            \"log_level\",\n            \"monitor\",\n            \"name\",\n            \"revision_sha\",\n            \"scm_subdirectory\",\n            \"socket_path\",\n        ]\n\n    def core_agent_permissions(self):\n        try:\n            return octal(self.value(\"core_agent_permissions\"))\n        except ValueError:\n            logger.exception(\n                \"Invalid core_agent_permissions value, using default of 0o700\"\n            )\n            return 0o700\n\n    @classmethod\n    def set(cls, **kwargs):\n        \"\"\"\n        Sets a configuration value for the Scout agent. Values set here will\n        not override values set in ENV.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        for key, value in kwargs.items():\n            SCOUT_PYTHON_VALUES[key] = value\n\n    @classmethod\n    def unset(cls, *keys):\n        \"\"\"\n        Removes a configuration value for the Scout agent.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        for key in keys:\n            SCOUT_PYTHON_VALUES.pop(key, None)\n\n    @classmethod\n    def reset_all(cls):\n        \"\"\"\n        Remove all configuration settings set via `ScoutConfig.set(...)`.\n\n        This is meant for use in testing.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        SCOUT_PYTHON_VALUES.clear()\n\n\n# Module-level data, the ScoutConfig.set(key=\"value\") adds to this\nSCOUT_PYTHON_VALUES = {}\n\n\nclass ScoutConfigPython(object):\n    \"\"\"\n    A configuration overlay that lets other parts of python set values.\n    \"\"\"\n\n    def name(self):\n        return \"Python\"\n\n    def has_config(self, key):\n        return key in SCOUT_PYTHON_VALUES\n\n    def value(self, key):\n        return SCOUT_PYTHON_VALUES[key]\n\n\nclass ScoutConfigEnv(object):\n    \"\"\"\n    Reads configuration from environment by prefixing the key\n    requested with \"SCOUT_\"\n\n    Example: the `log_level` config looks for SCOUT_LOG_LEVEL\n    environment variable\n    \"\"\"\n\n    def name(self):\n        return \"ENV\"\n\n    def has_config(self, key):\n        env_key = self.modify_key(key)\n        return env_key in os.environ\n\n    def value(self, key):\n        env_key = self.modify_key(key)\n        return os.environ[env_key]\n\n    def modify_key(self, key):\n        env_key = (\"SCOUT_\" + key).upper()\n        return env_key\n\n\nclass ScoutConfigDerived(object):\n    \"\"\"\n    A configuration overlay that calculates from other values.\n    \"\"\"\n\n    def __init__(self, config):\n        \"\"\"\n        config argument is the overall ScoutConfig var, so we can lookup the\n        components of the derived info.\n        \"\"\"\n        self.config = config\n\n    def name(self):\n        return \"Derived\"\n\n    def has_config(self, key):\n        return self.lookup_func(key) is not None\n\n    def value(self, key):\n        return self.lookup_func(key)()\n\n    def lookup_func(self, key):\n        \"\"\"\n        Returns the derive_#{key} function, or None if it isn't defined\n        \"\"\"\n        func_name = \"derive_\" + key\n        return getattr(self, func_name, None)\n\n    def derive_socket_path(self):\n        return \"{}/{}/scout-agent.sock\".format(\n            self.config.value(\"core_agent_dir\"),\n            self.config.value(\"core_agent_full_name\"),\n        )\n\n    def derive_core_agent_full_name(self):\n        return \"{name}-{version}-{triple}\".format(\n            name=\"scout_apm_core\",\n            version=self.config.value(\"core_agent_version\"),\n            triple=self.config.value(\"core_agent_triple\"),\n        )\n\n    def derive_core_agent_triple(self):\n        return platform_detection.get_triple()\n\n\nclass ScoutConfigDefaults(object):\n    \"\"\"\n    Provides default values for important configurations\n    \"\"\"\n\n    def name(self):\n        return \"Defaults\"\n\n    def __init__(self):\n        self.defaults = {\n            \"app_server\": \"\",\n            \"application_root\": \"\",\n            \"core_agent_dir\": \"/tmp/scout_apm_core\",\n            \"core_agent_download\": True,\n            \"core_agent_launch\": True,\n            \"core_agent_permissions\": 700,\n            \"core_agent_version\": \"v1.2.0\",  # can be an exact tag name, or 'latest'\n            \"disabled_instruments\": [],\n            \"download_url\": \"https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release\",  # noqa: E501\n            \"framework\": \"\",\n            \"framework_version\": \"\",\n            \"hostname\": None,\n            \"key\": \"\",\n            \"log_level\": \"info\",\n            \"monitor\": False,\n            \"name\": \"\",\n            \"revision_sha\": self._git_revision_sha(),\n            \"scm_subdirectory\": \"\",\n        }\n\n    def _git_revision_sha(self):\n        # N.B. The environment variable SCOUT_REVISION_SHA may also be used,\n        # but that will be picked up by ScoutConfigEnv\n        return os.environ.get(\"HEROKU_SLUG_COMMIT\", \"\")\n\n    def has_config(self, key):\n        return key in self.defaults\n\n    def value(self, key):\n        return self.defaults[key]\n\n\n# Always returns None to any key\nclass ScoutConfigNull(object):\n    \"\"\"\n    Always answers that a key is present, but the value is None\n\n    Used as the last step of the layered configuration.\n    \"\"\"\n\n    def name(self):\n        return \"Null\"\n\n    def has_config(self, key):\n        return True\n\n    def value(self, key):\n        return None\n\n\ndef convert_to_bool(value):\n    if isinstance(value, bool):\n        return value\n    if isinstance(value, string_type):\n        return value.lower() in (\"yes\", \"true\", \"t\", \"1\")\n    # Unknown type - default to false?\n    return False\n\n\ndef convert_to_list(value):\n    if isinstance(value, list):\n        return value\n    if isinstance(value, tuple):\n        return list(value)\n    if isinstance(value, string_type):\n        # Split on commas\n        return [item.strip() for item in value.split(\",\") if item]\n    # Unknown type - default to empty?\n    return []\n\n\nCONVERSIONS = {\n    \"core_agent_download\": convert_to_bool,\n    \"core_agent_launch\": convert_to_bool,\n    \"monitor\": convert_to_bool,\n    \"disabled_instruments\": convert_to_list,\n    \"ignore\": convert_to_list,\n}\n", "path": "src/scout_apm/core/config.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport platform\nimport subprocess\n\n\ndef is_valid_triple(triple):\n    values = triple.split(\"-\", 1)\n    return (\n        len(values) == 2\n        and values[0] in (\"i686\", \"x86_64\", \"unknown\")\n        and values[1]\n        in (\"unknown-linux-gnu\", \"unknown-linux-musl\", \"apple-darwin\", \"unknown\")\n    )\n\n\ndef get_triple():\n    return \"{arch}-{platform}\".format(arch=get_arch(), platform=get_platform())\n\n\ndef get_arch():\n    \"\"\"\n    What CPU are we on?\n    \"\"\"\n    arch = platform.machine()\n    if arch == \"i686\":\n        return \"i686\"\n    elif arch == \"x86_64\":\n        return \"x86_64\"\n    else:\n        return \"unknown\"\n\n\ndef get_platform():\n    \"\"\"\n    What Operating System (and sub-system like glibc / musl)\n    \"\"\"\n    system_name = platform.system()\n    if system_name == \"Linux\":\n        libc = get_libc()\n        return \"unknown-linux-{libc}\".format(libc=libc)\n    elif system_name == \"Darwin\":\n        return \"apple-darwin\"\n    else:\n        return \"unknown\"\n\n\n_libc = None\n\n\ndef get_libc():\n    \"\"\"\n    Alpine linux uses a non glibc version of the standard library, it uses\n    the stripped down musl instead. The core agent can be built against it,\n    but which one is running must be detected. Shelling out to `ldd`\n    appears to be the most reliable way to do this.\n    \"\"\"\n    global _libc\n    if _libc is None:\n        try:\n            output = subprocess.check_output(\n                [\"ldd\", \"--version\"], stderr=subprocess.STDOUT, close_fds=True\n            )\n        except (OSError, subprocess.CalledProcessError):\n            _libc = \"gnu\"\n        else:\n            if b\"musl\" in output:\n                _libc = \"musl\"\n            else:\n                _libc = \"gnu\"\n    return _libc\n", "path": "src/scout_apm/core/platform_detection.py"}, {"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport logging\nimport os\n\nfrom scout_apm.compat import string_type\nfrom scout_apm.core import platform_detection\nfrom scout_apm.core.util import octal\n\nlogger = logging.getLogger(__name__)\n\n\nclass ScoutConfig(object):\n    \"\"\"\n    Configuration object for the ScoutApm agent.\n\n    Contains a list of configuration \"layers\". When a configuration key is\n    looked up, each layer is asked in turn if it knows the value. The first one\n    to answer affirmatively returns the value.\n    \"\"\"\n\n    def __init__(self):\n        self.layers = [\n            ScoutConfigEnv(),\n            ScoutConfigPython(),\n            ScoutConfigDerived(self),\n            ScoutConfigDefaults(),\n            ScoutConfigNull(),\n        ]\n\n    def value(self, key):\n        value = self.locate_layer_for_key(key).value(key)\n        if key in CONVERSIONS:\n            return CONVERSIONS[key](value)\n        return value\n\n    def locate_layer_for_key(self, key):\n        for layer in self.layers:\n            if layer.has_config(key):\n                return layer\n\n        # Should be unreachable because ScoutConfigNull returns None for all\n        # keys.\n        raise ValueError(\"key {!r} not found in any layer\".format(key))\n\n    def log(self):\n        logger.debug(\"Configuration Loaded:\")\n        for key in self.known_keys():\n            layer = self.locate_layer_for_key(key)\n            logger.debug(\"%-9s: %s = %s\", layer.name(), key, layer.value(key))\n\n    def known_keys(self):\n        return [\n            \"app_server\",\n            \"application_root\",\n            \"core_agent_dir\",\n            \"core_agent_download\",\n            \"core_agent_launch\",\n            \"core_agent_permissions\",\n            \"core_agent_version\",\n            \"disabled_instruments\",\n            \"download_url\",\n            \"framework\",\n            \"framework_version\",\n            \"hostname\",\n            \"ignore\",\n            \"key\",\n            \"log_level\",\n            \"monitor\",\n            \"name\",\n            \"revision_sha\",\n            \"scm_subdirectory\",\n            \"socket_path\",\n        ]\n\n    def core_agent_permissions(self):\n        try:\n            return octal(self.value(\"core_agent_permissions\"))\n        except ValueError:\n            logger.exception(\n                \"Invalid core_agent_permissions value, using default of 0o700\"\n            )\n            return 0o700\n\n    @classmethod\n    def set(cls, **kwargs):\n        \"\"\"\n        Sets a configuration value for the Scout agent. Values set here will\n        not override values set in ENV.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        for key, value in kwargs.items():\n            SCOUT_PYTHON_VALUES[key] = value\n\n    @classmethod\n    def unset(cls, *keys):\n        \"\"\"\n        Removes a configuration value for the Scout agent.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        for key in keys:\n            SCOUT_PYTHON_VALUES.pop(key, None)\n\n    @classmethod\n    def reset_all(cls):\n        \"\"\"\n        Remove all configuration settings set via `ScoutConfig.set(...)`.\n\n        This is meant for use in testing.\n        \"\"\"\n        global SCOUT_PYTHON_VALUES\n        SCOUT_PYTHON_VALUES.clear()\n\n\n# Module-level data, the ScoutConfig.set(key=\"value\") adds to this\nSCOUT_PYTHON_VALUES = {}\n\n\nclass ScoutConfigPython(object):\n    \"\"\"\n    A configuration overlay that lets other parts of python set values.\n    \"\"\"\n\n    def name(self):\n        return \"Python\"\n\n    def has_config(self, key):\n        return key in SCOUT_PYTHON_VALUES\n\n    def value(self, key):\n        return SCOUT_PYTHON_VALUES[key]\n\n\nclass ScoutConfigEnv(object):\n    \"\"\"\n    Reads configuration from environment by prefixing the key\n    requested with \"SCOUT_\"\n\n    Example: the `log_level` config looks for SCOUT_LOG_LEVEL\n    environment variable\n    \"\"\"\n\n    def name(self):\n        return \"ENV\"\n\n    def has_config(self, key):\n        env_key = self.modify_key(key)\n        return env_key in os.environ\n\n    def value(self, key):\n        env_key = self.modify_key(key)\n        return os.environ[env_key]\n\n    def modify_key(self, key):\n        env_key = (\"SCOUT_\" + key).upper()\n        return env_key\n\n\nclass ScoutConfigDerived(object):\n    \"\"\"\n    A configuration overlay that calculates from other values.\n    \"\"\"\n\n    def __init__(self, config):\n        \"\"\"\n        config argument is the overall ScoutConfig var, so we can lookup the\n        components of the derived info.\n        \"\"\"\n        self.config = config\n\n    def name(self):\n        return \"Derived\"\n\n    def has_config(self, key):\n        return self.lookup_func(key) is not None\n\n    def value(self, key):\n        return self.lookup_func(key)()\n\n    def lookup_func(self, key):\n        \"\"\"\n        Returns the derive_#{key} function, or None if it isn't defined\n        \"\"\"\n        func_name = \"derive_\" + key\n        return getattr(self, func_name, None)\n\n    def derive_socket_path(self):\n        return \"{}/{}/scout-agent.sock\".format(\n            self.config.value(\"core_agent_dir\"),\n            self.config.value(\"core_agent_full_name\"),\n        )\n\n    def derive_core_agent_full_name(self):\n        triple = self.config.value(\"core_agent_triple\")\n        if not platform_detection.is_valid_triple(triple):\n            logger.warning(\"Invalid value for core_agent_triple: %s\", triple)\n        return \"{name}-{version}-{triple}\".format(\n            name=\"scout_apm_core\",\n            version=self.config.value(\"core_agent_version\"),\n            triple=triple,\n        )\n\n    def derive_core_agent_triple(self):\n        return platform_detection.get_triple()\n\n\nclass ScoutConfigDefaults(object):\n    \"\"\"\n    Provides default values for important configurations\n    \"\"\"\n\n    def name(self):\n        return \"Defaults\"\n\n    def __init__(self):\n        self.defaults = {\n            \"app_server\": \"\",\n            \"application_root\": \"\",\n            \"core_agent_dir\": \"/tmp/scout_apm_core\",\n            \"core_agent_download\": True,\n            \"core_agent_launch\": True,\n            \"core_agent_permissions\": 700,\n            \"core_agent_version\": \"v1.2.0\",  # can be an exact tag name, or 'latest'\n            \"disabled_instruments\": [],\n            \"download_url\": \"https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release\",  # noqa: E501\n            \"framework\": \"\",\n            \"framework_version\": \"\",\n            \"hostname\": None,\n            \"key\": \"\",\n            \"log_level\": \"info\",\n            \"monitor\": False,\n            \"name\": \"\",\n            \"revision_sha\": self._git_revision_sha(),\n            \"scm_subdirectory\": \"\",\n        }\n\n    def _git_revision_sha(self):\n        # N.B. The environment variable SCOUT_REVISION_SHA may also be used,\n        # but that will be picked up by ScoutConfigEnv\n        return os.environ.get(\"HEROKU_SLUG_COMMIT\", \"\")\n\n    def has_config(self, key):\n        return key in self.defaults\n\n    def value(self, key):\n        return self.defaults[key]\n\n\n# Always returns None to any key\nclass ScoutConfigNull(object):\n    \"\"\"\n    Always answers that a key is present, but the value is None\n\n    Used as the last step of the layered configuration.\n    \"\"\"\n\n    def name(self):\n        return \"Null\"\n\n    def has_config(self, key):\n        return True\n\n    def value(self, key):\n        return None\n\n\ndef convert_to_bool(value):\n    if isinstance(value, bool):\n        return value\n    if isinstance(value, string_type):\n        return value.lower() in (\"yes\", \"true\", \"t\", \"1\")\n    # Unknown type - default to false?\n    return False\n\n\ndef convert_to_list(value):\n    if isinstance(value, list):\n        return value\n    if isinstance(value, tuple):\n        return list(value)\n    if isinstance(value, string_type):\n        # Split on commas\n        return [item.strip() for item in value.split(\",\") if item]\n    # Unknown type - default to empty?\n    return []\n\n\nCONVERSIONS = {\n    \"core_agent_download\": convert_to_bool,\n    \"core_agent_launch\": convert_to_bool,\n    \"monitor\": convert_to_bool,\n    \"disabled_instruments\": convert_to_list,\n    \"ignore\": convert_to_list,\n}\n", "path": "src/scout_apm/core/config.py"}]}