Repository: dynaconf/dynaconf

Issue:
[bug] Dynaconf searches for a file in various directories even when the absolute path is provided
Dynaconf searches for a file in various directories even when the absolute path is provided.
It can cause the issue when the user performing the command does not have access to a directory dynaconf is searching in.

To reproduce:
 - run any django-admin command from a directory where the user performing a command do not have access to
 - provide an absolute path to your configuration (`/etc/pulp/settings.py` in the example below)

E.g.
```
# pwd
/root/somedir
# sudo -u pulp stat /root/somedir
stat: cannot stat ‘/root/somedir’: Permission denied
# sudo -u pulp DJANGO_SETTINGS_MODULE=pulpcore.app.settings PULP_SETTINGS=/etc/pulp/settings.py pulpcore-manager

Traceback (most recent call last):
  File "/bin/pulpcore-manager", line 11, in <module>
    load_entry_point('pulpcore==3.7.4', 'console_scripts', 'pulpcore-manager')()
  File "/usr/lib/python3.6/site-packages/pulpcore/app/manage.py", line 11, in manage
    execute_from_command_line(sys.argv)
  File "/usr/lib/python3.6/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/usr/lib/python3.6/site-packages/django/core/management/__init__.py", line 325, in execute
    settings.INSTALLED_APPS
  File "/usr/lib/python3.6/site-packages/django/conf/__init__.py", line 79, in __getattr__
    self._setup(name)
  File "/usr/lib/python3.6/site-packages/django/conf/__init__.py", line 66, in _setup
    self._wrapped = Settings(settings_module)
  File "/usr/lib/python3.6/site-packages/django/conf/__init__.py", line 157, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/usr/lib/python3.6/site-packages/pulpcore/app/settings.py", line 249, in <module>
    load_dotenv=False,
  File "/usr/lib/python3.6/site-packages/dynaconf/contrib/django_dynaconf_v2.py", line 76, in load
    lazy_settings.populate_obj(django_settings_module)
  File "/usr/lib/python3.6/site-packages/dynaconf/base.py", line 141, in __getattr__
    self._setup()
  File "/usr/lib/python3.6/site-packages/dynaconf/base.py", line 192, in _setup
    settings_module=settings_module, **self._kwargs
  File "/usr/lib/python3.6/site-packages/dynaconf/base.py", line 255, in __init__
    self.execute_loaders()
  File "/usr/lib/python3.6/site-packages/dynaconf/base.py", line 952, in execute_loaders
    self, env=env, silent=silent, key=key, filename=filename
  File "/usr/lib/python3.6/site-packages/dynaconf/loaders/__init__.py", line 92, in settings_loader
    found = obj.find_file(item, project_root=p_root)
  File "/usr/lib/python3.6/site-packages/dynaconf/base.py", line 1067, in find_file
    return find_file(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/dynaconf/utils/files.py", line 60, in find_file
    search_tree.extend(_walk_to_root(work_dir))
  File "/usr/lib/python3.6/site-packages/dynaconf/utils/files.py", line 13, in _walk_to_root
    raise IOError("Starting path not found")
```

FWIW, here is dynaconf usage in [Pulp's settings.py](https://github.com/pulp/pulpcore/blob/5d63fc9ad78dfb63c68a52c28f21703a90164b08/pulpcore/app/settings.py#L272-L281):
```
 settings = dynaconf.DjangoDynaconf(
    __name__,
    GLOBAL_ENV_FOR_DYNACONF="PULP",
    ENV_SWITCHER_FOR_DYNACONF="PULP_ENV",
    PRELOAD_FOR_DYNACONF=[
        "{}.app.settings".format(plugin_name) for plugin_name in INSTALLED_PULP_PLUGINS
    ],
    ENVVAR_FOR_DYNACONF="PULP_SETTINGS",
    load_dotenv=False,
)
```

Dynaconf should not search anywhere if the absolute path is provided.




Discussion:

Resolving patch:
diff --git a/dynaconf/utils/files.py b/dynaconf/utils/files.py
--- a/dynaconf/utils/files.py
+++ b/dynaconf/utils/files.py
@@ -48,6 +48,12 @@ def find_file(filename=".env", project_root=None, skip_files=None, **kwargs):
     work_dir = os.getcwd()
     skip_files = skip_files or []
 
+    # If filename is an absolute path and exists, just return it
+    # if the absolute path does not exist, return empty string so
+    # that it can be joined and avoid IoError
+    if os.path.isabs(filename):
+        return filename if os.path.exists(filename) else ""
+
     if project_root is not None:
         search_tree.extend(_walk_to_root(project_root, break_at=work_dir))
 


Test patch:
diff --git a/tests/test_django.py b/tests/test_django.py
--- a/tests/test_django.py
+++ b/tests/test_django.py
@@ -5,11 +5,11 @@
 
 
 def test_djdt_382(tmpdir):
-    sys.path.insert(0, str(tmpdir))
     settings_file = tmpdir.join("settings.py")
-    settings_file.write("SECRET_KEY = 'dasfadfds'")
+    settings_file.write("SECRET_KEY = 'dasfadfds2'")
     tmpdir.join("__init__.py").write("")
-    os.environ["DJANGO_SETTINGS_MODULE"] = str("settings")
+    os.environ["DJANGO_SETTINGS_MODULE"] = "settings"
     settings = dynaconf.DjangoDynaconf(__name__, environments=True)
-    settings.configure(settings_module=__file__)
+    settings.configure(settings_module="settings")
+    assert settings.SECRET_KEY == "dasfadfds2"
     assert settings.is_overridden("FOO") is False
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -82,6 +82,12 @@ def test_find_file(tmpdir):
     child4 = dirs[-1]
 
     assert find_file("file-does-not-exist") == ""
+    assert find_file("/abs-file-does-not-exist") == ""
+
+    for _dir in dirs:
+        # search for abspath return the same path
+        assert os.path.isabs(_dir)
+        assert find_file(_dir) == _dir
 
     # now place a .env file a few levels up and make sure it's found
     filename = os.path.join(str(child4), ".env")