New command 'add-key'

also, fixed pep8

Change-Id: I526083d72b50dc99b7e945db8d3e95ddbb81459f
diff --git a/reclass_tools/walk_models.py b/reclass_tools/walk_models.py
index 8b4d91f..0a66033 100644
--- a/reclass_tools/walk_models.py
+++ b/reclass_tools/walk_models.py
@@ -1,11 +1,20 @@
+#    Copyright 2013 - 2017 Mirantis, Inc.
+#
+#    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
-import hashlib
+# import copy
 import os
-import re
-import tarfile
 
-import urllib2
 import yaml
 
 from reclass_tools import helpers
@@ -20,7 +29,7 @@
     if isdir:
         for dirName, subdirList, fileList in walker:
             for filename in fileList:
-                filepath = os.path.join(dirName,filename)
+                filepath = os.path.join(dirName, filename)
                 if verbose:
                     print (prefix + filepath)
                 with OpenFile(filepath, opener) as log:
@@ -32,12 +41,12 @@
             yield (log)
 
 
-#def yaml_read(yaml_file):
-#    if os.path.isfile(yaml_file):
-#        with open(yaml_file, 'r') as f:
-#            return yaml.load(f)
-#    else:
-#        print("\'{}\' is not a file!".format(yaml_file))
+# def yaml_read(yaml_file):
+#     if os.path.isfile(yaml_file):
+#         with open(yaml_file, 'r') as f:
+#             return yaml.load(f)
+#     else:
+#         print("\'{}\' is not a file!".format(yaml_file))
 
 
 class OpenFile(object):
@@ -53,14 +62,14 @@
 
     def get_parser(self):
         parsers = {'/lastlog': self.fake_parser,
-                    '/wtmp': self.fake_parser,
-                    '/btmp': self.fake_parser,
-                    '/atop.log': self.fake_parser,
-                    '/atop_': self.fake_parser,
-                    '/atop_current': self.fake_parser,
-                    '/supervisord.log': self.docker_parser,
-                    '.gz': self.gz_parser,
-                    '.bz2': self.gz_parser,
+                   '/wtmp': self.fake_parser,
+                   '/btmp': self.fake_parser,
+                   '/atop.log': self.fake_parser,
+                   '/atop_': self.fake_parser,
+                   '/atop_current': self.fake_parser,
+                   '/supervisord.log': self.docker_parser,
+                   '.gz': self.gz_parser,
+                   '.bz2': self.gz_parser,
                    }
         for w in parsers.keys():
             if w in self.fname:
@@ -73,7 +82,7 @@
             print("Error opening file {0}: {1}".format(self.fname, e))
             if self.fobj:
                 self.fobj.close()
-            self.fobj =  None
+            self.fobj = None
             self.readlines = self.fake_parser
 
     def plaintext_parser(self):
@@ -113,7 +122,9 @@
                 model = helpers.yaml_read(log.fname)
                 if model is not None:
                     # Collect all params from the models
-                    _param = helpers.get_nested_key(model, ['parameters', '_param'])
+                    _param = helpers.get_nested_key(
+                        model,
+                        ['parameters', '_param'])
                     if _param:
                         for key, val in _param.items():
                             if key in _params:
@@ -125,6 +136,60 @@
     return _params
 
 
+def add_reclass_parameter(paths, key, value, verbose=False, merge=False):
+    """Add a value to the specified key to all the files in the paths
+
+    if merge=False (default):
+      - new value replaces previous key content completely.
+
+    if merge=True:
+      - if the specified key type is list, then value will be appended
+        to the list. Value examples:
+          '1000'
+          'new_lab_name'
+          'cluster.virtual_cluster_name.infra'
+          'http://archive.ubuntu.com'
+          '[a, b, c]'   # a list in the list
+          '{a:1, b:2, c:3}' # a dict in the list
+      - if the specified key type is an existing dict, then the dict
+        will be extended with the dict in the value. Value example:
+          '{address: 192.168.1.1, netmask: 255.255.255.0}'
+
+    - If the specified key type is string/int/bool - it will replace previous
+      value
+    """
+    add_key = key.split('.')
+
+    for path in paths:
+        for fyml in walkfiles(path, verbose=verbose):
+            if fyml.fname.endswith('.yml'):
+                model = helpers.yaml_read(fyml.fname)
+                if model is not None:
+
+                    nested_key = helpers.get_nested_key(model, add_key)
+                    if nested_key:
+                        if merge is False:
+                            nested_key = value
+                        else:
+                            if type(nested_key) is list:
+                                nested_key.append(value)
+                            elif type(nested_key) is dict:
+                                nested_key.update(value)
+                            else:
+                                helpers.create_nested_key(model, path=add_key,
+                                                          value=value)
+                    else:
+                        helpers.create_nested_key(model, path=add_key,
+                                                  value=value)
+
+                    with open(fyml.fname, 'w') as f:
+                        f.write(
+                            yaml.dump(
+                                model, default_flow_style=False
+                            )
+                        )
+
+
 def remove_reclass_parameter(paths, key,
                              verbose=False,
                              pretend=False):
@@ -135,7 +200,7 @@
     :rtype dict: { 'file path': {nested_key}, ...}
     """
     remove_key = key.split('.')
-    found_keys = {}
+    # found_keys = {}
 
     for path in paths:
         for fyml in walkfiles(path, verbose=verbose):
@@ -146,15 +211,17 @@
                     # Clear linux.network.interfaces
                     nested_key = helpers.get_nested_key(model, remove_key)
                     if nested_key:
-                        found_keys[fyml.fname] = copy.deepcopy(nested_key)
+                        # found_keys[fyml.fname] = copy.deepcopy(nested_key)
                         if pretend:
-                            print("\n---\n# Found {0} in {1}".format('.'.join(remove_key),
-                                                                   fyml.fname))
-                            print(yaml.dump(nested_key, default_flow_style=False))
+                            print("\n---\n# Found {0} in {1}"
+                                  .format('.'.join(remove_key), fyml.fname))
+                            print(yaml.dump(nested_key,
+                                            default_flow_style=False))
                         else:
-                            print("\n---\n# Removing {0} from {1}".format('.'.join(remove_key),
-                                                                   fyml.fname))
-                            print(yaml.dump(nested_key, default_flow_style=False))
+                            print("\n---\n# Removing {0} from {1}"
+                                  .format('.'.join(remove_key), fyml.fname))
+                            print(yaml.dump(nested_key,
+                                            default_flow_style=False))
 
                             helpers.remove_nested_key(model, remove_key)
 
@@ -164,4 +231,4 @@
                                         model, default_flow_style=False
                                     )
                                 )
-    return found_keys
+    # return found_keys