Add env service for configuration

Issue-ID: DCAEGEN2-2630
Signed-off-by: Edyta Krukowska <edyta.krukowska@nokia.com>
Change-Id: I86b4a84fd49bfe41afd39a9a2096d72d8263e63a
diff --git a/onap-dcae-cbs-docker-client/Changelog.md b/onap-dcae-cbs-docker-client/Changelog.md
index d0ee35b..c16950d 100644
--- a/onap-dcae-cbs-docker-client/Changelog.md
+++ b/onap-dcae-cbs-docker-client/Changelog.md
@@ -4,6 +4,9 @@
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## [2.1.2] - 05/13/2021 
+* Add service for envs present in configuration
+
 ## [2.1.1] - 04/27/2020
 * Bug fix DCAEGEN2-2213 ConnectionError exception is lost when CBSUnreachable is raised
 
diff --git a/onap-dcae-cbs-docker-client/onap_dcae_cbs_docker_client/client.py b/onap-dcae-cbs-docker-client/onap_dcae_cbs_docker_client/client.py
index d02b1d1..7f36169 100644
--- a/onap-dcae-cbs-docker-client/onap_dcae_cbs_docker_client/client.py
+++ b/onap-dcae-cbs-docker-client/onap_dcae_cbs_docker_client/client.py
@@ -1,5 +1,6 @@
 # ================================================================================
 # Copyright (c) 2017-2019 AT&T Intellectual Property. All rights reserved.
+# Copyright (C) 2021 Nokia. All rights reserved.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -22,12 +23,33 @@
 from onap_dcae_cbs_docker_client import get_module_logger
 from onap_dcae_cbs_docker_client.exceptions import ENVsMissing, CantGetConfig, CBSUnreachable
 
-
 logger = get_module_logger(__name__)
 
 
 #########
 # HELPERS
+
+
+def _recurse(config):
+    """
+    Recurse through a configuration, or recursively a sub element of it.
+    If it's a dict: recurse over all the values
+    If it's a list: recurse over all the values
+    If it's a string: return the replacement
+    If none of the above, just return the item.
+    """
+    if isinstance(config, list):
+        return [_recurse(item) for item in config]
+    if isinstance(config, dict):
+        for key in config:
+            config[key] = _recurse(config[key])
+        return config
+    if isinstance(config, str):
+        return change_envs(config)
+    # not a dict, not a list, not a string, nothing to do.
+    return config
+
+
 def _get_path(path):
     """
     Try to get the config, and return appropriate exceptions otherwise
@@ -70,13 +92,26 @@
         raise CBSUnreachable(e)
 
 
+def change_envs(value):
+    """
+    Replace env reference by actual value and return it
+    """
+    if value.startswith('$'):
+        try:
+            value = os.environ[value.replace('${', '').replace('}', '')]
+        except KeyError as e:
+            raise ENVsMissing("Required ENV Variable {0} missing".format(e))
+    return value
+
+
 #########
 # Public
 def get_all():
     """
     Hit the CBS service_component_all endpoint
     """
-    return _get_path("service_component_all")
+    config = _get_path("service_component_all")
+    return _recurse(config)
 
 
 def get_config():
@@ -86,4 +121,5 @@
     TODO: should we take in a "retry" boolean, and retry on behalf of the caller?
     Currently, we return an exception and let the application decide how it wants to proceed (Crash, try again, etc).
     """
-    return _get_path("service_component")
+    config = _get_path("service_component")
+    return _recurse(config)
diff --git a/onap-dcae-cbs-docker-client/pom.xml b/onap-dcae-cbs-docker-client/pom.xml
index 6910bde..b566d89 100644
--- a/onap-dcae-cbs-docker-client/pom.xml
+++ b/onap-dcae-cbs-docker-client/pom.xml
@@ -28,7 +28,7 @@
   <groupId>org.onap.dcaegen2.utils</groupId>
   <artifactId>onap-dcae-cbs-docker-client</artifactId>
   <name>dcaegen2-utils-python-cbs-docker-client</name>
