Parse config recursively

So far the config was only parsed two levels deep, not allowing to apply
defaults deeper than that. This would lead to issues as soon as there is
for example a second parameter for each action that can be configured.

Now the parser goes through the config recursively allowing an
indefinite depth of the config dictionary.

Change-Id: I7a0eed1e0266dbe52b19716df81e93881dceebf7
diff --git a/container/tools/config/helper_dict.py b/container/tools/config/helper_dict.py
new file mode 100644
index 0000000..c590dc0
--- /dev/null
+++ b/container/tools/config/helper_dict.py
@@ -0,0 +1,36 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# 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.
+
+import copy
+
+
+def left_outer_join(dict_a, dict_b):
+    merged_dict = copy.deepcopy(dict_a)
+    merged_dict = _recursive_left_outer_join(merged_dict, dict_b)
+
+    return merged_dict
+
+
+def _recursive_left_outer_join(dict_a, dict_b):
+    for key, value in dict_a.items():
+        if key not in dict_b:
+            continue
+        if isinstance(value, dict):
+            if not isinstance(dict_b[key], dict):
+                raise ValueError("Expected dictionary as value of key '%s'" % key)
+            dict_a[key] = _recursive_left_outer_join(value, dict_b[key])
+        else:
+            dict_a[key] = dict_b[key]
+
+    return dict_a
diff --git a/container/tools/config/parser.py b/container/tools/config/parser.py
index f370d23..b55bf08 100644
--- a/container/tools/config/parser.py
+++ b/container/tools/config/parser.py
@@ -16,6 +16,8 @@
 
 import yaml
 
+from .helper_dict import left_outer_join
+
 DEFAULTS = {
     "gerrit": {"url": None, "user": "admin", "password": "secret"},
     "testrun": {"duration": None},
@@ -47,9 +49,8 @@
 
     def parse(self):
         if self.args["config_file"]:
-            for category, category_dict in self._parse_config_file().items():
-                for option, value in category_dict.items():
-                    self.config[category][option] = value
+            config_from_file = self._parse_config_file()
+            self.config = left_outer_join(DEFAULTS, config_from_file)
 
         self._apply_args()