Repository: CS-SI/eodag

Issue:
configure providers.yml path
Implement a way to configure where `providers.yml` is located.
- through a environment variable

Discussion:
github-actions[bot]: <strong>Code Coverage (Ubuntu)</strong>

| File | Coverage |   |
| - | :-: | :-: |
| **All files** | `82%` | :white_check_mark: |

_Minimum allowed coverage is `70%`_

<p align="right">Generated by :monkey: cobertura-action against df350c69e0a7099fc741626e47753f892b4301bc </p>
github-actions[bot]: <strong>Code Coverage (Windows)</strong>

| File | Coverage |   |
| - | :-: | :-: |
| **All files** | `76%` | :white_check_mark: |

_Minimum allowed coverage is `70%`_

<p align="right">Generated by :monkey: cobertura-action against df350c69e0a7099fc741626e47753f892b4301bc </p>

Patch:
diff --git a/docs/getting_started_guide/configure.rst b/docs/getting_started_guide/configure.rst
index c7814106..ac0559c5 100644
--- a/docs/getting_started_guide/configure.rst
+++ b/docs/getting_started_guide/configure.rst
@@ -20,10 +20,10 @@ for products in a provider's catalog, how to download products, etc. However, us
 to complete this configuration with additional settings, such as provider credentials. Users can
 also override pre-configured settings (e.g. the download folder).
 
-YAML configuration file
-^^^^^^^^^^^^^^^^^^^^^^^
+YAML user configuration file
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-The first time ``eodag`` is used after an install a default YAML configuration file is saved
+The first time ``eodag`` is used after an install, a default YAML user configuration file is saved
 in a local directory (``~/.config/eodag/eodag.yml`` on Linux).
 
 This YAML file contains a template that shows how to complete the configuration of one or
@@ -86,8 +86,11 @@ to the configuration file. In that case it is also loaded automatically:
 Environment variable configuration
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-``eodag`` can also be configured with environment variables, which have **priority over**
-the YAML file configuration.
+Providers configuration using environment variables
+"""""""""""""""""""""""""""""""""""""""""""""""""""
+
+``eodag`` providers can also be configured with environment variables, which have **priority over**
+the YAML user configuration file.
 
 The name of the environment variables understood by ``eodag`` must follow the pattern
 ``EODAG__KEY1__KEY2__[...]__KEYN`` (note the double underscore between the keys).
@@ -116,13 +119,32 @@ Each configuration parameter can be set with an environment variable.
 
 .. note::
 
-   Setting credentials must be done according to the `provider's plugin <https://eodag.readthedocs.io/en/stable/plugins.html#plugins-available>`_ (auth | api):
+   Setting credentials must be done according to the
+   `provider's plugin <https://eodag.readthedocs.io/en/stable/plugins.html#plugins-available>`_ (auth | api):
 
    * Authentication plugin: ``EODAG__<PROVIDER>__AUTH__CREDENTIALS__<KEY>``
 
    * API plugin: ``EODAG__<PROVIDER>__API__CREDENTIALS__<KEY>``
 
-   ``<KEY>`` should be replaced with the adapted credentials key (``USERNAME``, ``PASSWORD``, ``APIKEY``, ...) according to the provider configuration template in `the YAML configuration file <https://eodag.readthedocs.io/en/stable/getting_started_guide/configure.html#yaml-configuration-file>`_.
+   ``<KEY>`` should be replaced with the adapted credentials key (``USERNAME``, ``PASSWORD``, ``APIKEY``, ...) according
+   to the provider configuration template in
+   `the YAML user configuration file\
+   <https://eodag.readthedocs.io/en/stable/getting_started_guide/configure.html#yaml-user-configuration-file>`_.
+
+
+Core configuration using environment variables
+""""""""""""""""""""""""""""""""""""""""""""""
+
+Some EODAG core settings can be overriden using environment variables:
+
+* ``EODAG_CFG_FILE`` for defining the desired path to the `user configuration file\
+  <https://eodag.readthedocs.io/en/stable/getting_started_guide/configure.html#yaml-user-configuration-file>`_
+* ``EODAG_LOCS_CFG_FILE`` for defining the desired path to the
+  `locations <https://eodag.readthedocs.io/en/stable/notebooks/api_user_guide/4_search.html#Locations-search>`_
+  configuration file
+* ``EODAG_PROVIDERS_CFG_FILE`` for defining the desired path to the providers configuration file
+* ``EODAG_EXT_PRODUCT_TYPES_CFG_FILE`` for defining the desired path to the `external product types configuration file\
+  <https://eodag.readthedocs.io/en/stable/notebooks/api_user_guide/2_providers_products_available.html#Product-types-discovery>`_
 
 CLI configuration
 ^^^^^^^^^^^^^^^^^