-  <version>2.1.1-SNAPSHOT</version>
+  <version>2.1.2-SNAPSHOT</version>
   <url>http://maven.apache.org</url>
 
   <properties>
diff --git a/onap-dcae-cbs-docker-client/setup.py b/onap-dcae-cbs-docker-client/setup.py
index 017f7b3..84b7436 100644
--- a/onap-dcae-cbs-docker-client/setup.py
+++ b/onap-dcae-cbs-docker-client/setup.py
@@ -1,5 +1,6 @@
 # ================================================================================
 # Copyright (c) 2017-2019 AT&T Intellectual Property. All rights reserved.
+# Copyright (C) 2021 Nokia. All rights reserved.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -19,7 +20,7 @@
 setup(
     name="onap_dcae_cbs_docker_client",
     description="very lightweight client for a DCAE dockerized component to get it's config from the CBS",
-    version="2.1.1",
+    version="2.1.2",
     packages=find_packages(),
     author="Tommy Carpenter",
     author_email="tommy@research.att.com",
diff --git a/onap-dcae-cbs-docker-client/tests/conftest.py b/onap-dcae-cbs-docker-client/tests/conftest.py
index b927412..0f34ff1 100644
--- a/onap-dcae-cbs-docker-client/tests/conftest.py
+++ b/onap-dcae-cbs-docker-client/tests/conftest.py
@@ -1,5 +1,6 @@
 # ================================================================================
 # Copyright (c) 2019 AT&T Intellectual Property. All rights reserved.
+# Copyright (C) 2021 Nokia. All rights reserved.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -44,6 +45,30 @@
 )
 good_resp_conf = FakeResponse(status_code=200, thejson={"key_to_your_heart": 666})
 
+good_resp_conf_env = FakeResponse(status_code=200, thejson={"key_to_your_heart": "${TEST_ENV}"})
+
+good_resp_all_env = FakeResponse(
+    status_code=200,
+    thejson={
+        "config": {"key_to_your_heart": "${TEST_ENV}"},
+        "dti": {"some amazing": "dti stuff"},
+        "policies": {"event": {"foo": "bar"}, "items": [{"foo2": "bar2"}]},
+        "otherkey": {"foo3": "bar3"},
+    },
+)
+
+good_resp_conf_wrong_env = FakeResponse(status_code=200, thejson={"key_to_your_heart": "${WRONG_TEST_ENV}"})
+
+good_resp_all_wrong_env = FakeResponse(
+    status_code=200,
+    thejson={
+        "config": {"key_to_your_heart": "${WRONG_TEST_ENV}"},
+        "dti": {"some amazing": "dti stuff"},
+        "policies": {"event": {"foo": "bar"}, "items": [{"foo2": "bar2"}]},
+        "otherkey": {"foo3": "bar3"},
+    },
+)
+
 
 @pytest.fixture
 def monkeyed_requests_get():
@@ -80,6 +105,57 @@
 
 
 @pytest.fixture
