diff options
| author | OpenShift Bot <eparis+openshiftbot@redhat.com> | 2017-03-30 12:55:30 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-03-30 12:55:30 -0500 | 
| commit | c540fcaa851f179866fed2478b0e2520162d8513 (patch) | |
| tree | d80305ee0445e5a6596278fc71a810b31f15417e | |
| parent | 102db9c49a7b35bac0172c73c64b818d9d9a0024 (diff) | |
| parent | b7af36569f4f02a6320833759b48e15df25a9b06 (diff) | |
| download | openshift-c540fcaa851f179866fed2478b0e2520162d8513.tar.gz openshift-c540fcaa851f179866fed2478b0e2520162d8513.tar.bz2 openshift-c540fcaa851f179866fed2478b0e2520162d8513.tar.xz openshift-c540fcaa851f179866fed2478b0e2520162d8513.zip | |
Merge pull request #3628 from kwoodson/yedit_edits
Merged by openshift-bot
34 files changed, 3557 insertions, 2213 deletions
| diff --git a/roles/lib_openshift/library/oc_adm_ca_server_cert.py b/roles/lib_openshift/library/oc_adm_ca_server_cert.py index afe026099..2f6026fbf 100644 --- a/roles/lib_openshift/library/oc_adm_ca_server_cert.py +++ b/roles/lib_openshift/library/oc_adm_ca_server_cert.py @@ -155,8 +155,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/ca_server_cert -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -190,13 +188,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -212,13 +210,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -240,7 +238,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -329,7 +327,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -429,7 +427,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -548,8 +546,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -610,7 +608,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -636,7 +644,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -668,114 +676,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_manage_node.py b/roles/lib_openshift/library/oc_adm_manage_node.py index 0050ccf62..5f49eef39 100644 --- a/roles/lib_openshift/library/oc_adm_manage_node.py +++ b/roles/lib_openshift/library/oc_adm_manage_node.py @@ -141,8 +141,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/manage_node -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -176,13 +174,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -198,13 +196,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -226,7 +224,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -315,7 +313,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -415,7 +413,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -534,8 +532,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -596,7 +594,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -622,7 +630,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -654,114 +662,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) + +            elif params['edits'] is not None: +                edits = params['edits'] -                if rval[0] and module.params['src']: +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_policy_group.py b/roles/lib_openshift/library/oc_adm_policy_group.py index 3d1dc1c96..7caba04f5 100644 --- a/roles/lib_openshift/library/oc_adm_policy_group.py +++ b/roles/lib_openshift/library/oc_adm_policy_group.py @@ -127,8 +127,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/policy_group -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -162,13 +160,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -184,13 +182,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -212,7 +210,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -301,7 +299,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -401,7 +399,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -520,8 +518,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -582,7 +580,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -608,7 +616,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -640,114 +648,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_policy_user.py b/roles/lib_openshift/library/oc_adm_policy_user.py index 83f2165a3..aac3f7166 100644 --- a/roles/lib_openshift/library/oc_adm_policy_user.py +++ b/roles/lib_openshift/library/oc_adm_policy_user.py @@ -127,8 +127,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/policy_user -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -162,13 +160,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -184,13 +182,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -212,7 +210,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -301,7 +299,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -401,7 +399,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -520,8 +518,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -582,7 +580,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -608,7 +616,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -640,114 +648,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_registry.py b/roles/lib_openshift/library/oc_adm_registry.py index 3a892971b..b0345b026 100644 --- a/roles/lib_openshift/library/oc_adm_registry.py +++ b/roles/lib_openshift/library/oc_adm_registry.py @@ -245,8 +245,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/registry -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -280,13 +278,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -302,13 +300,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -330,7 +328,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -419,7 +417,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -519,7 +517,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -638,8 +636,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -700,7 +698,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -726,7 +734,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -758,114 +766,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_adm_router.py b/roles/lib_openshift/library/oc_adm_router.py index e666e0d09..307269da4 100644 --- a/roles/lib_openshift/library/oc_adm_router.py +++ b/roles/lib_openshift/library/oc_adm_router.py @@ -270,8 +270,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/router -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -305,13 +303,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -327,13 +325,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -355,7 +353,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -444,7 +442,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -544,7 +542,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -663,8 +661,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -725,7 +723,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -751,7 +759,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -783,114 +791,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_configmap.py b/roles/lib_openshift/library/oc_configmap.py index 90d38c7a9..96345ffe0 100644 --- a/roles/lib_openshift/library/oc_configmap.py +++ b/roles/lib_openshift/library/oc_configmap.py @@ -125,8 +125,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/configmap -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -160,13 +158,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -182,13 +180,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -210,7 +208,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -299,7 +297,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -399,7 +397,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -518,8 +516,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -580,7 +578,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -606,7 +614,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -638,114 +646,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_edit.py b/roles/lib_openshift/library/oc_edit.py index 42f50ebe7..99027c07f 100644 --- a/roles/lib_openshift/library/oc_edit.py +++ b/roles/lib_openshift/library/oc_edit.py @@ -169,8 +169,6 @@ oc_edit:  # -*- -*- -*- End included fragment: doc/edit -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -204,13 +202,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -226,13 +224,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -254,7 +252,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -343,7 +341,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -443,7 +441,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -562,8 +560,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -624,7 +622,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -650,7 +658,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -682,114 +690,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) + +            elif params['edits'] is not None: +                edits = params['edits'] -                if rval[0] and module.params['src']: +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_env.py b/roles/lib_openshift/library/oc_env.py index 3088ea947..34f86a478 100644 --- a/roles/lib_openshift/library/oc_env.py +++ b/roles/lib_openshift/library/oc_env.py @@ -136,8 +136,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/env -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -171,13 +169,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -193,13 +191,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -221,7 +219,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -310,7 +308,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -410,7 +408,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -529,8 +527,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -591,7 +589,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -617,7 +625,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -649,114 +657,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_group.py b/roles/lib_openshift/library/oc_group.py index 44611df82..00d67108d 100644 --- a/roles/lib_openshift/library/oc_group.py +++ b/roles/lib_openshift/library/oc_group.py @@ -109,8 +109,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/group -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -144,13 +142,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -166,13 +164,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -194,7 +192,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -283,7 +281,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -383,7 +381,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -502,8 +500,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -564,7 +562,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -590,7 +598,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -622,114 +630,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_image.py b/roles/lib_openshift/library/oc_image.py index b7cc37264..ee918a2d1 100644 --- a/roles/lib_openshift/library/oc_image.py +++ b/roles/lib_openshift/library/oc_image.py @@ -128,8 +128,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/image -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -163,13 +161,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -185,13 +183,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -213,7 +211,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -302,7 +300,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -402,7 +400,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -521,8 +519,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -583,7 +581,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -609,7 +617,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -641,114 +649,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_label.py b/roles/lib_openshift/library/oc_label.py index cfcb15241..62b6049c4 100644 --- a/roles/lib_openshift/library/oc_label.py +++ b/roles/lib_openshift/library/oc_label.py @@ -145,8 +145,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/label -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -180,13 +178,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -202,13 +200,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -230,7 +228,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -319,7 +317,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -419,7 +417,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -538,8 +536,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -600,7 +598,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -626,7 +634,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -658,114 +666,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_obj.py b/roles/lib_openshift/library/oc_obj.py index f5cba696d..075c286e0 100644 --- a/roles/lib_openshift/library/oc_obj.py +++ b/roles/lib_openshift/library/oc_obj.py @@ -148,8 +148,6 @@ register: router_output  # -*- -*- -*- End included fragment: doc/obj -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -183,13 +181,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -205,13 +203,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -233,7 +231,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -322,7 +320,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -422,7 +420,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -541,8 +539,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -603,7 +601,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -629,7 +637,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -661,114 +669,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_objectvalidator.py b/roles/lib_openshift/library/oc_objectvalidator.py index 4e1e769cf..d65e1d4c9 100644 --- a/roles/lib_openshift/library/oc_objectvalidator.py +++ b/roles/lib_openshift/library/oc_objectvalidator.py @@ -80,8 +80,6 @@ oc_objectvalidator:  # -*- -*- -*- End included fragment: doc/objectvalidator -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -115,13 +113,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -137,13 +135,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -165,7 +163,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -254,7 +252,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -354,7 +352,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -473,8 +471,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -535,7 +533,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -561,7 +569,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -593,114 +601,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_process.py b/roles/lib_openshift/library/oc_process.py index cabb2ff29..d487746eb 100644 --- a/roles/lib_openshift/library/oc_process.py +++ b/roles/lib_openshift/library/oc_process.py @@ -137,8 +137,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/process -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -172,13 +170,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -194,13 +192,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -222,7 +220,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -311,7 +309,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -411,7 +409,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -530,8 +528,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -592,7 +590,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -618,7 +626,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -650,114 +658,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_project.py b/roles/lib_openshift/library/oc_project.py index 7700a83a3..3fddce055 100644 --- a/roles/lib_openshift/library/oc_project.py +++ b/roles/lib_openshift/library/oc_project.py @@ -134,8 +134,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/project -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -169,13 +167,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -191,13 +189,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -219,7 +217,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -308,7 +306,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -408,7 +406,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -527,8 +525,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -589,7 +587,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -615,7 +623,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -647,114 +655,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_pvc.py b/roles/lib_openshift/library/oc_pvc.py index df0b0d86a..d63f6e063 100644 --- a/roles/lib_openshift/library/oc_pvc.py +++ b/roles/lib_openshift/library/oc_pvc.py @@ -129,8 +129,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/pvc -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -164,13 +162,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -186,13 +184,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -214,7 +212,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -303,7 +301,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -403,7 +401,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -522,8 +520,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -584,7 +582,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -610,7 +618,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -642,114 +650,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_route.py b/roles/lib_openshift/library/oc_route.py index fe59cca33..daddec69f 100644 --- a/roles/lib_openshift/library/oc_route.py +++ b/roles/lib_openshift/library/oc_route.py @@ -179,8 +179,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/route -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -214,13 +212,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -236,13 +234,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -264,7 +262,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -353,7 +351,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -453,7 +451,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -572,8 +570,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -634,7 +632,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -660,7 +668,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -692,114 +700,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_scale.py b/roles/lib_openshift/library/oc_scale.py index 98f1d94a7..92e9362be 100644 --- a/roles/lib_openshift/library/oc_scale.py +++ b/roles/lib_openshift/library/oc_scale.py @@ -123,8 +123,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/scale -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -158,13 +156,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -180,13 +178,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -208,7 +206,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -297,7 +295,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -397,7 +395,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -516,8 +514,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -578,7 +576,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -604,7 +612,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -636,114 +644,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_secret.py b/roles/lib_openshift/library/oc_secret.py index deba4ab8a..1ffdce4df 100644 --- a/roles/lib_openshift/library/oc_secret.py +++ b/roles/lib_openshift/library/oc_secret.py @@ -169,8 +169,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/secret -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -204,13 +202,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -226,13 +224,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -254,7 +252,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -343,7 +341,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -443,7 +441,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -562,8 +560,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -624,7 +622,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -650,7 +658,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -682,114 +690,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_service.py b/roles/lib_openshift/library/oc_service.py index c2e91e39e..77056d5de 100644 --- a/roles/lib_openshift/library/oc_service.py +++ b/roles/lib_openshift/library/oc_service.py @@ -175,8 +175,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/service -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -210,13 +208,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -232,13 +230,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -260,7 +258,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -349,7 +347,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -449,7 +447,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -568,8 +566,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -630,7 +628,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -656,7 +664,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -688,114 +696,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_serviceaccount.py b/roles/lib_openshift/library/oc_serviceaccount.py index a1d8fff14..807bfc992 100644 --- a/roles/lib_openshift/library/oc_serviceaccount.py +++ b/roles/lib_openshift/library/oc_serviceaccount.py @@ -121,8 +121,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/serviceaccount -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -156,13 +154,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -178,13 +176,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -206,7 +204,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -295,7 +293,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -395,7 +393,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -514,8 +512,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -576,7 +574,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -602,7 +610,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -634,114 +642,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_serviceaccount_secret.py b/roles/lib_openshift/library/oc_serviceaccount_secret.py index 470043cc6..c8f4ebef7 100644 --- a/roles/lib_openshift/library/oc_serviceaccount_secret.py +++ b/roles/lib_openshift/library/oc_serviceaccount_secret.py @@ -121,8 +121,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/serviceaccount_secret -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -156,13 +154,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -178,13 +176,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -206,7 +204,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -295,7 +293,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -395,7 +393,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -514,8 +512,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -576,7 +574,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -602,7 +610,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -634,114 +642,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_user.py b/roles/lib_openshift/library/oc_user.py index 2ccc00301..aa9f07980 100644 --- a/roles/lib_openshift/library/oc_user.py +++ b/roles/lib_openshift/library/oc_user.py @@ -181,8 +181,6 @@ ok: [ded-int-aws-master-61034] => {  # -*- -*- -*- End included fragment: doc/user -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -216,13 +214,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -238,13 +236,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -266,7 +264,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -355,7 +353,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -455,7 +453,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -574,8 +572,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -636,7 +634,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -662,7 +670,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -694,114 +702,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_version.py b/roles/lib_openshift/library/oc_version.py index 378c2b2e5..eb293322d 100644 --- a/roles/lib_openshift/library/oc_version.py +++ b/roles/lib_openshift/library/oc_version.py @@ -93,8 +93,6 @@ oc_version:  # -*- -*- -*- End included fragment: doc/version -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -128,13 +126,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -150,13 +148,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -178,7 +176,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -267,7 +265,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -367,7 +365,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -486,8 +484,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -548,7 +546,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -574,7 +582,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -606,114 +614,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- diff --git a/roles/lib_openshift/library/oc_volume.py b/roles/lib_openshift/library/oc_volume.py index e9e29468a..23b292763 100644 --- a/roles/lib_openshift/library/oc_volume.py +++ b/roles/lib_openshift/library/oc_volume.py @@ -158,8 +158,6 @@ EXAMPLES = '''  # -*- -*- -*- End included fragment: doc/volume -*- -*- -*-  # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -193,13 +191,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -215,13 +213,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -243,7 +241,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -332,7 +330,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -432,7 +430,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -551,8 +549,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -613,7 +611,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -639,7 +647,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -671,114 +679,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- @@ -1941,7 +1984,7 @@ class OCVolume(OpenShiftCLI):              if not oc_volume.exists():                  if check_mode: -                    exit_json(changed=False, msg='Would have performed a create.') +                    return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}                  # Create it here                  api_rval = oc_volume.put() diff --git a/roles/lib_openshift/src/class/oc_volume.py b/roles/lib_openshift/src/class/oc_volume.py index 5211a1afd..45b58a516 100644 --- a/roles/lib_openshift/src/class/oc_volume.py +++ b/roles/lib_openshift/src/class/oc_volume.py @@ -157,7 +157,7 @@ class OCVolume(OpenShiftCLI):              if not oc_volume.exists():                  if check_mode: -                    exit_json(changed=False, msg='Would have performed a create.') +                    return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}                  # Create it here                  api_rval = oc_volume.put() diff --git a/roles/lib_utils/library/yedit.py b/roles/lib_utils/library/yedit.py index a2ae6b4f6..9adaeeb52 100644 --- a/roles/lib_utils/library/yedit.py +++ b/roles/lib_utils/library/yedit.py @@ -180,13 +180,27 @@ EXAMPLES = '''  # a:  #   b:  #     c: d +# +# multiple edits at the same time +- name: perform multiple edits +  yedit: +    src: somefile.yml +    edits: +    - key: a#b#c +      value: d +    - key: a#b#c#d +      value: e +    state: present +# Results: +# a: +#   b: +#     c: +#       d: e  '''  # -*- -*- -*- End included fragment: doc/yedit -*- -*- -*-  # -*- -*- -*- Begin included fragment: class/yedit.py -*- -*- -*- -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302  class YeditException(Exception): @@ -220,13 +234,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -242,13 +256,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -270,7 +284,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -359,7 +373,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -459,7 +473,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -578,8 +592,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -640,7 +654,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -666,7 +690,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -698,114 +722,149 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) -        if module.params['src']: +        state = params['state'] + +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) -                if rval[0] and module.params['src']: +            elif params['edits'] is not None: +                edits = params['edits'] + +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'}  # -*- -*- -*- End included fragment: class/yedit.py -*- -*- -*- @@ -837,12 +896,34 @@ def main():                                     type='str'),              backup=dict(default=True, type='bool'),              separator=dict(default='.', type='str'), +            edits=dict(default=None, type='list'),          ),          mutually_exclusive=[["curr_value", "index"], ['update', "append"]],          required_one_of=[["content", "src"]],      ) -    rval = Yedit.run_ansible(module) +    # Verify we recieved either a valid key or edits with valid keys when receiving a src file. +    # A valid key being not None or not ''. +    if module.params['src'] is not None: +        key_error = False +        edit_error = False + +        if module.params['key'] in [None, '']: +            key_error = True + +        if module.params['edits'] in [None, []]: +            edit_error = True + +        else: +            for edit in module.params['edits']: +                if edit.get('key') in [None, '']: +                    edit_error = True +                    break + +        if key_error and edit_error: +            module.fail_json(failed=True, msg='Empty value for parameter key not allowed.') + +    rval = Yedit.run_ansible(module.params)      if 'failed' in rval and rval['failed']:          module.fail_json(**rval) diff --git a/roles/lib_utils/src/ansible/yedit.py b/roles/lib_utils/src/ansible/yedit.py index 8a1a7c2dc..c4b818cf1 100644 --- a/roles/lib_utils/src/ansible/yedit.py +++ b/roles/lib_utils/src/ansible/yedit.py @@ -26,12 +26,34 @@ def main():                                     type='str'),              backup=dict(default=True, type='bool'),              separator=dict(default='.', type='str'), +            edits=dict(default=None, type='list'),          ),          mutually_exclusive=[["curr_value", "index"], ['update', "append"]],          required_one_of=[["content", "src"]],      ) -    rval = Yedit.run_ansible(module) +    # Verify we recieved either a valid key or edits with valid keys when receiving a src file. +    # A valid key being not None or not ''. +    if module.params['src'] is not None: +        key_error = False +        edit_error = False + +        if module.params['key'] in [None, '']: +            key_error = True + +        if module.params['edits'] in [None, []]: +            edit_error = True + +        else: +            for edit in module.params['edits']: +                if edit.get('key') in [None, '']: +                    edit_error = True +                    break + +        if key_error and edit_error: +            module.fail_json(failed=True, msg='Empty value for parameter key not allowed.') + +    rval = Yedit.run_ansible(module.params)      if 'failed' in rval and rval['failed']:          module.fail_json(**rval) diff --git a/roles/lib_utils/src/class/yedit.py b/roles/lib_utils/src/class/yedit.py index 533665db2..e0a27012f 100644 --- a/roles/lib_utils/src/class/yedit.py +++ b/roles/lib_utils/src/class/yedit.py @@ -1,6 +1,5 @@  # flake8: noqa -# pylint: disable=undefined-variable,missing-docstring -# noqa: E301,E302 +# pylint: skip-file  class YeditException(Exception): @@ -34,13 +33,13 @@ class Yedit(object):      @property      def separator(self): -        ''' getter method for yaml_dict ''' +        ''' getter method for separator '''          return self._separator      @separator.setter -    def separator(self): -        ''' getter method for yaml_dict ''' -        return self._separator +    def separator(self, inc_sep): +        ''' setter method for separator ''' +        self._separator = inc_sep      @property      def yaml_dict(self): @@ -56,13 +55,13 @@ class Yedit(object):      def parse_key(key, sep='.'):          '''parse the key allowing the appropriate separator'''          common_separators = list(Yedit.com_sep - set([sep])) -        return re.findall(Yedit.re_key % ''.join(common_separators), key) +        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)      @staticmethod      def valid_key(key, sep='.'):          '''validate the incoming key'''          common_separators = list(Yedit.com_sep - set([sep])) -        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key): +        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):              return False          return True @@ -84,7 +83,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes[:-1]:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -173,7 +172,7 @@ class Yedit(object):          key_indexes = Yedit.parse_key(key, sep)          for arr_ind, dict_key in key_indexes:              if dict_key and isinstance(data, dict): -                data = data.get(dict_key, None) +                data = data.get(dict_key)              elif (arr_ind and isinstance(data, list) and                    int(arr_ind) <= len(data) - 1):                  data = data[int(arr_ind)] @@ -273,7 +272,7 @@ class Yedit(object):                  self.yaml_dict = json.loads(contents)          except yaml.YAMLError as err:              # Error loading yaml or json -            raise YeditException('Problem with loading yaml file. %s' % err) +            raise YeditException('Problem with loading yaml file. {}'.format(err))          return self.yaml_dict @@ -392,8 +391,8 @@ class Yedit(object):              # AUDIT:maybe-no-member makes sense due to fuzzy types              # pylint: disable=maybe-no-member              if not isinstance(value, dict): -                raise YeditException('Cannot replace key, value entry in ' + -                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501 +                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' + +                                     'value=[{}] type=[{}]'.format(value, type(value)))              entry.update(value)              return (True, self.yaml_dict) @@ -454,7 +453,17 @@ class Yedit(object):              pass          result = Yedit.add_entry(tmp_copy, path, value, self.separator) -        if not result: +        if result is None: +            return (False, self.yaml_dict) + +        # When path equals "" it is a special case. +        # "" refers to the root of the document +        # Only update the root path (entire document) when its a list or dict +        if path == '': +            if isinstance(result, list) or isinstance(result, dict): +                self.yaml_dict = result +                return (True, self.yaml_dict) +              return (False, self.yaml_dict)          self.yaml_dict = tmp_copy @@ -480,7 +489,7 @@ class Yedit(object):                  pass              result = Yedit.add_entry(tmp_copy, path, value, self.separator) -            if result: +            if result is not None:                  self.yaml_dict = tmp_copy                  return (True, self.yaml_dict) @@ -512,112 +521,147 @@ class Yedit(object):          # we will convert to bool if it matches any of the above cases          if isinstance(inc_value, str) and 'bool' in vtype:              if inc_value not in true_bools and inc_value not in false_bools: -                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]' -                                     % (inc_value, vtype)) +                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))          elif isinstance(inc_value, bool) and 'str' in vtype:              inc_value = str(inc_value) +        # There is a special case where '' will turn into None after yaml loading it so skip +        if isinstance(inc_value, str) and inc_value == '': +            pass          # If vtype is not str then go ahead and attempt to yaml load it. -        if isinstance(inc_value, str) and 'str' not in vtype: +        elif isinstance(inc_value, str) and 'str' not in vtype:              try: -                inc_value = yaml.load(inc_value) +                inc_value = yaml.safe_load(inc_value)              except Exception: -                raise YeditException('Could not determine type of incoming ' + -                                     'value. value=[%s] vtype=[%s]' -                                     % (type(inc_value), vtype)) +                raise YeditException('Could not determine type of incoming value. ' + +                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))          return inc_value +    @staticmethod +    def process_edits(edits, yamlfile): +        '''run through a list of edits and process them one-by-one''' +        results = [] +        for edit in edits: +            value = Yedit.parse_value(edit['value'], edit.get('value_type', '')) +            if edit.get('action') == 'update': +                # pylint: disable=line-too-long +                curr_value = Yedit.get_curr_value( +                    Yedit.parse_value(edit.get('curr_value')), +                    edit.get('curr_value_format')) + +                rval = yamlfile.update(edit['key'], +                                       value, +                                       edit.get('index'), +                                       curr_value) + +            elif edit.get('action') == 'append': +                rval = yamlfile.append(edit['key'], value) + +            else: +                rval = yamlfile.put(edit['key'], value) + +            if rval[0]: +                results.append({'key': edit['key'], 'edit': rval[1]}) + +        return {'changed': len(results) > 0, 'results': results} +      # pylint: disable=too-many-return-statements,too-many-branches      @staticmethod -    def run_ansible(module): +    def run_ansible(params):          '''perform the idempotent crud operations''' -        yamlfile = Yedit(filename=module.params['src'], -                         backup=module.params['backup'], -                         separator=module.params['separator']) +        yamlfile = Yedit(filename=params['src'], +                         backup=params['backup'], +                         separator=params['separator']) + +        state = params['state'] -        if module.params['src']: +        if params['src']:              rval = yamlfile.load() -            if yamlfile.yaml_dict is None and \ -               module.params['state'] != 'present': +            if yamlfile.yaml_dict is None and state != 'present':                  return {'failed': True, -                        'msg': 'Error opening file [%s].  Verify that the ' + -                               'file exists, that it is has correct' + -                               ' permissions, and is valid yaml.'} - -        if module.params['state'] == 'list': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) + +                               'file exists, that it is has correct permissions, and is valid yaml.'} + +        if state == 'list': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['key']: -                rval = yamlfile.get(module.params['key']) or {} +            if params['key']: +                rval = yamlfile.get(params['key']) or {} -            return {'changed': False, 'result': rval, 'state': "list"} +            return {'changed': False, 'result': rval, 'state': state} -        elif module.params['state'] == 'absent': -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +        elif state == 'absent': +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  yamlfile.yaml_dict = content -            if module.params['update']: -                rval = yamlfile.pop(module.params['key'], -                                    module.params['value']) +            if params['update']: +                rval = yamlfile.pop(params['key'], params['value'])              else: -                rval = yamlfile.delete(module.params['key']) +                rval = yamlfile.delete(params['key']) -            if rval[0] and module.params['src']: +            if rval[0] and params['src']:                  yamlfile.write() -            return {'changed': rval[0], 'result': rval[1], 'state': "absent"} +            return {'changed': rval[0], 'result': rval[1], 'state': state} -        elif module.params['state'] == 'present': +        elif state == 'present':              # check if content is different than what is in the file -            if module.params['content']: -                content = Yedit.parse_value(module.params['content'], -                                            module.params['content_type']) +            if params['content']: +                content = Yedit.parse_value(params['content'], params['content_type'])                  # We had no edits to make and the contents are the same                  if yamlfile.yaml_dict == content and \ -                   module.params['value'] is None: -                    return {'changed': False, -                            'result': yamlfile.yaml_dict, -                            'state': "present"} +                   params['value'] is None: +                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}                  yamlfile.yaml_dict = content -            # we were passed a value; parse it -            if module.params['value']: -                value = Yedit.parse_value(module.params['value'], -                                          module.params['value_type']) -                key = module.params['key'] -                if module.params['update']: -                    # pylint: disable=line-too-long -                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501 -                                                      module.params['curr_value_format'])  # noqa: E501 +            # If we were passed a key, value then +            # we enapsulate it in a list and process it +            # Key, Value passed to the module : Converted to Edits list # +            edits = [] +            _edit = {} +            if params['value'] is not None: +                _edit['value'] = params['value'] +                _edit['value_type'] = params['value_type'] +                _edit['key'] = params['key'] -                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501 +                if params['update']: +                    _edit['action'] = 'update' +                    _edit['curr_value'] = params['curr_value'] +                    _edit['curr_value_format'] = params['curr_value_format'] +                    _edit['index'] = params['index'] -                elif module.params['append']: -                    rval = yamlfile.append(key, value) -                else: -                    rval = yamlfile.put(key, value) +                elif params['append']: +                    _edit['action'] = 'append' + +                edits.append(_edit) + +            elif params['edits'] is not None: +                edits = params['edits'] -                if rval[0] and module.params['src']: +            if edits: +                results = Yedit.process_edits(edits, yamlfile) + +                # if there were changes and a src provided to us we need to write +                if results['changed'] and params['src']:                      yamlfile.write() -                return {'changed': rval[0], -                        'result': rval[1], 'state': "present"} +                return {'changed': results['changed'], 'result': results['results'], 'state': state}              # no edits to make -            if module.params['src']: +            if params['src']:                  # pylint: disable=redefined-variable-type                  rval = yamlfile.write()                  return {'changed': rval[0],                          'result': rval[1], -                        'state': "present"} +                        'state': state} +            # We were passed content but no src, key or value, or edits.  Return contents in memory +            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}          return {'failed': True, 'msg': 'Unkown state passed'} diff --git a/roles/lib_utils/src/doc/yedit b/roles/lib_utils/src/doc/yedit index 16b44943e..82af1f675 100644 --- a/roles/lib_utils/src/doc/yedit +++ b/roles/lib_utils/src/doc/yedit @@ -135,4 +135,20 @@ EXAMPLES = '''  # a:  #   b:  #     c: d +# +# multiple edits at the same time +- name: perform multiple edits +  yedit: +    src: somefile.yml +    edits: +    - key: a#b#c +      value: d +    - key: a#b#c#d +      value: e +    state: present +# Results: +# a: +#   b: +#     c: +#       d: e  ''' diff --git a/roles/lib_utils/src/test/integration/kube-manager-test.yaml.orig b/roles/lib_utils/src/test/integration/kube-manager-test.yaml.orig deleted file mode 100644 index 5541c3dae..000000000 --- a/roles/lib_utils/src/test/integration/kube-manager-test.yaml.orig +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: -  name: kube-controller-manager -  namespace: kube-system -spec: -  hostNetwork: true -  containers: -  - name: kube-controller-manager -    image: openshift/kube:v1.0.0 -    command: -    - /hyperkube -    - controller-manager -    - --master=http://127.0.0.1:8080 -    - --leader-elect=true -    - --service-account-private-key-file=/etc/kubernetes/ssl/apiserver-key.pem -    - --root-ca-file=/etc/k8s/ssl/my.pem -    - --my-new-parameter=openshift -    livenessProbe: -      httpGet: -        host: 127.0.0.1 -        path: /healthz -        port: 10252 -      initialDelaySeconds: 15 -      timeoutSeconds: 1 -    volumeMounts: -    - mountPath: /etc/kubernetes/ssl -      name: ssl-certs-kubernetes -      readOnly: true -    - mountPath: /etc/ssl/certs -      name: ssl-certs-host -      readOnly: 'true' -  volumes: -  - hostPath: -      path: /etc/kubernetes/ssl -    name: ssl-certs-kubernetes -  - hostPath: -      path: /usr/share/ca-certificates -    name: ssl-certs-host -yedittest: yedittest -metadata-namespace: openshift-is-awesome -nonexistingkey: -- --my-new-parameter=openshift -a: -  b: -    c: d -e: -  f: -    g: -      h: -        i: -          j: k diff --git a/roles/lib_utils/src/test/integration/yedit.yml b/roles/lib_utils/src/test/integration/yedit.yml index e3dfd490b..65209bade 100755 --- a/roles/lib_utils/src/test/integration/yedit.yml +++ b/roles/lib_utils/src/test/integration/yedit.yml @@ -219,4 +219,33 @@      assert:        that: results.result == [1, 2, 3]        msg: "Test: '[1, 2, 3]' != [{{ results.result }}]" -###### end test create list value ##### +  ###### end test create list value ##### + +  ###### test create multiple list value ##### +  - name: test multiple edits +    yedit: +      src: "{{ test_file }}" +      edits: +      - key: z.x.y +        value: +        - 1 +        - 2 +        - 3 +      - key: z.x.y +        value: 4 +        action: append + +  - name: retrieve the key +    yedit: +      src: "{{ test_file }}" +      state: list +      key: z#x#y +      separator: '#' +    register: results +  - debug: var=results + +  - name: Assert that the key was created +    assert: +      that: results.result == [1, 2, 3, 4] +      msg: "Test: '[1, 2, 3, 4]' != [{{ results.result }}]" +      ###### end test create multiple list value ##### diff --git a/roles/lib_utils/src/test/unit/test_yedit.py b/roles/lib_utils/src/test/unit/test_yedit.py index 23a3f7353..f9f42843a 100755 --- a/roles/lib_utils/src/test/unit/test_yedit.py +++ b/roles/lib_utils/src/test/unit/test_yedit.py @@ -5,6 +5,7 @@  import os  import sys  import unittest +import mock  # Removing invalid variable names for tests so that I can  # keep them brief @@ -277,6 +278,91 @@ class YeditTest(unittest.TestCase):          with self.assertRaises(YeditException):              yed.put('new.stuff.here[0]', 'item') +    def test_empty_key_with_int_value(self): +        '''test editing top level with not list or dict''' +        yed = Yedit(content={'a': {'b': 12}}) +        result = yed.put('', 'b') +        self.assertFalse(result[0]) + +    def test_setting_separator(self): +        '''test editing top level with not list or dict''' +        yed = Yedit(content={'a': {'b': 12}}) +        yed.separator = ':' +        self.assertEqual(yed.separator, ':') + +    def test_remove_all(self): +        '''test removing all data''' +        data = Yedit.remove_entry({'a': {'b': 12}}, '') +        self.assertTrue(data) + +    def test_remove_list_entry(self): +        '''test removing list entry''' +        data = {'a': {'b': [{'c': 3}]}} +        results = Yedit.remove_entry(data, 'a.b[0]') +        self.assertTrue(results) +        self.assertTrue(data, {'a': {'b': []}}) + +    def test_parse_value_string_true(self): +        '''test parse_value''' +        results = Yedit.parse_value('true', 'str') +        self.assertEqual(results, 'true') + +    def test_parse_value_bool_true(self): +        '''test parse_value''' +        results = Yedit.parse_value('true', 'bool') +        self.assertTrue(results) + +    def test_parse_value_bool_exception(self): +        '''test parse_value''' +        with self.assertRaises(YeditException): +            Yedit.parse_value('TTT', 'bool') + +    @mock.patch('yedit.Yedit.write') +    def test_run_ansible_basic(self, mock_write): +        '''test parse_value''' +        params = { +            'src': None, +            'backup': False, +            'separator': '.', +            'state': 'present', +            'edits': [], +            'value': None, +            'key': None, +            'content': {'a': {'b': {'c': 1}}}, +            'content_type': '', +        } + +        results = Yedit.run_ansible(params) + +        mock_write.side_effect = [ +            (True, params['content']), +        ] + +        self.assertFalse(results['changed']) + +    @mock.patch('yedit.Yedit.write') +    def test_run_ansible_and_write(self, mock_write): +        '''test parse_value''' +        params = { +            'src': '/tmp/test', +            'backup': False, +            'separator': '.', +            'state': 'present', +            'edits': [], +            'value': None, +            'key': None, +            'content': {'a': {'b': {'c': 1}}}, +            'content_type': '', +        } + +        results = Yedit.run_ansible(params) + +        mock_write.side_effect = [ +            (True, params['content']), +        ] + +        self.assertTrue(results['changed']) +      def tearDown(self):          '''TearDown method'''          os.unlink(YeditTest.filename) | 
