armada/armada/handlers/manifest.py
anthony.bellino cb57588968 Fix for get manifest
In some use cases, some site level docs are only included in specific
manifests. This is so sites can call out what they want deployed, however
currently Armada is checking for all documents to exist and leads
to an invalid manifest exception.

This PS removes the '.build_charts_deps()' and 'build_chart_groups()' calls
in 'get_manifest()' so that only chart documents, and chart group documents
are built after finding them within 'build_armada_manfiest()' and
'build_chart_group()'. 'build_armada_manifest()' will now throw the
related 'Could not find chart group... exception' for related chart
and chart group issues.  Additional subclass exceptions were added along
with adding traceback to capture the chained exceptions.

Change-Id: Idc8a75b290ac0afb1e177203535b012d589b708f
2018-09-14 15:27:03 +00:00

225 lines
8.8 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.BuildChartException(
details='Could not build {} 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.BuildChartGroupException(
details='Could not build {} named "{}"'.format(
const.DOCUMENT_GROUP, name))
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:
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.ChartDependencyException(
details="Could not build dependencies for chart {} in {}".
format(
chart.get('metadata').get('name'), 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)
self.build_chart_deps(chart_dep)
chart_group['data']['chart_group'][iter] = {
'chart': chart_dep.get('data', {})
}
except exceptions.ManifestException:
cg_name = chart_group.get('metadata', {}).get('name')
raise exceptions.BuildChartGroupException(
details="Could not build chart group {} in {}".format(
cg_name, const.DOCUMENT_GROUP))
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.
"""
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)
self.build_chart_group(chart_grp)
# 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
return self.manifest
def get_manifest(self):
"""Builds the Armada manifest
:returns: The Armada manifest.
:rtype: dict
"""
self.build_armada_manifest()
return {'armada': self.manifest.get('data', {})}