armada/armada/handlers/manifest.py

248 lines
9.4 KiB
Python

# Copyright 2017 The Armada Authors.
#
# 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 copy import deepcopy
from oslo_log import log as logging
from armada import const
from armada import exceptions
LOG = logging.getLogger(__name__)
class Manifest(object):
def __init__(self, documents, target_manifest=None):
"""Instantiates a Manifest object.
An Armada Manifest expects that at least one of each of the following
be included in ``documents``:
* A document with schema "armada/Chart/v1"
* A document with schema "armada/ChartGroup/v1"
And only one document of the following is allowed:
* A document with schema "armada/Manifest/v1"
If multiple documents with schema "armada/Manifest/v1" are provided,
specify ``target_manifest`` to select the target one.
:param List[dict] documents: Documents out of which to build the
Armada Manifest.
:param str target_manifest: The target manifest to use when multiple
documents with "armada/Manifest/v1" are contained in
``documents``. Default is None.
:raises ManifestException: If the expected number of document types
are not found or if the document types are missing required
properties.
"""
self.documents = deepcopy(documents)
self.charts, self.groups, manifests = self._find_documents(
target_manifest)
if len(manifests) > 1:
error = ('Multiple manifests are not supported. Ensure that the '
'`target_manifest` option is set to specify the target '
'manifest')
LOG.error(error)
raise exceptions.ManifestException(details=error)
else:
self.manifest = manifests[0] if manifests else None
if not all([self.charts, self.groups, self.manifest]):
expected_schemas = [const.DOCUMENT_CHART, const.DOCUMENT_GROUP]
error = ('Documents must be a list of documents with at least one '
'of each of the following schemas: %s and only one '
'manifest' % expected_schemas)
LOG.error(error)
raise exceptions.ManifestException(details=error)
def _find_documents(self, target_manifest=None):
"""Returns the chart documents, chart group documents,
and Armada manifest
If multiple documents with schema "armada/Manifest/v1" are provided,
specify ``target_manifest`` to select the target one.
:param str target_manifest: The target manifest to use when multiple
documents with "armada/Manifest/v1" are contained in
``documents``. Default is None.
:returns: Tuple of chart documents, chart groups, and manifests
found in ``self.documents``
:rtype: tuple
"""
charts = []
groups = []
manifests = []
for document in self.documents:
if document.get('schema') == const.DOCUMENT_CHART:
charts.append(document)
if document.get('schema') == const.DOCUMENT_GROUP:
groups.append(document)
if document.get('schema') == const.DOCUMENT_MANIFEST:
manifest_name = document.get('metadata', {}).get('name')
if target_manifest:
if manifest_name == target_manifest:
manifests.append(document)
else:
manifests.append(document)
return charts, groups, manifests
def find_chart_document(self, name):
"""Returns a chart document with the specified name
:param str name: name of the desired chart document
:returns: The requested chart document
:rtype: dict
:raises ManifestException: If a chart document with the
specified name is not found
"""
for chart in self.charts:
if chart.get('metadata', {}).get('name') == name:
return chart
raise exceptions.ManifestException(
details='Could not find a {} named "{}"'.format(
const.DOCUMENT_CHART, name))
def find_chart_group_document(self, name):
"""Returns a chart group document with the specified name
:param str name: name of the desired chart group document
:returns: The requested chart group document
:rtype: dict
:raises ManifestException: If a chart
group document with the specified name is not found
"""
for group in self.groups:
if group.get('metadata', {}).get('name') == name:
return group
raise exceptions.ManifestException(
details='Could not find a {} named "{}"'.format(
const.DOCUMENT_GROUP, name))
def build_charts_deps(self):
"""Build chart dependencies for every ``chart``.
:returns: None
"""
for chart in self.charts:
self.build_chart_deps(chart)
def build_chart_groups(self):
"""Build chart dependencies for every ``chart_group``.
:returns: None
"""
for chart_group in self.groups:
self.build_chart_group(chart_group)
def build_chart_deps(self, chart):
"""Recursively build chart dependencies for ``chart``.
:param dict chart: The chart whose dependencies will be recursively
built.
:returns: The chart with all dependencies.
:rtype: dict
:raises ManifestException: If a chart for a dependency name listed
under ``chart['data']['dependencies']`` could not be found.
"""
try:
dep = None
chart_dependencies = chart.get('data', {}).get('dependencies', [])
for iter, dep in enumerate(chart_dependencies):
if isinstance(dep, dict):
continue
chart_dep = self.find_chart_document(dep)
self.build_chart_deps(chart_dep)
chart['data']['dependencies'][iter] = {
'chart': chart_dep.get('data', {})
}
except Exception:
raise exceptions.ManifestException(
details="Could not find dependency chart {} in {}".format(
dep, const.DOCUMENT_CHART))
else:
return chart
def build_chart_group(self, chart_group):
"""Builds the chart dependencies for`charts`chart group``.
:param dict chart_group: The chart_group whose dependencies
will be built.
:returns: The chart_group with all dependencies.
:rtype: dict
:raises ManifestException: If a chart for a dependency name listed
under ``chart_group['data']['chart_group']`` could not be found.
"""
try:
chart = None
for iter, chart in enumerate(
chart_group.get('data', {}).get('chart_group', [])):
if isinstance(chart, dict):
continue
chart_dep = self.find_chart_document(chart)
chart_group['data']['chart_group'][iter] = {
'chart': chart_dep.get('data', {})
}
except Exception:
raise exceptions.ManifestException(
details="Could not find chart {} in {}".format(
chart, const.DOCUMENT_GROUP))
else:
return chart_group
def build_armada_manifest(self):
"""Builds the Armada manifest while pulling out data
from the chart_group.
:returns: The Armada manifest with the data of the chart groups.
:rtype: dict
:raises ManifestException: If a chart group's data listed
under ``chart_group['data']`` could not be found.
"""
try:
group = None
for iter, group in enumerate(
self.manifest.get('data', {}).get('chart_groups', [])):
if isinstance(group, dict):
continue
chart_grp = self.find_chart_group_document(group)
# Add name to chart group
ch_grp_data = chart_grp.get('data', {})
ch_grp_data['name'] = chart_grp.get('metadata', {}).get('name')
self.manifest['data']['chart_groups'][iter] = ch_grp_data
except Exception:
raise exceptions.ManifestException(
"Could not find chart group {} in {}".format(
group, const.DOCUMENT_MANIFEST))
else:
return self.manifest
def get_manifest(self):
"""Builds all of the documents including the dependencies of the
chart documents, the charts in the chart_groups, and the
Armada manifest
:returns: The Armada manifest.
:rtype: dict
"""
self.build_charts_deps()
self.build_chart_groups()
self.build_armada_manifest()
return {'armada': self.manifest.get('data', {})}