@@ -254,7 +276,8 @@ Use OTP through Python code
 """""""""""""""""""""""""""
 
 ``creodias`` needs a temporary 6-digits code to authenticate in addition of the ``username`` and ``password``. Check
-`Creodias documentation <https://creodias.docs.cloudferro.com/en/latest/gettingstarted/Two-Factor-Authentication-for-Creodias-Site.html>`_
+`Creodias documentation\
+<https://creodias.docs.cloudferro.com/en/latest/gettingstarted/Two-Factor-Authentication-for-Creodias-Site.html>`_
 to see how to get this code once you are registered. This OTP code will only be valid for a few seconds, so you will
 better set it dynamically in your python code instead of storing it statically in your user configuration file.
 
@@ -284,8 +307,8 @@ will be stored and used if further authentication tries fail:
    dag._plugins_manager.get_auth_plugin("creodias").authenticate()
 
 Please note that authentication mechanism is already included in
-`download methods <https://eodag.readthedocs.io/en/stable/notebooks/api_user_guide/7_download.html>`_ , so you could also directly execute a
-download to retrieve the token while the OTP is still valid.
+`download methods <https://eodag.readthedocs.io/en/stable/notebooks/api_user_guide/7_download.html>`_ , so you could
+also directly execute a download to retrieve the token while the OTP is still valid.
 
 Use OTP through CLI
 """""""""""""""""""
diff --git a/eodag/config.py b/eodag/config.py
index 8523d9b7..0e5a5eb1 100644
--- a/eodag/config.py
+++ b/eodag/config.py
@@ -226,12 +226,18 @@ class PluginConfig(yaml.YAMLObject):
 
 
 def load_default_config():
-    """Load the providers configuration into a dictionnary
+    """Load the providers configuration into a dictionnary.
+
+    Load from eodag `resources/providers.yml` or `EODAG_PROVIDERS_CFG_FILE` environment
+    variable if exists.
 
     :returns: The default provider's configuration
     :rtype: dict
     """
-    return load_config(resource_filename("eodag", "resources/providers.yml"))
+    eodag_providers_cfg_file = os.getenv(
+        "EODAG_PROVIDERS_CFG_FILE"
+    ) or resource_filename("eodag", "resources/providers.yml")
+    return load_config(eodag_providers_cfg_file)
 
 
 def load_config(config_path):
@@ -242,6 +248,7 @@ def load_config(config_path):
     :returns: The default provider's configuration
     :rtype: dict
     """
+    logger.debug(f"Loading configuration from {config_path}")
     config = {}
     try:
         # Providers configs are stored in this file as separated yaml documents


Test patch:
diff --git a/tests/resources/file_providers_override.yml b/tests/resources/file_providers_override.yml
new file mode 100644
index 00000000..fd3fea75
--- /dev/null
+++ b/tests/resources/file_providers_override.yml
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Copyright 2023, CS GROUP - France, https://www.csgroup.eu/
+#
+# This file is part of EODAG project
+#     https://www.github.com/CS-SI/EODAG
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+---
+!provider
+  name: foo_provider
+  priority: 2
+  roles:
+    - host
+  description: provider for test
+  search: !plugin
+    type: StacSearch
+    api_endpoint: https://foo.bar/search
+  products:
+    GENERIC_PRODUCT_TYPE:
+      productType: '{productType}'
+  download: !plugin
+    type: HTTPDownload
+    base_uri: https://foo.bar
diff --git a/tests/units/test_core.py b/tests/units/test_core.py
index 77b98952..430eb40c 100644
--- a/tests/units/test_core.py
+++ b/tests/units/test_core.py
@@ -900,7 +900,7 @@ class TestCoreConfWithEnvVar(TestCoreBase):
         cls.mock_os_environ.stop()
 
     def test_core_object_prioritize_locations_file_in_envvar(self):
-        """The core object must use the locations file pointed to by the EODAG_LOCS_CFG_FILE env var"""
+        """The core object must use the locations file pointed by the EODAG_LOCS_CFG_FILE env var"""
         try:
             os.environ["EODAG_LOCS_CFG_FILE"] = os.path.join(
                 TEST_RESOURCES_PATH, "file_locations_override.yml"
@@ -914,7 +914,7 @@ class TestCoreConfWithEnvVar(TestCoreBase):
             os.environ.pop("EODAG_LOCS_CFG_FILE", None)
 
     def test_core_object_prioritize_config_file_in_envvar(self):
-        """The core object must use the config file pointed to by the EODAG_CFG_FILE env var"""
+        """The core object must use the config file pointed by the EODAG_CFG_FILE env var"""
         try:
             os.environ["EODAG_CFG_FILE"] = os.path.join(
                 TEST_RESOURCES_PATH, "file_config_override.yml"
@@ -929,6 +929,22 @@ class TestCoreConfWithEnvVar(TestCoreBase):
         finally:
             os.environ.pop("EODAG_CFG_FILE", None)
 
+    def test_core_object_prioritize_providers_file_in_envvar(self):
+        """The core object must use the providers conf file pointed by the EODAG_PROVIDERS_CFG_FILE env var"""
+        try:
+            os.environ["EODAG_PROVIDERS_CFG_FILE"] = os.path.join(
+                TEST_RESOURCES_PATH, "file_providers_override.yml"
+            )
+            dag = EODataAccessGateway()
+            # only foo_provider in conf
+            self.assertEqual(dag.available_providers(), ["foo_provider"])
+            self.assertEqual(
+                dag.providers_config["foo_provider"].search.api_endpoint,
+                "https://foo.bar/search",
+            )
+        finally:
+            os.environ.pop("EODAG_PROVIDERS_CFG_FILE", None)
+
 
 class TestCoreInvolvingConfDir(unittest.TestCase):
     def setUp(self):