deckhand/deckhand/common/document.py
Wahlstedt, Walter (ww229g) 70aa35a396 update to focal and python 3.8
update dockerfile for python deckhand install
add deckhand version to chart 1.0
add chart version 0.2.0
update all packages to latest in requirements.txt
update zuul jobs for focal and python 3.8
remove zuul job functional-uwsgi-py38 in favor of functional-docker-py38
update tox config
typecast to string in re.sub() function
add stestr to test-requirements.txt
add SQLAlchemy jsonpickle sphinx-rtd-theme stestr to requirements.txt
deprecated function: BarbicanException -> BarbicanClientException
fix mock import using unittest
fix import collections to collections.abc
fix for collections modules for older than python 3.10 versions.
deprecated function: json -> to_json
deprecated function:  werkzeug.contrib.profiler ->
    werkzeug.middleware.profiler
deprecated function: falcon.AIP -> falcon.App
deprecation warning: switch from resp.body to resp.text
rename fixtures to dh_fixtures because there is an imported module
    fixtures
switch from stream.read to bounded_stream.read
deprecated function: falcon process_response needed additional parameter
deprecated function: falcon default_exception_handler changed parameter
    order
move from MagicMock object to falcon test generated object to fix
    incompatability with upgraded Falcon module.
Adjust gabbi tests to fix incompatability with upgraded DeepDiff module
update Makefile to execute ubuntu_focal
update HTK (helmtoolkit)
unpin barbican to pass integration tests
Use helm 3 in chart build.
    `helm serve` is removed in helm 3 so this moves
    to using local `file://` dependencies [0] instead.

Change-Id: I180416f480edea1b8968d80c993b3e1fcc95c08d
2023-02-24 10:51:57 -05:00

190 lines
5.4 KiB
Python

# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections.abc import Iterable
import re
import hashlib
from oslo_serialization import jsonutils as json
from oslo_utils import uuidutils
import six
import yaml
_URL_RE = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|'
'(?:%[0-9a-fA-F][0-9a-fA-F]))+')
class DocumentDict(dict):
"""Wrapper for a document.
Implements convenient properties for nested, commonly accessed document
keys. Property setters are only implemented for mutable data.
Useful for accessing nested dictionary keys without having to worry about
exceptions getting thrown.
.. note::
As a rule of thumb, setters for any metadata properties should be
avoided. Only implement or use for well-understood edge cases.
"""
@property
def meta(self):
return (self.schema, self.layer, self.name)
@property
def metadata(self):
return self.get('metadata') or DocumentDict({})
@property
def data(self):
return self.get('data')
@data.setter
def data(self, value):
self['data'] = value
@property
def name(self):
return self.metadata.get('name') or ''
@property
def schema(self):
return self.get('schema') or ''
@property
def layer(self):
return self.layering_definition.get('layer') or ''
@property
def is_abstract(self):
return self.layering_definition.get('abstract') is True
@property
def is_control(self):
return self.metadata.get('schema', '').startswith('metadata/Control')
@property
def layering_definition(self):
metadata = self.metadata or {}
return metadata.get('layeringDefinition') or DocumentDict({})
@property
def layeringDefinition(self):
metadata = self.metadata or {}
return metadata.get('layeringDefinition') or DocumentDict({})
@property
def layer_order(self):
if not self.schema.startswith('deckhand/LayeringPolicy'):
raise TypeError(
'layer_order only exists for LayeringPolicy documents')
return self.data.get('layerOrder', [])
@property
def parent_selector(self):
return self.layering_definition.get(
'parentSelector') or DocumentDict({})
@property
def labels(self):
return self.metadata.get('labels') or DocumentDict({})
@property
def substitutions(self):
return self.metadata.get('substitutions', [])
@substitutions.setter
def substitutions(self, value):
self.metadata.substitutions = value
@property
def actions(self):
return self.layering_definition.get('actions', [])
@property
def storage_policy(self):
return self.metadata.get('storagePolicy') or ''
@storage_policy.setter
def storage_policy(self, value):
self.metadata['storagePolicy'] = value
@property
def is_encrypted(self):
return self.storage_policy == 'encrypted'
@property
def has_barbican_ref(self):
try:
secret_ref = self.data
secret_uuid = secret_ref.split('/')[-1]
except Exception:
secret_uuid = None
return (
isinstance(secret_ref, six.string_types) and
_URL_RE.match(secret_ref) and
'secrets' in secret_ref and
uuidutils.is_uuid_like(secret_uuid)
)
@property
def is_replacement(self):
return self.metadata.get('replacement') is True
@property
def has_replacement(self):
return isinstance(self.replaced_by, DocumentDict)
@property
def replaced_by(self):
return getattr(self, '_replaced_by', None)
@replaced_by.setter
def replaced_by(self, other):
setattr(self, '_replaced_by', other)
def __hash__(self):
return hash(json.dumps(self, sort_keys=True))
@classmethod
def from_list(cls, documents):
"""Convert an iterable of documents into instances of this class.
:param documents: Documents to wrap in this class.
:type documents: iterable
"""
if not isinstance(documents, Iterable):
documents = [documents]
return [DocumentDict(d) for d in documents]
@classmethod
def redact(cls, field):
return hashlib.sha256(json.dumps(field).encode('utf-8')).hexdigest()
def document_dict_representer(dumper, data):
return dumper.represent_mapping('tag:yaml.org,2002:map', dict(data))
yaml.add_representer(DocumentDict, document_dict_representer)
# Required for py27 compatibility: yaml.safe_dump/safe_dump_all doesn't
# work unless SafeRepresenter add_representer method is called.
# Upd: somehow required also for py3*.
safe_representer = yaml.representer.SafeRepresenter
safe_representer.add_representer(DocumentDict, document_dict_representer)