Repository: scoutapp/scout_apm_python

Issue:
set /tmp/scout_apm_core directory perms from config
Right now, directory permissions are set with:

https://github.com/scoutapp/scout_apm_python/blob/cab4f2218cfada275a45c355269e61a8945fa581/src/scout_apm/core/core_agent_manager.py#L144

Which obviously results in `700` perms on `/tmp/scout_apm_core/*`.

In my setup, this ends up requiring `root` to clear these files.  If I could set the perms to `770` from a configuration option, it would make it a lot easier to clear these files out.

Comments:

Patch:
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 7901ca5..501be10 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -89,7 +89,7 @@ for example 2.7 and 3.7. In that case, do the whole setup for each virtualenv.
 
 Install all test dependencies:
 
-    $ pip install bottle celery Django elasticsearch flask flask-sqlalchemy jinja2 mock psutil pymongo pyramid pytest pytest-travis-fold pytest-cov PyYAML redis requests sqlalchemy urllib3 webtest
+    $ pip install bottle celery Django elasticsearch flask flask-sqlalchemy jinja2 mock psutil pymongo pyramid pytest pytest-travis-fold pytest-cov redis requests sqlalchemy urllib3 webtest
 
 Run tests with:
 
diff --git a/requirements.txt b/requirements.txt
index bc5d83f..75d1bd2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,2 @@
 psutil
-PyYAML
 requests
diff --git a/setup.py b/setup.py
index 790d135..143fa10 100644
--- a/setup.py
+++ b/setup.py
@@ -37,7 +37,7 @@ setup_args = {
             "core-agent-manager = scout_apm.core.cli.core_agent_manager:main"
         ]
     },
-    "install_requires": ["psutil", "PyYAML", "requests"],
+    "install_requires": ["psutil", "requests"],
     "keywords": "apm performance monitoring development",
     "classifiers": [
         "Development Status :: 5 - Production/Stable",
diff --git a/src/scout_apm/core/config.py b/src/scout_apm/core/config.py
index 56b60c7..9c6be33 100644
--- a/src/scout_apm/core/config.py
+++ b/src/scout_apm/core/config.py
@@ -6,6 +6,7 @@ import sys
 
 from scout_apm.core.git_revision import GitRevision
 from scout_apm.core.platform_detection import PlatformDetection
+from scout_apm.core.util import octal
 
 logger = logging.getLogger(__name__)
 
@@ -58,6 +59,7 @@ class ScoutConfig(object):
             "core_agent_dir",
             "core_agent_download",
             "core_agent_launch",
+            "core_agent_permissions",
             "core_agent_version",
             "disabled_instruments",
             "download_url",
@@ -73,6 +75,17 @@ class ScoutConfig(object):
             "socket_path",
         ]
 
+    def core_agent_permissions(self):
+        try:
+            return octal(self.value("core_agent_permissions"))
+        except ValueError as e:
+            logger.error(
+                "Invalid core_agent_permissions value: %s." " Using default: %s",
+                repr(e),
+                0o700,
+            )
+            return 0o700
+
     @classmethod
     def set(cls, **kwargs):
         """
@@ -195,6 +208,7 @@ class ScoutConfigDefaults(object):
             "core_agent_dir": "/tmp/scout_apm_core",
             "core_agent_download": True,
             "core_agent_launch": True,
+            "core_agent_permissions": 700,
             "core_agent_version": "v1.1.8",  # can be an exact tag name, or 'latest'
             "disabled_instruments": [],
             "download_url": "https://s3-us-west-1.amazonaws.com/scout-public-downloads/apm_core_agent/release",  # noqa: E501
diff --git a/src/scout_apm/core/core_agent_manager.py b/src/scout_apm/core/core_agent_manager.py
index d40e7d7..eb096df 100644
--- a/src/scout_apm/core/core_agent_manager.py
+++ b/src/scout_apm/core/core_agent_manager.py
@@ -146,7 +146,9 @@ class CoreAgentDownloader(object):
 
     def create_core_agent_dir(self):
         try:
-            os.makedirs(self.destination, 0o700)
+            os.makedirs(
+                self.destination, AgentContext.instance.config.core_agent_permissions()
+            )
         except OSError:
             pass
 
diff --git a/src/scout_apm/core/util.py b/src/scout_apm/core/util.py
new file mode 100644
index 0000000..f4d233b
--- /dev/null
+++ b/src/scout_apm/core/util.py
@@ -0,0 +1,8 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+
+# Takes an integer or a string containing an integer and returns
+# the octal value.
+# Raises a ValueError if the value cannot be converted to octal.
+def octal(value):
+    return int("{}".format(value), 8)
diff --git a/tox.ini b/tox.ini
index 76eaf53..6e54753 100644
--- a/tox.ini
+++ b/tox.ini
@@ -42,7 +42,6 @@ deps =
     pytest
     pytest-travis-fold
     pytest-cov
-    PyYAML
     redis
     requests
     sqlalchemy


Test patch:
diff --git a/tests/unit/core/test_config.py b/tests/unit/core/test_config.py
index c8ebfca..ed0535d 100644
--- a/tests/unit/core/test_config.py
+++ b/tests/unit/core/test_config.py
@@ -76,6 +76,29 @@ def test_log_config():
         ScoutConfig.reset_all()
 
 
+def test_core_agent_permissions_default():
+    config = ScoutConfig()
+    assert 0o700 == config.core_agent_permissions()
+
+
+def test_core_agent_permissions_custom():
+    ScoutConfig.set(core_agent_permissions=770)
+    config = ScoutConfig()
+    try:
+        assert 0o770 == config.core_agent_permissions()
+    finally:
+        ScoutConfig.reset_all()
+
+
+def test_core_agent_permissions_invalid_uses_default():
+    ScoutConfig.set(core_agent_permissions="THIS IS INVALID")
+    config = ScoutConfig()
+    try:
+        assert 0o700 == config.core_agent_permissions()
+    finally:
+        ScoutConfig.reset_all()
+
+
 def test_null_config_name():
     # For coverage... this is never called elsewhere.
     ScoutConfigNull().name()
diff --git a/tests/unit/core/test_util.py b/tests/unit/core/test_util.py
new file mode 100644
index 0000000..42e6f7a
--- /dev/null
+++ b/tests/unit/core/test_util.py
@@ -0,0 +1,18 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+from scout_apm.core.util import octal
+
+
+def test_octal_with_valid_integer():
+    assert 0o700 == octal(700)
+
+
+def test_octal_with_valid_string():
+    assert 0o700 == octal("700")
+
+
+def test_octal_raises_valueerror_on_invalid_value():
+    try:
+        octal("THIS IS INVALID")
+    except ValueError:
+        pass