+def monkeyed_requests_get_with_env():
+    """
+    mock for the CBS get
+    """
+
+    def _monkeyed_requests_get(url):
+        if url == "http://config-binding-service:10000/service_component_all/testhostname":
+            return good_resp_all_env
+        elif url == "http://config-binding-service:10000/service_component/testhostname":
+            return good_resp_conf_env
+        else:
+            raise Exception("Unexpected URL {0}!".format(url))
+
+    return _monkeyed_requests_get
+
+
+@pytest.fixture
+def monkeyed_requests_get_https_env():
+    """
+    mock for the CBS get
+    """
+
+    def _monkeyed_requests_get_https(url, verify=""):
+        if url == "https://config-binding-service:10443/service_component_all/testhostname":
+            return good_resp_all_env
+        elif url == "https://config-binding-service:10443/service_component/testhostname":
+            return good_resp_conf_env
+        else:
+            raise Exception("Unexpected URL {0}!".format(url))
+
+    return _monkeyed_requests_get_https
+
+
+@pytest.fixture
+def monkeyed_requests_get_http_with_wrong_env():
+    """
+    mock for the CBS get
+    """
+
+    def _monkeyed_requests_get(url):
+        if url == "http://config-binding-service:10000/service_component_all/testhostname":
+            return good_resp_all_wrong_env
+        elif url == "http://config-binding-service:10000/service_component/testhostname":
+            return good_resp_conf_wrong_env
+        else:
+            raise Exception("Unexpected URL {0}!".format(url))
+
+    return _monkeyed_requests_get
+
+
+@pytest.fixture
 def monkeyed_requests_get_404():
     def _monkeyed_requests_get_404(url):
         """
diff --git a/onap-dcae-cbs-docker-client/tests/test_client.py b/onap-dcae-cbs-docker-client/tests/test_client.py
index 132ab33..3b10923 100644
--- a/onap-dcae-cbs-docker-client/tests/test_client.py
+++ b/onap-dcae-cbs-docker-client/tests/test_client.py
@@ -1,5 +1,6 @@
 # ================================================================================
 # Copyright (c) 2017-2019 AT&T Intellectual Property. All rights reserved.
+# Copyright (C) 2021 Nokia. All rights reserved.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -77,3 +78,38 @@
         get_config()
     with pytest.raises(ENVsMissing):
         get_all()
+
+
+def test_http_with_env(monkeypatch, monkeyed_requests_get_with_env):
+    monkeypatch.setattr("requests.get", monkeyed_requests_get_with_env)
+
+    assert get_config() == {"key_to_your_heart": "test_env"}
+
+    assert get_all() == {
+        "config": {"key_to_your_heart": "test_env"},
+        "dti": {"some amazing": "dti stuff"},
+        "policies": {"event": {"foo": "bar"}, "items": [{"foo2": "bar2"}]},
+        "otherkey": {"foo3": "bar3"},
+    }
+
+
+def test_https_with_env(monkeypatch, monkeyed_requests_get_https_env):
+    monkeypatch.setattr("requests.get", monkeyed_requests_get_https_env)
+    monkeypatch.setenv("DCAE_CA_CERTPATH", "1")
+
+    assert get_config() == {"key_to_your_heart": "test_env"}
+
+    assert get_all() == {
+        "config": {"key_to_your_heart": "test_env"},
+        "dti": {"some amazing": "dti stuff"},
+        "policies": {"event": {"foo": "bar"}, "items": [{"foo2": "bar2"}]},
+        "otherkey": {"foo3": "bar3"},
+    }
+
+
+def test_http_with_wrong_env(monkeypatch, monkeyed_requests_get_http_with_wrong_env):
+    monkeypatch.setattr("requests.get", monkeyed_requests_get_http_with_wrong_env)
+    with pytest.raises(ENVsMissing):
+        get_config()
+    with pytest.raises(ENVsMissing):
+        get_all()
diff --git a/onap-dcae-cbs-docker-client/tox.ini b/onap-dcae-cbs-docker-client/tox.ini
index 673ca37..04b292e 100644
--- a/onap-dcae-cbs-docker-client/tox.ini
+++ b/onap-dcae-cbs-docker-client/tox.ini
@@ -1,6 +1,6 @@
 # content of: tox.ini , put in same dir as setup.py
 [tox]
-envlist = py36,flake8,py38
+envlist = py36, flake8, py38, py39
 
 [testenv]
 deps=
@@ -11,13 +11,14 @@
     HOSTNAME = testhostname
     CONFIG_BINDING_SERVICE = config-binding-service
     PYTHONPATH={toxinidir}
+    TEST_ENV=test_env
 
 commands=
     pytest --junitxml xunit-results.xml --cov onap_dcae_cbs_docker_client --cov-report xml
     coverage xml
 
 [testenv:flake8]
-basepython = python3.6
+basepython = python3.8
 skip_install = true
 deps = flake8
 commands = flake8 setup.py onap_dcae_cbs_docker_client tests