bug(manifest): Allow specific manifest to be specified

This PS allows users to specify the manifest file to use
by the Armada handler by introducing a new flag called
`target_manifest`. This flag was added to the API and
CLI.

A foundation of unit tests for the manifest handler
is included in this PS. Most of the coverage is aimed
at checking the various success and failure cases
surrounding the new target_manifest feature.

Also updates documentation to convey information about
the new flag and clean up some documentation formatting
inconsistencies and typos.

Change-Id: I1d5a3ecc1e99b6479438d0ee5490610178be34fe
This commit is contained in:
Felipe Monteiro 2018-01-22 21:44:30 +00:00
parent 62338b394f
commit 093b5d2296
18 changed files with 634 additions and 139 deletions

View File

@ -19,57 +19,54 @@ import falcon
from armada import api from armada import api
from armada.common import policy from armada.common import policy
from armada import exceptions
from armada.handlers.armada import Armada from armada.handlers.armada import Armada
from armada.handlers.document import ReferenceResolver from armada.handlers.document import ReferenceResolver
from armada.handlers.override import Override from armada.handlers.override import Override
class Apply(api.BaseResource): class Apply(api.BaseResource):
''' """Controller for installing and updating charts defined in an Armada
apply armada endpoint service manifest file.
''' """
@policy.enforce('armada:create_endpoints') @policy.enforce('armada:create_endpoints')
def on_post(self, req, resp): def on_post(self, req, resp):
try: # Load data from request and get options
if req.content_type == 'application/x-yaml':
# Load data from request and get options data = list(self.req_yaml(req))
if req.content_type == 'application/x-yaml': if type(data[0]) is list:
data = list(self.req_yaml(req)) documents = list(data[0])
if type(data[0]) is list:
documents = list(data[0])
else:
documents = data
elif req.content_type == 'application/json':
self.logger.debug("Applying manifest based on reference.")
req_body = self.req_json(req)
doc_ref = req_body.get('hrefs', None)
if not doc_ref:
self.logger.info("Request did not contain 'hrefs'.")
resp.status = falcon.HTTP_400
return
data = ReferenceResolver.resolve_reference(doc_ref)
documents = list()
for d in data:
documents.extend(list(yaml.safe_load_all(d.decode())))
if req_body.get('overrides', None):
overrides = Override(documents,
overrides=req_body.get('overrides'))
documents = overrides.update_manifests()
else: else:
self.error(req.context, "Unknown content-type %s" documents = data
% req.content_type) elif req.content_type == 'application/json':
self.return_error( self.logger.debug("Applying manifest based on reference.")
resp, req_body = self.req_json(req)
falcon.HTTP_415, doc_ref = req_body.get('hrefs', None)
message="Request must be in application/x-yaml"
"or application/json")
opts = req.params if not doc_ref:
self.logger.info("Request did not contain 'hrefs'.")
resp.status = falcon.HTTP_400
return
# Encode filename data = ReferenceResolver.resolve_reference(doc_ref)
documents = list()
for d in data:
documents.extend(list(yaml.safe_load_all(d.decode())))
if req_body.get('overrides', None):
overrides = Override(documents,
overrides=req_body.get('overrides'))
documents = overrides.update_manifests()
else:
self.error(req.context, "Unknown content-type %s"
% req.content_type)
self.return_error(
resp,
falcon.HTTP_415,
message="Request must be in application/x-yaml"
"or application/json")
try:
armada = Armada( armada = Armada(
documents, documents,
disable_update_pre=req.get_param_as_bool( disable_update_pre=req.get_param_as_bool(
@ -80,9 +77,10 @@ class Apply(api.BaseResource):
'enable_chart_cleanup'), 'enable_chart_cleanup'),
dry_run=req.get_param_as_bool('dry_run'), dry_run=req.get_param_as_bool('dry_run'),
wait=req.get_param_as_bool('wait'), wait=req.get_param_as_bool('wait'),
timeout=int(opts.get('timeout', 3600)), timeout=req.get_param_as_int('timeout') or 3600,
tiller_host=opts.get('tiller_host', None), tiller_host=req.get_param('tiller_host', default=None),
tiller_port=int(opts.get('tiller_port', 44134)), tiller_port=req.get_param_as_int('tiller_port') or 44134,
target_manifest=req.get_param('target_manifest')
) )
msg = armada.sync() msg = armada.sync()
@ -95,6 +93,8 @@ class Apply(api.BaseResource):
resp.content_type = 'application/json' resp.content_type = 'application/json'
resp.status = falcon.HTTP_200 resp.status = falcon.HTTP_200
except exceptions.ManifestException as e:
self.return_error(resp, falcon.HTTP_400, message=str(e))
except Exception as e: except Exception as e:
err_message = 'Failed to apply manifest: {}'.format(e) err_message = 'Failed to apply manifest: {}'.format(e)
self.error(req.context, err_message) self.error(req.context, err_message)

View File

@ -77,12 +77,13 @@ class Tests(api.BaseResource):
@policy.enforce('armada:tests_manifest') @policy.enforce('armada:tests_manifest')
def on_post(self, req, resp): def on_post(self, req, resp):
try: try:
opts = req.params tiller = Tiller(tiller_host=req.get_param('tiller_host', None),
tiller = Tiller(tiller_host=opts.get('tiller_host', None), tiller_port=req.get_param('tiller_port', None))
tiller_port=opts.get('tiller_port', None))
documents = self.req_yaml(req) documents = self.req_yaml(req)
armada_obj = Manifest(documents).get_manifest() target_manifest = req.get_param('target_manifest', None)
armada_obj = Manifest(
documents, target_manifest=target_manifest).get_manifest()
prefix = armada_obj.get(const.KEYWORD_ARMADA).get( prefix = armada_obj.get(const.KEYWORD_ARMADA).get(
const.KEYWORD_PREFIX) const.KEYWORD_PREFIX)
known_releases = [release[0] for release in tiller.list_charts()] known_releases = [release[0] for release in tiller.list_charts()]

View File

@ -23,8 +23,7 @@ from armada.handlers.document import ReferenceResolver
class Validate(api.BaseResource): class Validate(api.BaseResource):
''' '''Controller for validating an Armada manifest.
apply armada endpoint service
''' '''
@policy.enforce('armada:validate_manifest') @policy.enforce('armada:validate_manifest')

View File

@ -65,46 +65,76 @@ To obtain override manifest:
SHORT_DESC = "command install manifest charts" SHORT_DESC = "command install manifest charts"
@apply.command(name='apply', help=DESC, short_help=SHORT_DESC) @apply.command(name='apply',
help=DESC,
short_help=SHORT_DESC)
@click.argument('locations', nargs=-1) @click.argument('locations', nargs=-1)
@click.option('--api', help="Contacts service endpoint", is_flag=True) @click.option('--api',
@click.option( help="Contacts service endpoint.",
'--disable-update-post', help="run charts without install", is_flag=True) is_flag=True)
@click.option( @click.option('--disable-update-post',
'--disable-update-pre', help="run charts without install", is_flag=True) help="Disable post-update Tiller operations.",
@click.option('--dry-run', help="run charts without install", is_flag=True) is_flag=True)
@click.option( @click.option('--disable-update-pre',
'--enable-chart-cleanup', help="Clean up Unmanaged Charts", is_flag=True) help="Disable pre-update Tiller operations.",
@click.option('--set', multiple=True, type=str, default=[]) is_flag=True)
@click.option('--tiller-host', help="Tiller host ip") @click.option('--dry-run',
@click.option( help="Run charts without installing them.",
'--tiller-port', help="Tiller host port", type=int, default=44134) is_flag=True)
@click.option( @click.option('--enable-chart-cleanup',
'--timeout', help="Clean up unmanaged charts.",
help="specifies time to wait for charts", is_flag=True)
type=int, @click.option('--set',
default=3600) help=("Use to override Armada Manifest values. Accepts "
@click.option('--values', '-f', multiple=True, type=str, default=[]) "overrides that adhere to the format <key>=<value>"),
@click.option('--wait', help="wait until all charts deployed", is_flag=True) multiple=True,
@click.option( type=str,
'--debug/--no-debug', help='Enable or disable debugging', default=False) default=[])
@click.option('--tiller-host',
help="Tiller host IP.")
@click.option('--tiller-port',
help="Tiller host port.",
type=int,
default=44134)
@click.option('--timeout',
help="Specifies time to wait for charts to deploy.",
type=int,
default=3600)
@click.option('--values',
'-f',
help=("Use to override multiple Armada Manifest values by "
"reading overrides from a values.yaml-type file."),
multiple=True,
type=str,
default=[])
@click.option('--wait',
help="Wait until all charts deployed.",
is_flag=True)
@click.option('--target-manifest',
help=('The target manifest to run. Required for specifying '
'which manifest to run when multiple are available.'),
default=None)
@click.option('--debug/--no-debug',
help='Enable or disable debugging.',
default=False)
@click.pass_context @click.pass_context
def apply_create(ctx, locations, api, disable_update_post, disable_update_pre, def apply_create(ctx, locations, api, disable_update_post, disable_update_pre,
dry_run, enable_chart_cleanup, set, tiller_host, tiller_port, dry_run, enable_chart_cleanup, set, tiller_host, tiller_port,
timeout, values, wait, debug): timeout, values, wait, target_manifest, debug):
if debug: if debug:
CONF.debug = debug CONF.debug = debug
ApplyManifest(ctx, locations, api, disable_update_post, disable_update_pre, ApplyManifest(ctx, locations, api, disable_update_post, disable_update_pre,
dry_run, enable_chart_cleanup, set, tiller_host, tiller_port, dry_run, enable_chart_cleanup, set, tiller_host, tiller_port,
timeout, values, wait).invoke() timeout, values, wait, target_manifest).invoke()
class ApplyManifest(CliAction): class ApplyManifest(CliAction):
def __init__(self, ctx, locations, api, disable_update_post, def __init__(self, ctx, locations, api, disable_update_post,
disable_update_pre, dry_run, enable_chart_cleanup, set, disable_update_pre, dry_run, enable_chart_cleanup, set,
tiller_host, tiller_port, timeout, values, wait): tiller_host, tiller_port, timeout, values, wait,
target_manifest):
super(ApplyManifest, self).__init__() super(ApplyManifest, self).__init__()
self.ctx = ctx self.ctx = ctx
# Filename can also be a URL reference # Filename can also be a URL reference
@ -120,6 +150,7 @@ class ApplyManifest(CliAction):
self.timeout = timeout self.timeout = timeout
self.values = values self.values = values
self.wait = wait self.wait = wait
self.target_manifest = target_manifest
def output(self, resp): def output(self, resp):
for result in resp: for result in resp:
@ -153,7 +184,8 @@ class ApplyManifest(CliAction):
armada = Armada( armada = Armada(
documents, self.disable_update_pre, self.disable_update_post, documents, self.disable_update_pre, self.disable_update_post,
self.enable_chart_cleanup, self.dry_run, self.set, self.wait, self.enable_chart_cleanup, self.dry_run, self.set, self.wait,
self.timeout, self.tiller_host, self.tiller_port, self.values) self.timeout, self.tiller_host, self.tiller_port, self.values,
self.target_manifest)
resp = armada.sync() resp = armada.sync()
self.output(resp) self.output(resp)

View File

@ -56,14 +56,19 @@ SHORT_DESC = "command test releases"
@click.option('--tiller-host', help="Tiller Host IP") @click.option('--tiller-host', help="Tiller Host IP")
@click.option( @click.option(
'--tiller-port', help="Tiller host Port", type=int, default=44134) '--tiller-port', help="Tiller host Port", type=int, default=44134)
@click.option('--target-manifest',
help=('The target manifest to run. Required for specifying '
'which manifest to run when multiple are available.'),
default=None)
@click.pass_context @click.pass_context
def test_charts(ctx, file, release, tiller_host, tiller_port): def test_charts(ctx, file, release, tiller_host, tiller_port, target_manifest):
TestChartManifest( TestChartManifest(
ctx, file, release, tiller_host, tiller_port).invoke() ctx, file, release, tiller_host, tiller_port).invoke()
class TestChartManifest(CliAction): class TestChartManifest(CliAction):
def __init__(self, ctx, file, release, tiller_host, tiller_port): def __init__(self, ctx, file, release, tiller_host, tiller_port,
target_manifest):
super(TestChartManifest, self).__init__() super(TestChartManifest, self).__init__()
self.ctx = ctx self.ctx = ctx
@ -71,6 +76,7 @@ class TestChartManifest(CliAction):
self.release = release self.release = release
self.tiller_host = tiller_host self.tiller_host = tiller_host
self.tiller_port = tiller_port self.tiller_port = tiller_port
self.target_manifest = target_manifest
def invoke(self): def invoke(self):
tiller = Tiller( tiller = Tiller(
@ -107,7 +113,9 @@ class TestChartManifest(CliAction):
if self.file: if self.file:
if not self.ctx.obj.get('api', False): if not self.ctx.obj.get('api', False):
documents = yaml.safe_load_all(open(self.file).read()) documents = yaml.safe_load_all(open(self.file).read())
armada_obj = Manifest(documents).get_manifest() armada_obj = Manifest(
documents,
target_manifest=self.target_manifest).get_manifest()
prefix = armada_obj.get(const.KEYWORD_ARMADA).get( prefix = armada_obj.get(const.KEYWORD_ARMADA).get(
const.KEYWORD_PREFIX) const.KEYWORD_PREFIX)

View File

@ -75,13 +75,13 @@ class ArmadaClient(object):
values=None, values=None,
set=None, set=None,
query=None): query=None):
"""Call the Armada API to apply a manifest. """Call the Armada API to apply a Manifest.
If manifest is not None, then the request body will be a fully If ``manifest`` is not None, then the request body will be a fully
rendered set of YAML documents including overrides and rendered set of YAML documents including overrides and
values-files application. values-files application.
If manifest is None and manifest_ref is not, then the request If ``manifest`` is None and ``manifest_ref`` is not, then the request
body will be a JSON structure providing a list of references body will be a JSON structure providing a list of references
to Armada manifest documents and a list of overrides. Local to Armada manifest documents and a list of overrides. Local
values files are not supported when using the API with references. values files are not supported when using the API with references.

View File

@ -191,7 +191,7 @@ class AppError(Exception):
""" """
:param description: The internal error description :param description: The internal error description
:param error_list: The list of errors :param error_list: The list of errors
:param status: The desired falcon HTTP resposne code :param status: The desired falcon HTTP response code
:param title: The title of the error message :param title: The title of the error message
:param error_list: A list of errors to be included in output :param error_list: A list of errors to be included in output
messages list messages list

View File

@ -0,0 +1,18 @@
# 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 armada.exceptions.manifest_exceptions import ManifestException
__all__ = ['ManifestException']

View File

@ -27,8 +27,12 @@ CONF = cfg.CONF
class ArmadaBaseException(Exception): class ArmadaBaseException(Exception):
'''Base class for Armada exception and error handling.''' '''Base class for Armada exception and error handling.'''
def __init__(self, message=None): def __init__(self, message=None, **kwargs):
self.message = message or self.message self.message = message or self.message
try:
self.message = self.message % kwargs
except TypeError:
pass
super(ArmadaBaseException, self).__init__(self.message) super(ArmadaBaseException, self).__init__(self.message)

View File

@ -0,0 +1,19 @@
# 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 armada.exceptions import base_exception as base
class ManifestException(base.ArmadaBaseException):
message = 'An error occurred while generating the manifest: %(details)s.'

View File

@ -54,10 +54,23 @@ class Armada(object):
timeout=DEFAULT_TIMEOUT, timeout=DEFAULT_TIMEOUT,
tiller_host=None, tiller_host=None,
tiller_port=44134, tiller_port=44134,
values=None): values=None,
target_manifest=None):
''' '''
Initialize the Armada Engine and establish Initialize the Armada engine and establish a connection to Tiller.
a connection to Tiller
:param List[dict] file: Armada documents.
:param bool disable_update_pre: Disable pre-update Tiller operations.
:param bool disable_update_post: Disable post-update Tiller
operations.
:param bool enable_chart_cleanup: Clean up unmanaged charts.
:param bool dry_run: Run charts without installing them.
:param bool wait: Wait until all charts are deployed.
:param int timeout: Specifies time to wait for charts to deploy.
:param str tiller_host: Tiller host IP.
:param int tiller_port: Tiller host port.
:param str target_manifest: The target manifest to run. Useful for
specifying which manifest to run when multiple are available.
''' '''
self.disable_update_pre = disable_update_pre self.disable_update_pre = disable_update_pre
self.disable_update_post = disable_update_post self.disable_update_post = disable_update_post
@ -70,9 +83,13 @@ class Armada(object):
self.values = values self.values = values
self.documents = file self.documents = file
self.config = None self.config = None
self.target_manifest = target_manifest
def get_armada_manifest(self): def get_armada_manifest(self):
return Manifest(self.documents).get_manifest() return Manifest(
self.documents,
target_manifest=self.target_manifest
).get_manifest()
def find_release_chart(self, known_releases, name): def find_release_chart(self, known_releases, name):
''' '''
@ -303,7 +320,7 @@ class Armada(object):
timeout=chart_timeout) timeout=chart_timeout)
if chart_wait: if chart_wait:
# TODO(gardlt): after v0.7.1 depricate timeout values # TODO(gardlt): after v0.7.1 deprecate timeout values
if not wait_values.get('timeout', None): if not wait_values.get('timeout', None):
wait_values['timeout'] = chart_timeout wait_values['timeout'] = chart_timeout

View File

@ -12,44 +12,97 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from armada.const import DOCUMENT_CHART, DOCUMENT_GROUP, DOCUMENT_MANIFEST from oslo_log import log as logging
from armada import const
from armada import exceptions
LOG = logging.getLogger(__name__)
class Manifest(object): class Manifest(object):
def __init__(self, documents):
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.config = None self.config = None
self.documents = documents self.documents = documents
self.charts = [] self.charts, self.groups, manifests = self._find_documents(
self.groups = [] target_manifest)
self.manifest = None
self.get_documents()
def get_documents(self): 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, expected_schemas)
raise exceptions.ManifestException(
details=error % expected_schemas)
def _find_documents(self, target_manifest=None):
charts = []
groups = []
manifests = []
for document in self.documents: for document in self.documents:
if document.get('schema') == DOCUMENT_CHART: if document.get('schema') == const.DOCUMENT_CHART:
self.charts.append(document) charts.append(document)
if document.get('schema') == DOCUMENT_GROUP: if document.get('schema') == const.DOCUMENT_GROUP:
self.groups.append(document) groups.append(document)
if document.get('schema') == DOCUMENT_MANIFEST: if document.get('schema') == const.DOCUMENT_MANIFEST:
self.manifest = document 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): def find_chart_document(self, name):
try: for chart in self.charts:
for chart in self.charts: if chart.get('metadata', {}).get('name') == name:
if chart.get('metadata').get('name') == name: return chart
return chart raise exceptions.ManifestException(
except Exception: details='Could not find a {} named "{}"'.format(
raise Exception( const.DOCUMENT_CHART, name))
"Could not find {} in {}".format(name, DOCUMENT_CHART))
def find_chart_group_document(self, name): def find_chart_group_document(self, name):
try: for group in self.groups:
for group in self.groups: if group.get('metadata', {}).get('name') == name:
if group.get('metadata').get('name') == name: return group
return group raise exceptions.ManifestException(
except Exception: details='Could not find a {} named "{}"'.format(
raise Exception( const.DOCUMENT_GROUP, name))
"Could not find {} in {}".format(name, DOCUMENT_GROUP))
def build_charts_deps(self): def build_charts_deps(self):
for chart in self.charts: for chart in self.charts:
@ -71,9 +124,9 @@ class Manifest(object):
'chart': chart_dep.get('data') 'chart': chart_dep.get('data')
} }
except Exception: except Exception:
raise Exception( raise exceptions.ManifestException(
"Could not find dependency chart {} in {}".format( details="Could not find dependency chart {} in {}".format(
dep, DOCUMENT_CHART)) dep, const.DOCUMENT_CHART))
def build_chart_group(self, chart_group): def build_chart_group(self, chart_group):
try: try:
@ -87,9 +140,9 @@ class Manifest(object):
'chart': chart_dep.get('data') 'chart': chart_dep.get('data')
} }
except Exception: except Exception:
raise Exception( raise exceptions.ManifestException(
"Could not find chart {} in {}".format( details="Could not find chart {} in {}".format(
chart, DOCUMENT_GROUP)) chart, const.DOCUMENT_GROUP))
def build_armada_manifest(self): def build_armada_manifest(self):
try: try:
@ -106,9 +159,9 @@ class Manifest(object):
self.manifest['data']['chart_groups'][iter] = ch_grp_data self.manifest['data']['chart_groups'][iter] = ch_grp_data
except Exception: except Exception:
raise Exception( raise exceptions.ManifestException(
"Could not find chart group {} in {}".format( "Could not find chart group {} in {}".format(
group, DOCUMENT_MANIFEST)) group, const.DOCUMENT_MANIFEST))
def get_manifest(self): def get_manifest(self):
self.build_charts_deps() self.build_charts_deps()

View File

@ -52,6 +52,7 @@ class ArmadaControllerTest(base.BaseControllerTest):
'timeout': 100, 'timeout': 100,
'tiller_host': None, 'tiller_host': None,
'tiller_port': 44134, 'tiller_port': 44134,
'target_manifest': None
} }
payload_url = 'http://foo.com/test.yaml' payload_url = 'http://foo.com/test.yaml'

View File

@ -0,0 +1,191 @@
# 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.
import copy
import os
import yaml
import testtools
from armada import const
from armada import exceptions
from armada.handlers import manifest
class ManifestTestCase(testtools.TestCase):
def setUp(self):
super(ManifestTestCase, self).setUp()
examples_dir = os.path.join(
os.getcwd(), 'armada', 'tests', 'unit', 'resources')
with open(os.path.join(examples_dir, 'keystone-manifest.yaml')) as f:
self.documents = list(yaml.safe_load_all(f.read()))
def test_get_documents(self):
armada_manifest = manifest.Manifest(self.documents)
self.assertIsInstance(armada_manifest.charts, list)
self.assertIsInstance(armada_manifest.groups, list)
self.assertIsNotNone(armada_manifest.manifest)
self.assertEqual(4, len(armada_manifest.charts))
self.assertEqual(2, len(armada_manifest.groups))
self.assertEqual([self.documents[x] for x in range(4)],
armada_manifest.charts)
self.assertEqual([self.documents[x] for x in range(4, 6)],
armada_manifest.groups)
self.assertEqual(self.documents[-1], armada_manifest.manifest)
def test_get_documents_with_target_manifest(self):
# Validate that specifying `target_manifest` flag returns the correct
# manifest.
armada_manifest = manifest.Manifest(
self.documents, target_manifest='armada-manifest')
self.assertIsInstance(armada_manifest.charts, list)
self.assertIsInstance(armada_manifest.groups, list)
self.assertIsNotNone(armada_manifest.manifest)
self.assertEqual(4, len(armada_manifest.charts))
self.assertEqual(2, len(armada_manifest.groups))
self.assertEqual([self.documents[x] for x in range(4)],
armada_manifest.charts)
self.assertEqual([self.documents[x] for x in range(4, 6)],
armada_manifest.groups)
self.assertEqual(self.documents[-1], armada_manifest.manifest)
self.assertEqual('armada-manifest',
self.documents[-1]['metadata']['name'])
def test_get_documents_with_multi_manifest_and_target_manifest(self):
# Validate that specifying `target_manifest` flag returns the correct
# manifest even if there are multiple existing manifests. (Only works
# when the manifest names are distinct or else should raise error.)
documents = copy.deepcopy(self.documents)
other_manifest = copy.deepcopy(self.documents[-1])
other_manifest['metadata']['name'] = 'alt-armada-manifest'
documents.append(other_manifest)
# Specify the "original" manifest and verify it works.
armada_manifest = manifest.Manifest(
documents, target_manifest='armada-manifest')
self.assertIsInstance(armada_manifest.charts, list)
self.assertIsInstance(armada_manifest.groups, list)
self.assertIsNotNone(armada_manifest.manifest)
self.assertEqual(4, len(armada_manifest.charts))
self.assertEqual(2, len(armada_manifest.groups))
self.assertEqual([self.documents[x] for x in range(4)],
armada_manifest.charts)
self.assertEqual([self.documents[x] for x in range(4, 6)],
armada_manifest.groups)
self.assertEqual(armada_manifest.manifest, self.documents[-1])
self.assertEqual('armada-manifest',
armada_manifest.manifest['metadata']['name'])
# Specify the alternative manifest and verify it works.
armada_manifest = manifest.Manifest(
documents, target_manifest='alt-armada-manifest')
self.assertIsNotNone(armada_manifest.manifest)
self.assertEqual(other_manifest, armada_manifest.manifest)
self.assertEqual('alt-armada-manifest',
armada_manifest.manifest['metadata']['name'])
def test_find_chart_document(self):
armada_manifest = manifest.Manifest(self.documents)
chart = armada_manifest.find_chart_document('helm-toolkit')
self.assertEqual(self.documents[0], chart)
def test_find_group_document(self):
armada_manifest = manifest.Manifest(self.documents)
chart = armada_manifest.find_chart_group_document('openstack-keystone')
self.assertEqual(self.documents[-2], chart)
class ManifestNegativeTestCase(testtools.TestCase):
def setUp(self):
super(ManifestNegativeTestCase, self).setUp()
examples_dir = os.path.join(
os.getcwd(), 'armada', 'tests', 'unit', 'resources')
with open(os.path.join(examples_dir, 'keystone-manifest.yaml')) as f:
self.documents = list(yaml.safe_load_all(f.read()))
def test_get_documents_multi_manifests_raises_value_error(self):
# Validates that finding multiple manifests without `target_manifest`
# flag raises exceptions.ManifestException.
documents = copy.deepcopy(self.documents)
documents.append(documents[-1]) # Copy the last manifest.
error_re = r'Multiple manifests are not supported.*'
self.assertRaisesRegexp(
exceptions.ManifestException, error_re, manifest.Manifest,
documents)
def test_get_documents_multi_target_manifests_raises_value_error(self):
# Validates that finding multiple manifests with `target_manifest`
# flag raises exceptions.ManifestException.
documents = copy.deepcopy(self.documents)
documents.append(documents[-1]) # Copy the last manifest.
error_re = r'Multiple manifests are not supported.*'
self.assertRaisesRegexp(
exceptions.ManifestException, error_re, manifest.Manifest,
documents, target_manifest='armada-manifest')
def test_get_documents_missing_manifest(self):
# Validates exceptions.ManifestException is thrown if no manifest is
# found. Manifest is last document in sample YAML.
error_re = ('Documents must be a list of documents with at least one '
'of each of the following schemas: .*')
self.assertRaisesRegexp(
exceptions.ManifestException, error_re, manifest.Manifest,
self.documents[:-1])
def test_get_documents_missing_charts(self):
# Validates exceptions.ManifestException is thrown if no chart is
# found. Charts are first 4 documents in sample YAML.
error_re = ('Documents must be a list of documents with at least one '
'of each of the following schemas: .*')
self.assertRaisesRegexp(
exceptions.ManifestException, error_re, manifest.Manifest,
self.documents[4:])
def test_get_documents_missing_chart_groups(self):
# Validates exceptions.ManifestException is thrown if no chart is
# found. ChartGroups are 5-6 documents in sample YAML.
documents = self.documents[:4] + [self.documents[-1]]
error_re = ('Documents must be a list of documents with at least one '
'of each of the following schemas: .*')
self.assertRaisesRegexp(
exceptions.ManifestException, error_re, manifest.Manifest,
documents)
def test_find_chart_document_negative(self):
armada_manifest = manifest.Manifest(self.documents)
error_re = r'Could not find a %s named "%s"' % (
const.DOCUMENT_CHART, 'invalid')
self.assertRaisesRegexp(exceptions.ManifestException, error_re,
armada_manifest.find_chart_document, 'invalid')
def test_find_group_document_negative(self):
armada_manifest = manifest.Manifest(self.documents)
error_re = r'Could not find a %s named "%s"' % (
const.DOCUMENT_GROUP, 'invalid')
self.assertRaisesRegexp(exceptions.ManifestException, error_re,
armada_manifest.find_chart_group_document,
'invalid')

View File

@ -0,0 +1,135 @@
---
schema: armada/Chart/v1
metadata:
schema: metadata/Document/v1
name: helm-toolkit
data:
chart_name: helm-toolkit
release: helm-toolkit
namespace: helm-tookit
values: {}
source:
type: git
location: https://git.openstack.org/openstack/openstack-helm
subpath: helm-toolkit
reference: master
dependencies: []
---
schema: armada/Chart/v1
metadata:
schema: metadata/Document/v1
name: mariadb
data:
chart_name: mariadb
release: mariadb
namespace: openstack
timeout: 3600
wait:
timeout: 3600
labels:
release_group: armada-mariadb
install:
no_hooks: false
upgrade:
no_hooks: false
values: {}
source:
type: git
location: https://git.openstack.org/openstack/openstack-helm
subpath: mariadb
reference: master
dependencies:
- helm-toolkit
---
schema: armada/Chart/v1
metadata:
schema: metadata/Document/v1
name: memcached
data:
chart_name: memcached
release: memcached
namespace: openstack
timeout: 100
wait:
timeout: 100
labels:
release_group: armada-memcached
install:
no_hooks: false
upgrade:
no_hooks: false
values: {}
source:
type: git
location: https://git.openstack.org/openstack/openstack-helm
subpath: memcached
reference: master
dependencies:
- helm-toolkit
---
schema: armada/Chart/v1
metadata:
schema: metadata/Document/v1
name: keystone
data:
chart_name: keystone
test: true
release: keystone
namespace: openstack
timeout: 100
wait:
timeout: 100
labels:
release_group: armada-keystone
install:
no_hooks: false
upgrade:
no_hooks: false
pre:
delete:
- name: keystone-bootstrap
type: job
labels:
application: keystone
component: bootstrap
values:
replicas: 3
source:
type: git
location: https://git.openstack.org/openstack/openstack-helm
subpath: keystone
reference: master
dependencies:
- helm-toolkit
---
schema: armada/ChartGroup/v1
metadata:
schema: metadata/Document/v1
name: keystone-infra-services
data:
description: "Keystone Infra Services"
sequenced: True
chart_group:
- mariadb
- memcached
---
schema: armada/ChartGroup/v1
metadata:
schema: metadata/Document/v1
name: openstack-keystone
data:
description: "Deploying OpenStack Keystone"
sequenced: True
test_charts: False
chart_group:
- keystone
---
schema: armada/Manifest/v1
metadata:
schema: metadata/Document/v1
name: armada-manifest
data:
release_prefix: armada
chart_groups:
- keystone-infra-services
- openstack-keystone

View File

@ -31,17 +31,22 @@ Commands
$ armada apply examples/simple.yaml --values examples/simple-ovr-values.yaml $ armada apply examples/simple.yaml --values examples/simple-ovr-values.yaml
Options: Options:
--api Contacts service endpoint --api Contacts service endpoint.
--disable-update-post run charts without install --disable-update-post Disable post-update Tiller operations.
--disable-update-pre run charts without install --disable-update-pre Disable pre-update Tiller operations.
--dry-run run charts without install --dry-run Run charts without installing them.
--enable-chart-cleanup Clean up Unmanaged Charts --enable-chart-cleanup Clean up unmanaged charts.
--set TEXT --set TEXT Use to override Armada Manifest values. Accepts
--tiller-host TEXT Tiller host ip overrides that adhere to the format <key>=<value>
--tiller-port INTEGER Tiller host port --tiller-host TEXT Tiller host IP.
--timeout INTEGER specifies time to wait for charts --tiller-port INTEGER Tiller host port.
-f, --values TEXT --timeout INTEGER Specifies time to wait for charts to deploy.
--wait wait until all charts deployed -f, --values TEXT Use to override multiple Armada Manifest values by
reading overrides from a values.yaml-type file.
--wait Wait until all charts deployed.
--target-manifest TEXT The target manifest to run. Required for specifying
which manifest to run when multiple are available.
--debug / --no-debug Enable or disable debugging.
--help Show this message and exit. --help Show this message and exit.
Synopsis Synopsis

View File

@ -24,11 +24,13 @@ Commands
$ armada test --release blog-1 $ armada test --release blog-1
Options: Options:
--file TEXT armada manifest --file TEXT armada manifest
--release TEXT helm release --release TEXT helm release
--tiller-host TEXT Tiller Host IP --tiller-host TEXT Tiller Host IP
--tiller-port INTEGER Tiller host Port --tiller-port INTEGER Tiller host Port
--help Show this message and exit. --help Show this message and exit.
--target-manifest TEXT The target manifest to run. Required for specifying
which manifest to run when multiple are available.
Synopsis Synopsis

View File

@ -89,3 +89,13 @@ Lint Exceptions
+----------------------------------+------------------------------+ +----------------------------------+------------------------------+
| InvalidArmadaObjectException | Armada object not declared. | | InvalidArmadaObjectException | Armada object not declared. |
+----------------------------------+------------------------------+ +----------------------------------+------------------------------+
Manifest Exceptions
===================
+----------------------------------+------------------------------------------------+
| Exception | Error Description |
+==================================+================================================+
| ManifestException | An exception occurred while attempting to build|
| | an Armada manifest. The exception will return |
| | with details as to why. |
+----------------------------------+------------------------------------------------+