
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
190 lines
5.4 KiB
Python
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)
|