def _try_replace_pattern(to_replace): try: # A pattern requires us to look up the data located at # to_replace[jsonpath] and then figure out what # re.match(to_replace[jsonpath], pattern) is (in pseudocode). # Raise an exception in case the path isn't present in the # to_replace and a pattern has been provided since it is # otherwise impossible to do the look-up. replacement = re.sub(pattern, six.text_type(value_copy), to_replace) except TypeError as e: LOG.error( 'Failed to substitute the value %s into %s ' 'using pattern %s. Details: %s', six.text_type(value_copy), to_replace, pattern, six.text_type(e)) raise errors.MissingDocumentPattern(jsonpath=jsonpath, pattern=pattern) return replacement
def jsonpath_replace(data, value, jsonpath, pattern=None): """Update value in ``data`` at the path specified by ``jsonpath``. If the nested path corresponding to ``jsonpath`` isn't found in ``data``, the path is created as an empty ``{}`` for each sub-path along the ``jsonpath``. :param data: The `data` section of a document. :param value: The new value for ``data[jsonpath]``. :param jsonpath: A multi-part key that references a nested path in ``data``. :param pattern: A regular expression pattern. :returns: Updated value at ``data[jsonpath]``. :raises: MissingDocumentPattern if ``pattern`` is not None and ``data[jsonpath]`` doesn't exist. Example:: doc = { 'data': { 'some_url': http://admin:INSERT_PASSWORD_HERE@svc-name:8080/v1 } } secret = 'super-duper-secret' path = '$.some_url' pattern = 'INSERT_[A-Z]+_HERE' replaced_data = utils.jsonpath_replace( doc['data'], secret, path, pattern) # The returned URL will look like: # http://admin:super-duper-secret@svc-name:8080/v1 doc['data'].update(replaced_data) """ data = data.copy() if jsonpath.startswith('.'): jsonpath = '$' + jsonpath def _do_replace(): p = jsonpath_ng.parse(jsonpath) p_to_change = p.find(data) if p_to_change: _value = value if pattern: to_replace = p_to_change[0].value # `value` represents the value to inject into `to_replace` that # matches the `pattern`. try: _value = re.sub(pattern, value, to_replace) except TypeError: _value = None return p.update(data, _value) result = _do_replace() if result: return result # A pattern requires us to look up the data located at data[jsonpath] # and then figure out what re.match(data[jsonpath], pattern) is (in # pseudocode). But raise an exception in case the path isn't present in the # data and a pattern has been provided since it is impossible to do the # look up. if pattern: raise errors.MissingDocumentPattern( data=data, path=jsonpath, pattern=pattern) # However, Deckhand should be smart enough to create the nested keys in the # data if they don't exist and a pattern isn't required. d = data for path in jsonpath.split('.')[1:]: if path not in d: d.setdefault(path, {}) d = d.get(path) return _do_replace()
def jsonpath_replace(data, value, jsonpath, pattern=None): """Update value in ``data`` at the path specified by ``jsonpath``. If the nested path corresponding to ``jsonpath`` isn't found in ``data``, the path is created as an empty ``{}`` for each sub-path along the ``jsonpath``. :param data: The `data` section of a document. :param value: The new value for ``data[jsonpath]``. :param jsonpath: A multi-part key that references a nested path in ``data``. Must begin with "." (without quotes). :param pattern: A regular expression pattern. :returns: Updated value at ``data[jsonpath]``. :raises: MissingDocumentPattern if ``pattern`` is not None and ``data[jsonpath]`` doesn't exist. :raises ValueError: If ``jsonpath`` doesn't begin with "." Example:: doc = { 'data': { 'some_url': http://admin:INSERT_PASSWORD_HERE@svc-name:8080/v1 } } secret = 'super-duper-secret' path = '$.some_url' pattern = 'INSERT_[A-Z]+_HERE' replaced_data = utils.jsonpath_replace( doc['data'], secret, path, pattern) # The returned URL will look like: # http://admin:super-duper-secret@svc-name:8080/v1 doc['data'].update(replaced_data) """ data = copy.copy(data) value = copy.copy(value) jsonpath = _normalize_jsonpath(jsonpath) if not jsonpath == '$' and not jsonpath.startswith('$.'): LOG.error('The provided jsonpath %s does not begin with "." or "$"', jsonpath) raise ValueError('The provided jsonpath %s does not begin with "." ' 'or "$"' % jsonpath) def _do_replace(): p = _jsonpath_parse_cache(jsonpath) p_to_change = p.find(data) if p_to_change: new_value = value if pattern: to_replace = p_to_change[0].value # `new_value` represents the value to inject into `to_replace` # that matches the `pattern`. try: new_value = re.sub(pattern, str(value), to_replace) except TypeError as e: with excutils.save_and_reraise_exception(): LOG.error( 'Failed to substitute the value %s into %s ' 'using pattern %s. Details: %s', str(value), to_replace, pattern, six.text_type(e)) return p.update(data, new_value) result = _do_replace() if result: return result # A pattern requires us to look up the data located at data[jsonpath] # and then figure out what re.match(data[jsonpath], pattern) is (in # pseudocode). But raise an exception in case the path isn't present in the # data and a pattern has been provided since it is impossible to do the # look-up. if pattern: raise errors.MissingDocumentPattern(path=jsonpath, pattern=pattern) # However, Deckhand should be smart enough to create the nested keys in the # data if they don't exist and a pattern isn't required. _populate_data_with_attributes(jsonpath, data) return _do_replace()