Introduce v2 docs

This introduces v2 docs in order to allow users to opt in to
breaking changes, while still supporting v1 docs for a time
so folks can migrate. At some point v1 doc support will be
removed.

This initial version of v2 docs is experimental. Further
breaking changes will be made before v2 docs are finalized.

A v1-v2 migration guide is included in the documentation.

This also refactors the internal data model to include the full
document structure, such as `metadata` and `schema`, so that
different behavior can be acheived for v1, v2, etc.

Change-Id: Ia0d44ff4276ef4c27f78706ab02c88aa421a307f
This commit is contained in:
Sean Eagan 2019-04-15 10:17:29 -05:00
parent 9a43213198
commit 8a50591dbf
37 changed files with 1452 additions and 421 deletions

View File

@ -133,12 +133,12 @@ class TestReleasesManifestController(api.BaseResource):
armada_obj = Manifest(
documents, target_manifest=target_manifest).get_manifest()
prefix = armada_obj.get(const.KEYWORD_ARMADA).get(const.KEYWORD_PREFIX)
prefix = armada_obj[const.KEYWORD_DATA][const.KEYWORD_PREFIX]
known_releases = [release[0] for release in tiller.list_charts()]
message = {'tests': {'passed': [], 'skipped': [], 'failed': []}}
for group in armada_obj.get(const.KEYWORD_ARMADA).get(
for group in armada_obj.get(const.KEYWORD_DATA).get(
const.KEYWORD_GROUPS):
for ch in group.get(const.KEYWORD_CHARTS):
chart = ch['chart']

View File

@ -126,13 +126,14 @@ class DeleteChartManifest(CliAction):
documents = list(yaml.safe_load_all(f.read()))
try:
armada_obj = Manifest(documents).get_manifest()
prefix = armada_obj.get(const.KEYWORD_ARMADA).get(
prefix = armada_obj.get(const.KEYWORD_DATA).get(
const.KEYWORD_PREFIX)
for group in armada_obj.get(const.KEYWORD_ARMADA).get(
for group in armada_obj.get(const.KEYWORD_DATA).get(
const.KEYWORD_GROUPS):
for ch in group.get(const.KEYWORD_CHARTS):
chart = ch.get('chart')
for ch in group.get(const.KEYWORD_DATA).get(
const.KEYWORD_CHARTS):
chart = ch.get(const.KEYWORD_DATA)
release_name = release_prefixer(
prefix, chart.get('release'))
if release_name in known_release_names:

View File

@ -147,10 +147,10 @@ class TestChartManifest(CliAction):
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_DATA).get(
const.KEYWORD_PREFIX)
for group in armada_obj.get(const.KEYWORD_ARMADA).get(
for group in armada_obj.get(const.KEYWORD_DATA).get(
const.KEYWORD_GROUPS):
for ch in group.get(const.KEYWORD_CHARTS):
chart = ch['chart']

View File

@ -88,7 +88,7 @@ class ChartDeployAwareLogger(logging.Logger):
def _log(self, level, msg, *args, **kwargs):
chart = get_current_chart()
if chart:
name = chart['chart_name']
name = chart['metadata']['name']
prefix = '[chart={}]: '.format(name)
else:
prefix = ''

View File

@ -12,11 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Documents
DOCUMENT_CHART = 'armada/Chart/v1'
DOCUMENT_GROUP = 'armada/ChartGroup/v1'
DOCUMENT_MANIFEST = 'armada/Manifest/v1'
KEYWORD_ARMADA = 'armada'
# Keywords
KEYWORD_DATA = 'data'
KEYWORD_PREFIX = 'release_prefix'
KEYWORD_GROUPS = 'chart_groups'
KEYWORD_CHARTS = 'chart_group'

View File

@ -104,13 +104,14 @@ class Armada(object):
raise tiller_exceptions.TillerServicesUnavailableException()
# Clone the chart sources
manifest_data = self.manifest.get(const.KEYWORD_ARMADA, {})
manifest_data = self.manifest.get(const.KEYWORD_DATA, {})
for group in manifest_data.get(const.KEYWORD_GROUPS, []):
for ch in group.get(const.KEYWORD_CHARTS, []):
for ch in group.get(const.KEYWORD_DATA).get(
const.KEYWORD_CHARTS, []):
self.get_chart(ch)
def get_chart(self, ch):
chart = ch.get('chart', {})
chart = ch.get(const.KEYWORD_DATA)
chart_source = chart.get('source', {})
location = chart_source.get('location')
ct_type = chart_source.get('type')
@ -158,10 +159,10 @@ class Armada(object):
self.chart_cache[source_key] = repo_dir
chart['source_dir'] = (self.chart_cache.get(source_key), subpath)
else:
chart_name = chart.get('chart_name')
raise source_exceptions.ChartSourceException(ct_type, chart_name)
name = chart['metadata']['name']
raise source_exceptions.ChartSourceException(ct_type, name)
for dep in ch.get('chart', {}).get('dependencies', []):
for dep in ch.get(const.KEYWORD_DATA, {}).get('dependencies', []):
self.get_chart(dep)
def sync(self):
@ -185,11 +186,12 @@ class Armada(object):
known_releases = self.tiller.list_releases()
manifest_data = self.manifest.get(const.KEYWORD_ARMADA, {})
manifest_data = self.manifest.get(const.KEYWORD_DATA, {})
prefix = manifest_data.get(const.KEYWORD_PREFIX)
for chartgroup in manifest_data.get(const.KEYWORD_GROUPS, []):
cg_name = chartgroup.get('name', '<missing name>')
for cg in manifest_data.get(const.KEYWORD_GROUPS, []):
chartgroup = cg.get(const.KEYWORD_DATA)
cg_name = cg.get('metadata').get('name')
cg_desc = chartgroup.get('description', '<missing description>')
cg_sequenced = chartgroup.get('sequenced',
False) or self.force_wait
@ -198,11 +200,10 @@ class Armada(object):
cg_desc, cg_sequenced,
' (forced)' if self.force_wait else '')
# TODO(MarshM): Deprecate the `test_charts` key
# TODO: Remove when v1 doc support is removed.
cg_test_all_charts = chartgroup.get('test_charts')
cg_charts = chartgroup.get(const.KEYWORD_CHARTS, [])
charts = map(lambda x: x.get('chart', {}), cg_charts)
def deploy_chart(chart):
set_current_chart(chart)
@ -217,7 +218,7 @@ class Armada(object):
# Returns whether or not there was a failure
def handle_result(chart, get_result):
name = chart['chart_name']
name = chart['metadata']['name']
try:
result = get_result()
except Exception:
@ -229,7 +230,7 @@ class Armada(object):
return False
if cg_sequenced:
for chart in charts:
for chart in cg_charts:
if (handle_result(chart, lambda: deploy_chart(chart))):
break
else:
@ -237,7 +238,7 @@ class Armada(object):
max_workers=len(cg_charts)) as executor:
future_to_chart = {
executor.submit(deploy_chart, chart): chart
for chart in charts
for chart in cg_charts
}
for future in as_completed(future_to_chart):
@ -260,7 +261,7 @@ class Armada(object):
if self.enable_chart_cleanup:
self._chart_cleanup(
prefix,
self.manifest[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS], msg)
self.manifest[const.KEYWORD_DATA][const.KEYWORD_GROUPS], msg)
LOG.info('Done applying manifest.')
return msg

View File

@ -41,7 +41,8 @@ class ChartDeploy(object):
self.timeout = timeout
self.tiller = tiller
def execute(self, chart, cg_test_all_charts, prefix, known_releases):
def execute(self, ch, cg_test_all_charts, prefix, known_releases):
chart = ch[const.KEYWORD_DATA]
namespace = chart.get('namespace')
release = chart.get('release')
release_name = r.release_prefixer(prefix, release)
@ -73,7 +74,7 @@ class ChartDeploy(object):
# Begin Chart timeout deadline
deadline = time.time() + chart_wait.get_timeout()
chartbuilder = ChartBuilder(chart)
chartbuilder = ChartBuilder(ch)
new_chart = chartbuilder.get_helm_chart()
# TODO(mark-burnett): It may be more robust to directly call

View File

@ -25,6 +25,7 @@ from oslo_config import cfg
from oslo_log import log as logging
from armada.exceptions import chartbuilder_exceptions
from armada import const
LOG = logging.getLogger(__name__)
@ -49,6 +50,7 @@ class ChartBuilder(object):
# store chart schema
self.chart = chart
self.chart_data = chart[const.KEYWORD_DATA]
# extract, pull, whatever the chart from its source
self.source_directory = self.get_source_path()
@ -62,7 +64,7 @@ class ChartBuilder(object):
Returns "<source directory>/<subpath>" taken from the "source_dir"
property from the chart, or else "" if the property isn't a 2-tuple.
'''
source_dir = self.chart.get('source_dir')
source_dir = self.chart_data.get('source_dir')
return (os.path.join(*source_dir) if
(source_dir and isinstance(source_dir, (list, tuple)) and
len(source_dir) == 2) else "")
@ -206,7 +208,7 @@ class ChartBuilder(object):
Process all files in templates/ as a template to attach to the chart,
building a :class:`hapi.chart.template_pb2.Template` object.
'''
chart_name = self.chart.get('chart_name')
chart_name = self.chart['metadata']['name']
templates = []
if not os.path.exists(
os.path.join(self.source_directory, 'templates')):
@ -240,12 +242,11 @@ class ChartBuilder(object):
return self._helm_chart
dependencies = []
chart_dependencies = self.chart.get('dependencies', [])
chart_name = self.chart.get('chart_name', None)
chart_release = self.chart.get('release', None)
for dep in chart_dependencies:
dep_chart = dep.get('chart', {})
dep_chart_name = dep_chart.get('chart_name', None)
chart_dependencies = self.chart_data.get('dependencies', [])
chart_name = self.chart['metadata']['name']
chart_release = self.chart_data.get('release', None)
for dep_chart in chart_dependencies:
dep_chart_name = dep_chart['metadata']['name']
LOG.info("Building dependency chart %s for release %s.",
dep_chart_name, chart_release)
try:

View File

@ -17,6 +17,7 @@ from oslo_log import log as logging
from armada import const
from armada import exceptions
from armada.handlers import schema
LOG = logging.getLogger(__name__)
@ -62,10 +63,10 @@ class Manifest(object):
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)
expected_schemas = [schema.TYPE_CHART, schema.TYPE_CHARTGROUP]
error = ('Documents must include at least one of each of {} '
'and only one {}').format(expected_schemas,
schema.TYPE_MANIFEST)
LOG.error(error)
raise exceptions.ManifestException(details=error)
@ -87,11 +88,14 @@ class Manifest(object):
groups = []
manifests = []
for document in self.documents:
if document.get('schema') == const.DOCUMENT_CHART:
schema_info = schema.get_schema_info(document.get('schema'))
if not schema_info:
continue
if schema_info.type == schema.TYPE_CHART:
charts.append(document)
if document.get('schema') == const.DOCUMENT_GROUP:
if schema_info.type == schema.TYPE_CHARTGROUP:
groups.append(document)
if document.get('schema') == const.DOCUMENT_MANIFEST:
if schema_info.type == schema.TYPE_MANIFEST:
manifest_name = document.get('metadata', {}).get('name')
if target_manifest:
if manifest_name == target_manifest:
@ -113,8 +117,8 @@ class Manifest(object):
if chart.get('metadata', {}).get('name') == name:
return chart
raise exceptions.BuildChartException(
details='Could not build {} named "{}"'.format(
const.DOCUMENT_CHART, name))
details='Could not find {} named "{}"'.format(
schema.TYPE_CHART, name))
def find_chart_group_document(self, name):
"""Returns a chart group document with the specified name
@ -129,8 +133,8 @@ class Manifest(object):
if group.get('metadata', {}).get('name') == name:
return group
raise exceptions.BuildChartGroupException(
details='Could not build {} named "{}"'.format(
const.DOCUMENT_GROUP, name))
details='Could not find {} named "{}"'.format(
schema.TYPE_CHARTGROUP, name))
def build_chart_deps(self, chart):
"""Recursively build chart dependencies for ``chart``.
@ -143,20 +147,19 @@ class Manifest(object):
under ``chart['data']['dependencies']`` could not be found.
"""
try:
chart_dependencies = chart.get('data', {}).get('dependencies', [])
chart_dependencies = chart.get(const.KEYWORD_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', {})
}
chart[const.KEYWORD_DATA]['dependencies'][iter] = chart_dep
except Exception:
raise exceptions.ChartDependencyException(
details="Could not build dependencies for chart {} in {}".
format(
chart.get('metadata').get('name'), const.DOCUMENT_CHART))
details='Could not build dependencies for {} named "{}"'.
format(schema.TYPE_CHART,
chart.get('metadata').get('name')))
else:
return chart
@ -173,19 +176,19 @@ class Manifest(object):
try:
chart = None
for iter, chart in enumerate(
chart_group.get('data', {}).get('chart_group', [])):
chart_group.get(const.KEYWORD_DATA).get(
const.KEYWORD_CHARTS, [])):
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', {})
}
chart_object = self.find_chart_document(chart)
self.build_chart_deps(chart_object)
chart_group[const.KEYWORD_DATA][const.KEYWORD_CHARTS][iter] = \
chart_object
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))
details='Could not build {} named "{}"'.format(
schema.TYPE_CHARTGROUP, cg_name))
return chart_group
@ -196,20 +199,18 @@ class Manifest(object):
: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.
under ``chart_group[const.KEYWORD_DATA]`` could not be found.
"""
for iter, group in enumerate(
self.manifest.get('data', {}).get('chart_groups', [])):
self.manifest.get(const.KEYWORD_DATA, {}).get(
const.KEYWORD_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
self.manifest[const.KEYWORD_DATA][const.KEYWORD_GROUPS][iter] = \
chart_grp
return self.manifest
@ -221,4 +222,4 @@ class Manifest(object):
"""
self.build_armada_manifest()
return {'armada': self.manifest.get('data', {})}
return self.manifest

View File

@ -16,9 +16,9 @@ import collections
import json
import yaml
from armada import const
from armada.exceptions import override_exceptions
from armada.exceptions import validate_exceptions
from armada.handlers import schema
from armada.utils import validate
@ -65,17 +65,18 @@ class Override(object):
def find_document_type(self, alias):
if alias == 'chart_group':
return const.DOCUMENT_GROUP
return schema.TYPE_CHARTGROUP
if alias == 'chart':
return const.DOCUMENT_CHART
return schema.TYPE_CHART
if alias == 'manifest':
return const.DOCUMENT_MANIFEST
return schema.TYPE_MANIFEST
else:
raise ValueError("Could not find {} document".format(alias))
def find_manifest_document(self, doc_path):
for doc in self.documents:
if doc.get('schema') == self.find_document_type(
schema_info = schema.get_schema_info(doc.get('schema'))
if schema_info.type == self.find_document_type(
doc_path[0]) and doc.get('metadata',
{}).get('name') == doc_path[1]:
return doc
@ -121,45 +122,29 @@ class Override(object):
new_data = self.array_to_dict(data_path, new_value)
self.update(document.get('data', {}), new_data)
def update_document(self, merging_values):
def update_documents(self, merging_values):
for doc in merging_values:
if doc.get('schema') == const.DOCUMENT_CHART:
self.update_chart_document(doc)
if doc.get('schema') == const.DOCUMENT_GROUP:
self.update_chart_group_document(doc)
if doc.get('schema') == const.DOCUMENT_MANIFEST:
self.update_armada_manifest(doc)
self.update_document(doc)
def update_chart_document(self, ovr):
for doc in self.documents:
if doc.get('schema') == const.DOCUMENT_CHART and doc.get(
'metadata', {}).get('name') == ovr.get('metadata',
{}).get('name'):
self.update(doc.get('data', {}), ovr.get('data', {}))
return
def update_chart_group_document(self, ovr):
for doc in self.documents:
if doc.get('schema') == const.DOCUMENT_GROUP and doc.get(
'metadata', {}).get('name') == ovr.get('metadata',
{}).get('name'):
self.update(doc.get('data', {}), ovr.get('data', {}))
return
def update_armada_manifest(self, ovr):
for doc in self.documents:
if doc.get('schema') == const.DOCUMENT_MANIFEST and doc.get(
'metadata', {}).get('name') == ovr.get('metadata',
{}).get('name'):
self.update(doc.get('data', {}), ovr.get('data', {}))
return
def update_document(self, ovr):
ovr_schema_info = schema.get_schema_info(ovr.get('schema'))
if ovr_schema_info:
for doc in self.documents:
schema_info = schema.get_schema_info(doc.get('schema'))
if schema_info:
if schema_info == ovr_schema_info:
if doc['metadata']['name'] == ovr['metadata']['name']:
data = doc.get('data', {})
ovr_data = ovr.get('data', {})
self.update(data, ovr_data)
return
def update_manifests(self):
if self.values:
for value in self.values:
merging_values = self._load_yaml_file(value)
self.update_document(merging_values)
self.update_documents(merging_values)
# Validate document with updated values
self._document_checker(self.documents, self.values)

80
armada/handlers/schema.py Normal file
View File

@ -0,0 +1,80 @@
# Copyright 2019 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 os
import pkg_resources
import re
import yaml
# Types
TYPE_CHART = 'Chart'
TYPE_CHARTGROUP = 'ChartGroup'
TYPE_MANIFEST = 'Manifest'
# Versions
VERSION_FORMAT = r'^v(\d+)$'
VERSION_MIN = 1
VERSION_MAX = 2
# Creates a mapping between ``metadata.name``: ``data`` where the
# ``metadata.name`` is the ``schema`` of a manifest and the ``data`` is the
# JSON schema to be used to validate the manifest in question.
_SCHEMAS = {}
class SchemaInfo(object):
def __init__(self, type, version, data):
self.type = type
self.version = version
self.data = data
def __eq__(self, other):
return self.type == other.type and self.version == other.version
def get_schema_info(name):
return _SCHEMAS.get(name)
def _get_schema_info(name, data):
parts = name.split('/')
prefix, type, version_string = parts
version_match = re.search(VERSION_FORMAT, version_string)
version = int(version_match.group(1))
return SchemaInfo(type, version, data)
def _get_schema_dir():
return pkg_resources.resource_filename('armada', 'schemas')
def _load_schemas():
"""Populates ``_SCHEMAS`` with the schemas defined in package
``armada.schemas``.
"""
schema_dir = _get_schema_dir()
for schema_file in os.listdir(schema_dir):
with open(os.path.join(schema_dir, schema_file)) as f:
for schema in yaml.safe_load_all(f):
name = schema['metadata']['name']
if name in _SCHEMAS:
raise RuntimeError(
'Duplicate schema specified for: %s.' % name)
_SCHEMAS[name] = _get_schema_info(name, schema['data'])
# Fill the cache.
_load_schemas()

View File

@ -60,9 +60,7 @@ class Test(object):
self.timeout = const.DEFAULT_TEST_TIMEOUT
# NOTE(drewwalters96): Support the chart_group `test_charts` key until
# its deprecation period ends. The `test.enabled`, `enable_all` flag,
# and deprecated, boolean `test` key override this value if provided.
# TODO: Remove when v1 doc support is removed.
if cg_test_charts is not None:
LOG.warn('Chart group key `test_charts` is deprecated and will be '
'removed. Use `test.enabled` instead.')
@ -70,7 +68,7 @@ class Test(object):
else:
self.test_enabled = True
# NOTE: Support old, boolean `test` key until deprecation period ends.
# TODO: Remove when v1 doc support is removed.
if (type(test_values) == bool):
LOG.warn('Boolean value for chart `test` key is deprecated and '
'will be removed. Use `test.enabled` instead.')

View File

@ -30,8 +30,10 @@ from oslo_config import cfg
from oslo_log import log as logging
from armada import const
from armada.conf import get_current_chart
from armada.exceptions import tiller_exceptions as ex
from armada.handlers.k8s import K8s
from armada.handlers import schema
from armada.utils import helm
from armada.utils.release import label_selectors, get_release_status
@ -303,6 +305,7 @@ class Tiller(object):
:param namespace: name of pod for actions
'''
# TODO: Remove when v1 doc support is removed.
try:
for action in actions.get('update', []):
name = action.get('name')
@ -667,15 +670,20 @@ class Tiller(object):
self.k8s.delete_job_action(jb_name, namespace, timeout=timeout)
handled = True
if resource_type == 'cronjob' or resource_type == 'job':
# TODO: Remove when v1 doc support is removed.
chart = get_current_chart()
schema_info = schema.get_schema_info(chart['schema'])
job_implies_cronjob = schema_info.version < 2
implied_cronjob = resource_type == 'job' and job_implies_cronjob
if resource_type == 'cronjob' or implied_cronjob:
get_jobs = self.k8s.get_namespace_cron_job(
namespace, label_selector=label_selector)
for jb in get_jobs.items:
jb_name = jb.metadata.name
if resource_type == 'job':
# TODO: Eventually disallow this, allowing initially since
# some existing clients were expecting this behavior.
# TODO: Remove when v1 doc support is removed.
if implied_cronjob:
LOG.warn("Deleting cronjobs via `type: job` is "
"deprecated, use `type: cronjob` instead")
@ -726,7 +734,7 @@ class Tiller(object):
values,
timeout=const.DEFAULT_TILLER_TIMEOUT):
'''
update statefullsets (daemon, stateful)
update statefulsets (daemon, stateful)
'''
if action_type == 'daemonset':

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# JSON schema for validating Armada charts.
# NOTE: Do not modify this schema, it is deprecated.
---
schema: deckhand/DataSchema/v1
metadata:
@ -60,7 +60,6 @@ data:
additionalProperties: false
test:
anyOf:
# TODO: Remove boolean support after deprecation period.
- type: boolean
- type: object
properties:
@ -75,7 +74,6 @@ data:
type: boolean
additionalProperties: false
additionalProperties: false
# TODO(MarshM): Deprecate this `timeout` in favor of `wait.timeout`
timeout:
type: integer
wait:
@ -153,8 +151,6 @@ data:
$ref: '#/definitions/hook_action'
create:
$ref: '#/definitions/hook_action'
# TODO(drewwalters96): Armada ignores post-update actions. Remove them
# in future schemas.
post:
type: object
additionalProperties: false

View File

@ -0,0 +1,151 @@
# 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.
# JSON schema for validating Armada charts.
---
schema: deckhand/DataSchema/v1
metadata:
name: armada/Chart/v2
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
definitions:
labels:
type: object
additionalProperties:
type: string
hook_action:
type: array
items:
properties:
type:
type: string
labels:
$ref: '#/definitions/labels'
required:
- type
additionalProperties: false
type: object
properties:
release:
type: string
namespace:
type: string
values:
type: object
# TODO: Remove this, and just read dependencies out of `chart` dir as helm
# CLI does.
dependencies:
type: array
items:
type: string
protected:
type: object
properties:
continue_processing:
type: boolean
additionalProperties: false
test:
type: object
properties:
enabled:
type: boolean
timeout:
type: integer
options:
type: object
properties:
cleanup:
type: boolean
additionalProperties: false
additionalProperties: false
wait:
type: object
properties:
timeout:
type: integer
resources:
type: array
items:
properties:
type:
type: string
labels:
$ref: '#/definitions/labels'
min_ready:
anyOf:
- type: integer
- type: string
required:
- type
additionalProperties: false
labels:
$ref: "#/definitions/labels"
# Config for helm's native `--wait` param.
native:
type: object
properties:
enabled:
type: boolean
additionalProperties: false
additionalProperties: false
source:
type: object
properties:
type:
type: string
location:
type: string
subpath:
type: string
reference:
type: string
proxy_server:
type: string
auth_method:
type: string
required:
- location
- type
delete:
type: object
properties:
timeout:
type: integer
upgrade:
type: object
properties:
no_hooks:
type: boolean
pre:
type: object
additionalProperties: false
properties:
delete:
$ref: '#/definitions/hook_action'
options:
type: object
properties:
force:
type: boolean
recreate_pods:
type: boolean
additionalProperties: false
additionalProperties: false
required:
- namespace
- release
- source
additionalProperties: false
...

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# JSON schema for validating Armada chart groups.
# NOTE: Do not modify this schema, it is deprecated.
---
schema: deckhand/DataSchema/v1
metadata:

View File

@ -0,0 +1,38 @@
# 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.
# JSON schema for validating Armada chart groups.
---
schema: deckhand/DataSchema/v1
metadata:
name: armada/ChartGroup/v2
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
name:
type: string
description:
type: string
sequenced:
type: boolean
chart_group:
type: array
items:
type: string
required:
# TODO: Rename to `charts`?
- chart_group
additionalProperties: false
...

View File

@ -0,0 +1,34 @@
# 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.
# NOTE: Do not modify this schema, it is deprecated.
---
schema: deckhand/DataSchema/v1
metadata:
name: armada/Manifest/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
release_prefix:
type: string
chart_groups:
type: array
items:
type: string
required:
- chart_groups
- release_prefix
additionalProperties: false
...

View File

@ -16,7 +16,7 @@
---
schema: deckhand/DataSchema/v1
metadata:
name: armada/Manifest/v1
name: armada/Manifest/v2
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#

View File

@ -169,8 +169,7 @@ class TestReleasesManifestControllerNegativeTest(base.BaseControllerTest):
self.assertIn({
'message':
('An error occurred while building chart group: '
'Could not build chart group keystone-infra-services in '
'armada/ChartGroup/v1.'),
'Could not build ChartGroup named "keystone-infra-services".'),
'error':
True,
'kind':

View File

@ -158,125 +158,153 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
armada_obj.pre_flight_ops()
expected_config = {
'armada': {
'release_prefix':
'armada',
'schema': 'armada/Manifest/v1',
'metadata': {
'schema': 'metadata/Document/v1',
'name': 'example-manifest'
},
'data': {
'release_prefix': 'armada',
'chart_groups': [{
'chart_group': [{
'chart': {
'dependencies': [],
'chart_name': 'test_chart_1',
'namespace': 'test',
'release': 'test_chart_1',
'source': {
'location': ('git://github.com/dummy/armada'),
'reference': 'master',
'subpath': 'chart_1',
'type': 'git'
'schema': 'armada/ChartGroup/v1',
'metadata': {
'schema': 'metadata/Document/v1',
'name': 'example-group'
},
'data': {
'chart_group': [{
'schema': 'armada/Chart/v1',
'metadata': {
'schema': 'metadata/Document/v1',
'name': 'example-chart-1'
},
'source_dir': CHART_SOURCES[0],
'values': {},
'wait': {
'timeout': 10,
'native': {
'enabled': False
}
},
'test': {
'enabled': True
}
}
}, {
'chart': {
'dependencies': [],
'chart_name': 'test_chart_2',
'namespace': 'test',
'protected': {
'continue_processing': True
},
'release': 'test_chart_2',
'source': {
'location': '/tmp/dummy/armada',
'subpath': 'chart_2',
'type': 'local'
},
'source_dir': CHART_SOURCES[1],
'values': {},
'wait': {
'timeout': 10
},
'upgrade': {
'no_hooks': False,
'options': {
'force': True,
'recreate_pods': True
}
},
'test': {
'enabled': True,
'options': {
'cleanup': True
'data': {
'dependencies': [],
'chart_name': 'test_chart_1',
'namespace': 'test',
'release': 'test_chart_1',
'source': {
'location':
'git://github.com/dummy/armada',
'reference': 'master',
'subpath': 'chart_1',
'type': 'git'
},
'source_dir': CHART_SOURCES[0],
'values': {},
'wait': {
'timeout': 10,
'native': {
'enabled': False
}
},
'test': {
'enabled': True
}
}
}
}, {
'chart': {
'dependencies': [],
'chart_name': 'test_chart_3',
'namespace': 'test',
'protected': {
'continue_processing': False
}, {
'schema': 'armada/Chart/v1',
'metadata': {
'schema': 'metadata/Document/v1',
'name': 'example-chart-2'
},
'release': 'test_chart_3',
'source': {
'location': '/tmp/dummy/armada',
'subpath': 'chart_3',
'type': 'local'
},
'source_dir': CHART_SOURCES[2],
'values': {},
'wait': {
'timeout': 10
},
'upgrade': {
'no_hooks': False
'data': {
'dependencies': [],
'chart_name': 'test_chart_2',
'namespace': 'test',
'protected': {
'continue_processing': True
},
'release': 'test_chart_2',
'source': {
'location': '/tmp/dummy/armada',
'subpath': 'chart_2',
'type': 'local'
},
'source_dir': CHART_SOURCES[1],
'values': {},
'wait': {
'timeout': 10
},
'upgrade': {
'no_hooks': False,
'options': {
'force': True,
'recreate_pods': True
}
},
'test': {
'enabled': True,
'options': {
'cleanup': True
}
}
}
}
}, {
'chart': {
'dependencies': [],
'chart_name': 'test_chart_4',
'namespace': 'test',
'release': 'test_chart_4',
'source': {
'location': '/tmp/dummy/armada',
'subpath': 'chart_4',
'type': 'local'
}, {
'schema': 'armada/Chart/v1',
'metadata': {
'schema': 'metadata/Document/v1',
'name': 'example-chart-3'
},
'source_dir': CHART_SOURCES[3],
'values': {},
'wait': {
'timeout': 10
'data': {
'dependencies': [],
'chart_name': 'test_chart_3',
'namespace': 'test',
'protected': {
'continue_processing': False
},
'release': 'test_chart_3',
'source': {
'location': '/tmp/dummy/armada',
'subpath': 'chart_3',
'type': 'local'
},
'source_dir': CHART_SOURCES[2],
'values': {},
'wait': {
'timeout': 10
},
'upgrade': {
'no_hooks': False
}
}
}, {
'schema': 'armada/Chart/v1',
'metadata': {
'schema': 'metadata/Document/v1',
'name': 'example-chart-4'
},
'upgrade': {
'no_hooks': False
},
'test': True
}
}],
'description':
'this is a test',
'name':
'example-group',
'sequenced':
True
'data': {
'dependencies': [],
'chart_name': 'test_chart_4',
'namespace': 'test',
'release': 'test_chart_4',
'source': {
'location': '/tmp/dummy/armada',
'subpath': 'chart_4',
'type': 'local'
},
'source_dir': CHART_SOURCES[3],
'values': {},
'wait': {
'timeout': 10
},
'upgrade': {
'no_hooks': False
},
'test': True
}
}],
'description': 'this is a test',
'sequenced': True
}
}]
}
} # yapf: disable
self.assertTrue(hasattr(armada_obj, 'manifest'))
self.assertIsInstance(armada_obj.manifest, dict)
self.assertIn('armada', armada_obj.manifest)
self.assertIn('data', armada_obj.manifest)
self.assertEqual(expected_config, armada_obj.manifest)
@mock.patch.object(armada, 'source')
@ -314,9 +342,11 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
armada_obj.post_flight_ops()
for group in armada_obj.manifest['armada']['chart_groups']:
for counter, chart in enumerate(group.get('chart_group')):
if chart.get('chart').get('source').get('type') == 'git':
for group in armada_obj.manifest['data']['chart_groups']:
for counter, chart in enumerate(
group.get(const.KEYWORD_DATA).get(const.KEYWORD_CHARTS)):
if chart.get(
const.KEYWORD_DATA).get('source').get('type') == 'git':
mock_source.source_cleanup.assert_called_with(
CHART_SOURCES[counter][0])
@ -348,7 +378,8 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
armada_obj = armada.Armada(yaml_documents, m_tiller)
armada_obj.chart_deploy.get_diff = mock.Mock()
chart_group = armada_obj.manifest['armada']['chart_groups'][0]
cg = armada_obj.manifest['data']['chart_groups'][0]
chart_group = cg['data']
charts = chart_group['chart_group']
cg_test_all_charts = chart_group.get('test_charts')
@ -380,9 +411,9 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
expected_test_constructor_calls = []
for c in charts:
chart = c['chart']
chart = c['data']
release = chart['release']
prefix = armada_obj.manifest['armada']['release_prefix']
prefix = armada_obj.manifest['data']['release_prefix']
release_name = release_prefixer(prefix, release)
# Simplified check because the actual code uses logical-or's
# multiple conditions, so this is enough.
@ -394,8 +425,8 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
mock.call(
mock_chartbuilder().get_helm_chart(),
"{}-{}".format(
armada_obj.manifest['armada']
['release_prefix'], chart['release']),
armada_obj.manifest['data']['release_prefix'],
chart['release']),
chart['namespace'],
values=yaml.safe_dump(chart['values']),
wait=native_wait_enabled,
@ -420,7 +451,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
mock.call(
mock_chartbuilder().get_helm_chart(),
"{}-{}".format(
armada_obj.manifest['armada']
armada_obj.manifest['data']
['release_prefix'],
chart['release']),
chart['namespace'],
@ -449,7 +480,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
mock.call(
mock_chartbuilder().get_helm_chart(),
"{}-{}".format(
armada_obj.manifest['armada']
armada_obj.manifest['data']
['release_prefix'],
chart['release']),
chart['namespace'],
@ -647,8 +678,8 @@ class ArmadaNegativeHandlerTestCase(base.ArmadaTestCase):
def test_armada_get_manifest_exception(self, mock_source):
"""Test armada handling with invalid manifest."""
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
error_re = ('Documents must be a list of documents with at least one '
'of each of the following schemas: .*')
error_re = ('.*Documents must include at least one of each of .* and '
'only one .*')
self.assertRaisesRegexp(ManifestException, error_re, armada.Armada,
yaml_documents[:1], mock.MagicMock())

View File

@ -23,6 +23,7 @@ from hapi.chart.metadata_pb2 import Metadata
import mock
import testtools
from armada import const
from armada.handlers.chartbuilder import ChartBuilder
from armada.exceptions import chartbuilder_exceptions
@ -59,7 +60,9 @@ class BaseChartBuilderTestCase(testtools.TestCase):
"""
chart_stream = """
chart:
metadata:
name: test
data:
chart_name: mariadb
release: mariadb
namespace: openstack
@ -87,7 +90,9 @@ class BaseChartBuilderTestCase(testtools.TestCase):
"""
dependency_chart_stream = """
chart:
metadata:
name: dep
data:
chart_name: keystone
release: keystone
namespace: undercloud
@ -120,6 +125,16 @@ class BaseChartBuilderTestCase(testtools.TestCase):
self.addCleanup(shutil.rmtree, subdir)
return subdir
def _get_test_chart(self, chart_dir):
return {
'metadata': {
'name': 'test'
},
const.KEYWORD_DATA: {
'source_dir': (chart_dir.path, '')
}
}
class ChartBuilderTestCase(BaseChartBuilderTestCase):
@ -131,8 +146,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(chart_dir.path, 'Chart.yaml',
self.chart_yaml)
test_chart = {'source_dir': (chart_dir.path, '')}
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
# Validate response type is :class:`hapi.chart.metadata_pb2.Metadata`
resp = chartbuilder.get_metadata()
@ -142,8 +156,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
chart_dir = self.useFixture(fixtures.TempDir())
self.addCleanup(shutil.rmtree, chart_dir.path)
test_chart = {'source_dir': (chart_dir.path, '')}
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
self.assertRaises(chartbuilder_exceptions.MetadataLoadException,
chartbuilder.get_metadata)
@ -168,8 +181,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
for filename in ['template%d' % x for x in range(3)]:
self._write_temporary_file_contents(templates_subdir, filename, "")
test_chart = {'source_dir': (chart_dir.path, '')}
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
expected_files = (
'[type_url: "%s"\n, type_url: "%s"\n]' % ('./bar', './foo'))
@ -185,8 +197,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(
chart_dir.path, filename, "DIRC^@^@^@^B^@^@^@×Z®<86>F.1")
test_chart = {'source_dir': (chart_dir.path, '')}
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
chartbuilder.get_files()
def test_get_basic_helm_chart(self):
@ -197,8 +208,8 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self.addCleanup(shutil.rmtree, chart_dir.path)
self._write_temporary_file_contents(chart_dir.path, 'Chart.yaml',
self.chart_yaml)
ch = yaml.safe_load(self.chart_stream)['chart']
ch['source_dir'] = (chart_dir.path, '')
ch = yaml.safe_load(self.chart_stream)
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
@ -228,8 +239,8 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(chart_dir.path, 'values.yaml',
self.chart_value)
ch = yaml.safe_load(self.chart_stream)['chart']
ch['source_dir'] = (chart_dir.path, '')
ch = yaml.safe_load(self.chart_stream)
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
@ -257,8 +268,8 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
'nested')
self._write_temporary_file_contents(nested_dir, 'nested0', "random")
ch = yaml.safe_load(self.chart_stream)['chart']
ch['source_dir'] = (chart_dir.path, '')
ch = yaml.safe_load(self.chart_stream)
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
@ -313,8 +324,8 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
# Files to **include** within charts/ subdirectory.
self._write_temporary_file_contents(charts_subdir, '.prov', "xyzzy")
ch = yaml.safe_load(self.chart_stream)['chart']
ch['source_dir'] = (chart_dir.path, '')
ch = yaml.safe_load(self.chart_stream)
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
@ -340,8 +351,8 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self.addCleanup(shutil.rmtree, chart_dir.path)
self._write_temporary_file_contents(chart_dir.path, 'Chart.yaml',
self.chart_yaml)
ch = yaml.safe_load(self.chart_stream)['chart']
ch['source_dir'] = (chart_dir.path, '')
ch = yaml.safe_load(self.chart_stream)
ch['data']['source_dir'] = (chart_dir.path, '')
# Dependency chart directory and files.
dep_chart_dir = self.useFixture(fixtures.TempDir())
@ -349,11 +360,11 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(dep_chart_dir.path, 'Chart.yaml',
self.dependency_chart_yaml)
dep_ch = yaml.safe_load(self.dependency_chart_stream)
dep_ch['chart']['source_dir'] = (dep_chart_dir.path, '')
dep_ch['data']['source_dir'] = (dep_chart_dir.path, '')
main_chart = ch
dependency_chart = dep_ch
main_chart['dependencies'] = [dependency_chart]
main_chart['data']['dependencies'] = [dependency_chart]
chartbuilder = ChartBuilder(main_chart)
helm_chart = chartbuilder.get_helm_chart()
@ -409,8 +420,8 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self.addCleanup(shutil.rmtree, chart_dir.path)
self._write_temporary_file_contents(chart_dir.path, 'Chart.yaml',
self.chart_yaml)
ch = yaml.safe_load(self.chart_stream)['chart']
ch['source_dir'] = (chart_dir.path, '')
ch = yaml.safe_load(self.chart_stream)
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
@ -424,10 +435,10 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(dep_chart_dir.path, 'Chart.yaml',
self.dependency_chart_yaml)
dep_ch = yaml.safe_load(self.dependency_chart_stream)
dep_ch['chart']['source_dir'] = (dep_chart_dir.path, '')
dep_ch['data']['source_dir'] = (dep_chart_dir.path, '')
dependency_chart = dep_ch
test_chart['dependencies'] = [dependency_chart]
test_chart['data']['dependencies'] = [dependency_chart]
chartbuilder = ChartBuilder(test_chart)
re = inspect.cleandoc("""
@ -457,8 +468,7 @@ class ChartBuilderNegativeTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(
chart_dir.path, filename, "DIRC^@^@^@^B^@^@^@×Z®<86>F.1")
test_chart = {'source_dir': (chart_dir.path, '')}
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
# Confirm it failed for both encodings.
error_re = (r'.*A str exception occurred while trying to read file:'
@ -477,8 +487,7 @@ class ChartBuilderNegativeTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(
chart_dir.path, filename, "DIRC^@^@^@^B^@^@^@×Z®<86>F.1")
test_chart = {'source_dir': (chart_dir.path, '')}
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
side_effects = [self.exc_to_raise, "", ""]
with mock.patch("builtins.open", mock.mock_open(read_data="")) \

View File

@ -18,9 +18,9 @@ import yaml
import testtools
from armada import const
from armada import exceptions
from armada.handlers import manifest
from armada.handlers import schema
from armada.utils import validate
@ -111,7 +111,7 @@ class ManifestTestCase(testtools.TestCase):
self.documents, target_manifest='armada-manifest')
obtained_manifest = armada_manifest.get_manifest()
self.assertIsInstance(obtained_manifest, dict)
self.assertEqual(obtained_manifest['armada'],
self.assertEqual(obtained_manifest['data'],
armada_manifest.manifest['data'])
def test_find_documents(self):
@ -194,19 +194,15 @@ class ManifestTestCase(testtools.TestCase):
# the first chart group in the Armada manifest
keystone_infra_services_chart_group = armada_manifest. \
find_chart_group_document('keystone-infra-services')
keystone_infra_services_chart_group_data = \
keystone_infra_services_chart_group.get('data')
self.assertEqual(keystone_infra_services_chart_group_data,
self.assertEqual(keystone_infra_services_chart_group,
built_armada_manifest['data']['chart_groups'][0])
# the first chart group in the Armada manifest
openstack_keystone_chart_group = armada_manifest. \
find_chart_group_document('openstack-keystone')
openstack_keystone_chart_group_data = \
openstack_keystone_chart_group.get('data')
self.assertEqual(openstack_keystone_chart_group_data,
self.assertEqual(openstack_keystone_chart_group,
built_armada_manifest['data']['chart_groups'][1])
def test_verify_build_chart_group_deps(self):
@ -218,7 +214,7 @@ class ManifestTestCase(testtools.TestCase):
build_chart_group(chart_group)
openstack_keystone_chart_group_deps_dep_added = \
openstack_keystone_chart_group_deps[
'data']['chart_group'][0]['chart']['dependencies']
'data']['chart_group'][0]['data']['dependencies']
# keystone chart dependencies
keystone_chart = armada_manifest.find_chart_document('keystone')
@ -237,7 +233,7 @@ class ManifestTestCase(testtools.TestCase):
build_chart_group(chart_group)
keystone_infra_services_dep_added = \
openstack_keystone_chart_group_deps[
'data']['chart_group'][0]['chart']['dependencies']
'data']['chart_group'][0]['data']['dependencies']
# building mariadb chart dependencies
mariadb_chart = armada_manifest.find_chart_document('mariadb')
@ -274,9 +270,7 @@ class ManifestTestCase(testtools.TestCase):
# helm-toolkit dependency, the basis for comparison of d
# ependencies in other charts
expected_helm_toolkit_dependency = {
'chart': helm_toolkit_chart.get('data')
}
expected_helm_toolkit_dependency = helm_toolkit_chart
# keystone chart dependencies
keystone_chart = armada_manifest.find_chart_document('keystone')
@ -366,42 +360,39 @@ class ManifestNegativeTestCase(testtools.TestCase):
documents,
target_manifest='armada-manifest')
def _assert_missing_documents_raises(self, documents):
error_re = ('.*Documents must include at least one of each of .* and '
'only one .*')
self.assertRaisesRegexp(exceptions.ManifestException, error_re,
manifest.Manifest, documents)
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])
self._assert_missing_documents_raises(self.documents[:-1])
def test_get_documents_missing_charts(self):
# Validates exceptions.ManifestException is thrown if no chart is
# found. Charts are first 5 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[5:])
self._assert_missing_documents_raises(self.documents[5:])
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)
self._assert_missing_documents_raises(documents)
def test_find_chart_document_negative(self):
armada_manifest = manifest.Manifest(self.documents)
error_re = r'Could not build %s named "%s"' % (const.DOCUMENT_CHART,
'invalid')
error_re = r'.*Could not find %s named "%s"' % (schema.TYPE_CHART,
'invalid')
self.assertRaisesRegexp(exceptions.BuildChartException, 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 build %s named "%s"' % (const.DOCUMENT_GROUP,
'invalid')
error_re = r'.*Could not find %s named "%s"' % (schema.TYPE_CHARTGROUP,
'invalid')
self.assertRaisesRegexp(exceptions.BuildChartGroupException, error_re,
armada_manifest.find_chart_group_document,
'invalid')

View File

@ -20,8 +20,8 @@ import yaml
import testtools
from armada.handlers.override import Override
from armada.handlers import schema
from armada.exceptions import override_exceptions
from armada import const
class OverrideTestCase(testtools.TestCase):
@ -117,13 +117,13 @@ class OverrideTestCase(testtools.TestCase):
documents = list(yaml.safe_load_all(f.read()))
ovr = Override(documents)
test_group = ovr.find_document_type('chart_group')
self.assertEqual(test_group, const.DOCUMENT_GROUP)
self.assertEqual(test_group, schema.TYPE_CHARTGROUP)
test_chart = ovr.find_document_type('chart')
self.assertEqual(test_chart, const.DOCUMENT_CHART)
self.assertEqual(test_chart, schema.TYPE_CHART)
test_manifest = ovr.find_document_type('manifest')
self.assertEqual(test_manifest, const.DOCUMENT_MANIFEST)
self.assertEqual(test_manifest, schema.TYPE_MANIFEST)
def test_update_chart_document_valid(self):
with open(self.base_manifest) as f:
@ -138,7 +138,7 @@ class OverrideTestCase(testtools.TestCase):
ovr = Override(documents)
# update with document values with the modified ones
ovr.update_chart_document(documents_modified[0])
ovr.update_document(documents_modified[0])
# after the update, both documents are equal
self.assertEqual(ovr.documents[0]['data']['chart_name'],
@ -148,7 +148,7 @@ class OverrideTestCase(testtools.TestCase):
# Case 2: Checking if dictionaries get updated
documents_modified[0]['data']['values'] = {'foo': 'bar'}
ovr.update_chart_document(documents_modified[0])
ovr.update_document(documents_modified[0])
# after the update, both documents are equal
self.assertEqual(ovr.documents[0]['data']['values'],
@ -158,7 +158,7 @@ class OverrideTestCase(testtools.TestCase):
# Case 3: Checking if lists get updated
documents_modified[0]['data']['dependencies'] = ['foo', 'bar']
ovr.update_chart_document(documents_modified[0])
ovr.update_document(documents_modified[0])
# after the update, both documents are equal
self.assertEqual(['foo', 'bar'],
@ -179,7 +179,7 @@ class OverrideTestCase(testtools.TestCase):
ovr = Override(documents)
# update with document values with the modified ones
ovr.update_chart_document(documents_modified[0])
ovr.update_document(documents_modified[0])
self.assertIn('chart_name', ovr.documents[0]['data'])
self.assertNotEqual(ovr.documents[0], documents_modified[0])
@ -195,7 +195,7 @@ class OverrideTestCase(testtools.TestCase):
ovr = Override(documents)
# update with document values with the modified ones
ovr.update_chart_group_document(documents_modified[1])
ovr.update_document(documents_modified[1])
# after the update, both documents are equal
self.assertEqual(ovr.documents[1]['data']['sequenced'],
@ -214,7 +214,7 @@ class OverrideTestCase(testtools.TestCase):
ovr = Override(documents)
# update with document values with the modified ones
ovr.update_chart_group_document(documents_modified[1])
ovr.update_document(documents_modified[1])
self.assertIn('sequenced', ovr.documents[1]['data'])
self.assertNotEqual(ovr.documents[1], documents_modified[1])
@ -230,7 +230,7 @@ class OverrideTestCase(testtools.TestCase):
ovr = Override(documents)
# update with document values with the modified ones
ovr.update_armada_manifest(documents_modified[2])
ovr.update_document(documents_modified[2])
# after the update, both documents are equal
self.assertEqual(ovr.documents[2]['data']['release_prefix'],
@ -249,7 +249,7 @@ class OverrideTestCase(testtools.TestCase):
ovr = Override(documents)
# update with document values from base_manifest
ovr.update_armada_manifest(documents_modified[2])
ovr.update_document(documents_modified[2])
self.assertIn('release_prefix', ovr.documents[2]['data'])
self.assertNotEqual(ovr.documents[2], documents_modified[2])
@ -265,7 +265,7 @@ class OverrideTestCase(testtools.TestCase):
documents = list(yaml.safe_load_all(f.read()))
doc_path = ['chart', 'blog-1']
ovr = Override(documents)
ovr.update_document(merging_values)
ovr.update_documents(merging_values)
ovr_doc = ovr.find_manifest_document(doc_path)
expect_doc = list(yaml.load_all(e.read()))[0]

View File

@ -0,0 +1,38 @@
# Copyright 2019 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 unittest
from armada.utils import schema
class SchemaTestCase(unittest.TestCase):
def test_validate_load_schemas(self):
expected_schemas = [
'armada/Chart/v1', 'armada/ChartGroup/v1', 'armada/Manifest/v1'
'armada/Chart/v2', 'armada/ChartGroup/v2', 'armada/Manifest/v2'
]
for expected_schema in expected_schemas:
self.assertIn(expected_schema, schema._SCHEMAS)
def test_validate_load_duplicate_schemas_expect_runtime_error(self):
"""Validate that calling ``_load_schemas`` results in a
``RuntimeError`` being thrown, because the call is made during module
import, and importing the schemas again in manually results in
duplicates.
"""
with self.assertRaisesRegexp(RuntimeError,
'Duplicate schema specified for: .*'):
schema._load_schemas()

View File

@ -111,13 +111,6 @@ class ValidateOwnExamplesTestCase(BaseValidateTest):
class ValidateTestCase(BaseValidateTest):
def test_validate_load_schemas(self):
expected_schemas = [
'armada/Chart/v1', 'armada/ChartGroup/v1', 'armada/Manifest/v1'
]
for expected_schema in expected_schemas:
self.assertIn(expected_schema, validate.SCHEMAS)
def test_validate_armada_yaml_passes(self):
template = '{}/resources/valid_armada_document.yaml'.format(
self.basepath)
@ -223,16 +216,6 @@ data:
class ValidateNegativeTestCase(BaseValidateTest):
def test_validate_load_duplicate_schemas_expect_runtime_error(self):
"""Validate that calling ``validate._load_schemas`` results in a
``RuntimeError`` being thrown, because the call is made during module
import, and importing the schemas again in manually results in
duplicates.
"""
with self.assertRaisesRegexp(RuntimeError,
'Duplicate schema specified for: .*'):
validate._load_schemas()
def test_validate_no_dictionary_expect_type_error(self):
expected_error = 'The provided input "invalid" must be a dictionary.'
self.assertRaisesRegexp(TypeError, expected_error,

View File

@ -13,44 +13,18 @@
# limitations under the License.
import jsonschema
import os
import pkg_resources
import requests
import traceback
import yaml
from oslo_log import log as logging
from armada.const import KEYWORD_GROUPS, KEYWORD_CHARTS, KEYWORD_RELEASE
from armada import const
from armada.handlers import schema as sch
from armada.handlers.manifest import Manifest
from armada.exceptions.manifest_exceptions import ManifestException
from armada.utils.validation_message import ValidationMessage
LOG = logging.getLogger(__name__)
# Creates a mapping between ``metadata.name``: ``data`` where the
# ``metadata.name`` is the ``schema`` of a manifest and the ``data`` is the
# JSON schema to be used to validate the manifest in question.
SCHEMAS = {}
def _get_schema_dir():
return pkg_resources.resource_filename('armada', 'schemas')
def _load_schemas():
"""Populates ``SCHEMAS`` with the schemas defined in package
``armada.schemas``.
"""
schema_dir = _get_schema_dir()
for schema_file in os.listdir(schema_dir):
with open(os.path.join(schema_dir, schema_file)) as f:
for schema in yaml.safe_load_all(f):
name = schema['metadata']['name']
if name in SCHEMAS:
raise RuntimeError(
'Duplicate schema specified for: %s.' % name)
SCHEMAS[name] = schema['data']
def _validate_armada_manifest(manifest):
@ -71,7 +45,7 @@ def _validate_armada_manifest(manifest):
details = []
try:
armada_object = manifest.get_manifest().get('armada')
manifest.get_manifest().get(const.KEYWORD_DATA)
except ManifestException as me:
vmsg = ValidationMessage(
message=str(me), error=True, name='ARM001', level='Error')
@ -80,27 +54,6 @@ def _validate_armada_manifest(manifest):
details.append(vmsg.get_output())
return False, details
groups = armada_object.get(KEYWORD_GROUPS)
if not isinstance(groups, list):
message = '{} entry is of wrong type: {} (expected: {})'.format(
KEYWORD_GROUPS, type(groups), 'list')
vmsg = ValidationMessage(
message=message, error=True, name='ARM101', level='Error')
LOG.info('ValidationMessage: %s', vmsg.get_output_json())
details.append(vmsg.get_output())
for group in groups:
for chart in group.get(KEYWORD_CHARTS):
chart_obj = chart.get('chart')
if KEYWORD_RELEASE not in chart_obj:
message = 'Could not find {} keyword in {}'.format(
KEYWORD_RELEASE, chart_obj.get('release'))
vmsg = ValidationMessage(
message=message, error=True, name='ARM102', level='Error')
LOG.info('ValidationMessage: %s', vmsg.get_output_json())
details.append(vmsg.get_output())
if len([x for x in details if x.get('error', False)]) > 0:
return False, details
@ -117,13 +70,16 @@ def validate_armada_manifests(documents):
all_valid = True
for document in documents:
if document.get('schema', '') == 'armada/Manifest/v1':
target = document.get('metadata').get('name')
# TODO(MarshM) explore: why does this pass 'documents'?
manifest = Manifest(documents, target_manifest=target)
is_valid, details = _validate_armada_manifest(manifest)
all_valid = all_valid and is_valid
messages.extend(details)
doc_schema = document.get('schema')
if doc_schema:
schema_info = sch.get_schema_info(doc_schema)
if schema_info and schema_info.type == sch.TYPE_MANIFEST:
target = document.get('metadata').get('name')
# TODO(MarshM) explore: why does this pass 'documents'?
manifest = Manifest(documents, target_manifest=target)
is_valid, details = _validate_armada_manifest(manifest)
all_valid = all_valid and is_valid
messages.extend(details)
return all_valid, messages
@ -151,9 +107,10 @@ def validate_armada_document(document):
details = []
LOG.debug('Validating document [%s] %s', schema, document_name)
if schema in SCHEMAS:
schema_info = sch.get_schema_info(schema)
if schema_info:
try:
validator = jsonschema.Draft4Validator(SCHEMAS[schema])
validator = jsonschema.Draft4Validator(schema_info.data)
for error in validator.iter_errors(document.get('data')):
error_message = "Invalid document [%s] %s: %s." % \
(schema, document_name, error.message)
@ -222,7 +179,3 @@ def validate_manifest_url(value):
return (requests.get(value).status_code == 200)
except requests.exceptions.RequestException:
return False
# Fill the cache.
_load_schemas()

View File

@ -0,0 +1,26 @@
..
Copyright 2019 AT&T Intellectual Property.
All 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.
Document Authoring Guide
========================
.. toctree::
:maxdepth: 2
:caption: Contents:
v1/index
v2/index
migration-v1-v2

View File

@ -0,0 +1,70 @@
..
Copyright 2019 AT&T Intellectual Property.
All 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.
v1-v2 Migration
===============
The following migrations must be done when moving from :ref:`v1 <document_authoring_v1>` to :ref:`v2 <document_authoring_v2>` docs.
Chart
-----
+--------------------------------+------------------------------------------------------------+
| change | migration |
+================================+============================================================+
| ``chart_name`` removed | Remove. It was redundant with ``metadata.name`` while at |
| | the same time not guaranteeing uniqueness. Log messages now|
| | reference ``metadata.name`` for improved grep-ability. |
+--------------------------------+------------------------------------------------------------+
| ``test`` as a boolean removed | :ref:`test <test_v2>` must now be an object. |
+--------------------------------+------------------------------------------------------------+
| ``timeout`` removed | Use ``wait.timeout`` instead. |
+--------------------------------+------------------------------------------------------------+
| ``install`` removed | Remove. Previously unused. |
+--------------------------------+------------------------------------------------------------+
| ``upgrade.post`` removed | Remove. |
+--------------------------------+------------------------------------------------------------+
| ``upgrade.pre.update`` removed | Remove. |
+--------------------------------+------------------------------------------------------------+
| ``upgrade.pre.create`` removed | Remove. |
+--------------------------------+------------------------------------------------------------+
| ``upgrade.pre.delete[*].name`` | Remove. |
| removed | |
+--------------------------------+------------------------------------------------------------+
| ``upgrade.pre.delete[*]`` | If you have an item in ``upgrade.pre.delete`` and |
| with ``type: job`` no longer | ``type: job`` and you also want to delete cronjobs, add |
| deletes cronjobs | another item with ``type: cronjob`` and same labels. |
+--------------------------------+------------------------------------------------------------+
| ``dependencies``, | Remove as desired. |
| ``upgrade.no_hooks``, | |
| ``source.subpath`` | |
| now optional | |
+--------------------------------+------------------------------------------------------------+
ChartGroup
----------
+--------------------------+-----------------------------------------------------------+
| change | migration |
+==========================+===========================================================+
| ``test_charts`` removed | Use the Chart schema's :ref:`test.enabled <test_v2>` |
| | instead. |
+--------------------------+-----------------------------------------------------------+
Manifest
--------
No changes.

View File

@ -1,5 +1,23 @@
Armada - Making Your First Armada Manifest
==========================================
..
Copyright 2019 AT&T Intellectual Property.
All 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.
.. _document_authoring_v1:
v1 Authoring
============
armada/Manifest/v1
------------------
@ -191,9 +209,6 @@ Run helm tests on the chart after install/upgrade.
deprecated and will be removed. The ``cleanup`` option below is set to true
in this case for backward compatibility.
.. _test_options:
Test Options
^^^^^^^^^^^^

View File

@ -0,0 +1,25 @@
..
Copyright 2019 AT&T Intellectual Property.
All 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.
v1
==
.. toctree::
:maxdepth: 2
:caption: Contents:
document-authoring
schemas

View File

@ -14,15 +14,11 @@
License for the specific language governing permissions and limitations
under the License.
.. _armada-documents:
v1 Schemas
==========
Armada Documents
================
Below are the schemas Armada uses to validate :ref:`Charts`,
:ref:`Chart Groups`, and :ref:`Manifests`.
.. _Charts:
Below are the schemas Armada uses to validate Charts, Chart Groups, and
Manifests.
Charts
------
@ -32,16 +28,12 @@ comparable to a Helm chart. Charts consist of all the labels, dependencies,
install and upgrade information, hooks and additional information needed to
convey to Tiller.
.. _Chart Groups:
Chart Groups
------------
A ``Chart Group`` consists of a list of charts. ``Chart Group`` documents are
useful for managing a group of ``Chart`` documents together.
.. _Manifests:
Manifests
---------
@ -76,7 +68,7 @@ Schemas
``metadata.name`` are validated.
.. literalinclude::
../../../armada/schemas/armada-chart-schema.yaml
../../../../../armada/schemas/armada-chart-schema-v1.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``armada/Chart/v1`` documents.
@ -90,7 +82,7 @@ Schemas
``metadata.name`` are validated.
.. literalinclude::
../../../armada/schemas/armada-chartgroup-schema.yaml
../../../../../armada/schemas/armada-chartgroup-schema-v1.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``armada/ChartGroup/v1`` documents.
@ -104,7 +96,7 @@ Schemas
``metadata.name`` are validated.
.. literalinclude::
../../../armada/schemas/armada-manifest-schema.yaml
../../../../../armada/schemas/armada-manifest-schema-v1.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``armada/Manifest/v1`` documents.
@ -112,8 +104,6 @@ Schemas
This schema is used to sanity-check all ``Manifest`` documents that are passed
to Armada.
.. _authoring-guidelines:
Authoring Guidelines
--------------------

View File

@ -0,0 +1,469 @@
..
Copyright 2019 AT&T Intellectual Property.
All 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.
.. _document_authoring_v2:
v2 Authoring
============
.. DANGER::
EXPERIMENTAL: `v2` docs are still experimental and WILL have breaking changes
before they are finalized.
armada/Manifest/v2
------------------
+---------------------+--------+-------------------------+
| keyword | type | action |
+=====================+========+=========================+
| ``release_prefix`` | string | appends to the |
| | | front of all |
| | | charts |
| | | released |
| | | by the |
| | | manifest in |
| | | order to |
| | | manage releases |
| | | throughout their |
| | | lifecycle |
+---------------------+--------+-------------------------+
| ``chart_groups`` | array | A list of the |
| | | ``metadata.name`` of |
| | | each ``ChartGroup`` to |
| | | deploy in order. |
+---------------------+--------+-------------------------+
Manifest Example
^^^^^^^^^^^^^^^^
::
---
schema: armada/Manifest/v2
metadata:
schema: metadata/Document/v1
name: simple-armada
data:
release_prefix: armada
chart_groups:
- chart_group
armada/ChartGroup/v2
--------------------
+-----------------+----------+------------------------------------------------------------------------+
| keyword | type | action |
+=================+==========+========================================================================+
| description | string | description of chart set |
+-----------------+----------+------------------------------------------------------------------------+
| chart_group | array | A list of the ``metadata.name`` of each ``Chart`` to deploy. |
+-----------------+----------+------------------------------------------------------------------------+
| sequenced | bool | If ``true``, deploys each chart in sequence, else in parallel. |
| | | Default ``false``. |
+-----------------+----------+------------------------------------------------------------------------+
Chart Group Example
^^^^^^^^^^^^^^^^^^^
::
---
schema: armada/ChartGroup/v2
metadata:
schema: metadata/Document/v1
name: blog-group
data:
description: Deploys Simple Service
chart_group:
- chart1
- chart2
armada/Chart/v2
---------------
Chart
^^^^^
+-----------------+----------+---------------------------------------------------------------------------------------+
| keyword | type | action |
+=================+==========+=======================================================================================+
| release | string | name of the release (Armada will prepend with ``release-prefix`` during processing) |
+-----------------+----------+---------------------------------------------------------------------------------------+
| namespace | string | namespace of your chart |
+-----------------+----------+---------------------------------------------------------------------------------------+
| wait | object | See `Wait`_. |
+-----------------+----------+---------------------------------------------------------------------------------------+
| protected | object | do not delete FAILED releases when encountered from previous run (provide the |
| | | 'continue_processing' bool to continue or halt execution (default: halt)) |
+-----------------+----------+---------------------------------------------------------------------------------------+
| test | object | See Test_. |
+-----------------+----------+---------------------------------------------------------------------------------------+
| upgrade | object | upgrade the chart managed by the armada yaml |
+-----------------+----------+---------------------------------------------------------------------------------------+
| delete | object | See Delete_. |
+-----------------+----------+---------------------------------------------------------------------------------------+
| values | object | (optional) override any default values in the charts |
+-----------------+----------+---------------------------------------------------------------------------------------+
| source | object | provide a path to a ``git repo``, ``local dir``, or ``tarball url`` chart |
+-----------------+----------+---------------------------------------------------------------------------------------+
| dependencies | object | (optional) reference any chart dependencies before install |
+-----------------+----------+---------------------------------------------------------------------------------------+
Wait
^^^^
+-------------+----------+--------------------------------------------------------------------+
| keyword | type | action |
+=============+==========+====================================================================+
| timeout | int | time (in seconds) to wait for chart to deploy |
+-------------+----------+--------------------------------------------------------------------+
| resources | array | Array of `Wait Resource`_ to wait on, with ``labels`` added to each|
| | | item. Defaults to pods and jobs (if any exist) matching ``labels``.|
+-------------+----------+--------------------------------------------------------------------+
| labels | object | Base mapping of labels to wait on. They are added to any labels in |
| | | each item in the ``resources`` array. |
+-------------+----------+--------------------------------------------------------------------+
| native | boolean | See `Wait Native`_. |
+-------------+----------+--------------------------------------------------------------------+
Wait Resource
^^^^^^^^^^^^^
+-------------+----------+--------------------------------------------------------------------+
| keyword | type | action |
+=============+==========+====================================================================+
| type | string | k8s resource type, supports: controllers ('deployment', |
| | | 'daemonset', 'statefulset'), 'pod', 'job' |
+-------------+----------+--------------------------------------------------------------------+
| labels | object | mapping of kubernetes resource labels |
+-------------+----------+--------------------------------------------------------------------+
| min\_ready | int | Only for controller ``type``s. Amount of pods in a controller |
| | string | which must be ready. Can be integer or percent string e.g. ``80%``.|
| | | Default ``100%``. |
+-------------+----------+--------------------------------------------------------------------+
Wait Native
^^^^^^^^^^^
Config for the native ``helm (install|upgrade) --wait`` flag.
+-------------+----------+--------------------------------------------------------------------+
| keyword | type | action |
+=============+==========+====================================================================+
| enabled | boolean | defaults to true |
+-------------+----------+--------------------------------------------------------------------+
.. _test_v2:
Test
^^^^
Run helm tests on the chart after install/upgrade.
+-------------+----------+--------------------------------------------------------------------+
| keyword | type | action |
+=============+==========+====================================================================+
| enabled | bool | whether to enable/disable helm tests for this chart (default True) |
+-------------+----------+--------------------------------------------------------------------+
| timeout | int | time (in sec) to wait for completion of Helm tests |
+-------------+----------+--------------------------------------------------------------------+
| options | object | See `Test Options`_. |
+-------------+----------+--------------------------------------------------------------------+
.. note::
Armada will attempt to run helm tests by default. They may be disabled by
setting the ``enabled`` key to ``False``.
Test Options
^^^^^^^^^^^^
Test options to pass through directly to helm.
+-------------+----------+---------------------------------------------------------------+
| keyword | type | action |
+=============+==========+===============================================================+
| cleanup | bool | cleanup test pods after test completion, defaults to false |
+-------------+----------+---------------------------------------------------------------+
.. note::
If cleanup is ``true`` this prevents being able to debug a test in the event of failure.
Historically, the preferred way to achieve test cleanup has been to add a pre-upgrade delete
action on the test pod.
This still works, however it is usually no longer necessary as Armada now automatically
cleans up any test pods which match the ``wait.labels`` of the chart, immediately before
running tests. Similar suggestions have been made for how ``helm test --cleanup`` itself
ought to work (https://github.com/helm/helm/issues/3279).
Upgrade - Pre
^^^^^^^^^^^^^
+-------------+----------+---------------------------------------------------------------+
| keyword | type | action |
+=============+==========+===============================================================+
| pre | object | actions performed prior to updating a release |
+-------------+----------+---------------------------------------------------------------+
Upgrade - Actions
^^^^^^^^^^^^^^^^^
+-------------+----------+---------------------------------------------------------------+
| keyword | type | action |
+=============+==========+===============================================================+
| delete | array | List of `Upgrade - Actions - Delete`_. |
+-------------+----------+---------------------------------------------------------------+
Upgrade - Actions - Delete
^^^^^^^^^^^^^^^^^^^^^^^^^^
+-------------+----------+---------------------------------------------------------------+
| keyword | type | action |
+=============+==========+===============================================================+
| type | string | type of kubernetes resource to delete |
| | | supported types are: 'pod', 'job', 'cronjob'. |
+-------------+----------+---------------------------------------------------------------+
| labels | object | k:v mapping of labels to select Kubernetes resources |
+-------------+----------+---------------------------------------------------------------+
Chart Example
^^^^^^^^^^^^^
::
---
schema: armada/Chart/v2
metadata:
schema: metadata/Document/v1
name: blog-1
data:
release: blog-1
namespace: default
wait:
timeout: 100
protected:
continue_processing: false
test:
enabled: true
upgrade:
pre:
delete:
- name: test-job
type: job
labels:
foo: bar
component: bar
rak1: enabled
source:
type: git
location: https://github.com/namespace/repo
reference: master
Delete
^^^^^^
+-------------+----------+-----------------------------------------------------------------------------------+
| keyword | type | action |
+=============+==========+===================================================================================+
| timeout | integer | time (in seconds) to wait for chart to be deleted |
+-------------+----------+-----------------------------------------------------------------------------------+
Source
^^^^^^
+-------------+----------+-----------------------------------------------------------------------------------+
| keyword | type | action |
+=============+==========+===================================================================================+
| type | string | source to build the chart: ``git``, ``local``, or ``tar`` |
+-------------+----------+-----------------------------------------------------------------------------------+
| location | string | ``url`` or ``path`` to the chart's parent directory |
+-------------+----------+-----------------------------------------------------------------------------------+
| subpath | string | (optional) relative path to target chart from parent (``.`` if not specified) |
+-------------+----------+-----------------------------------------------------------------------------------+
| reference | string | (optional) branch, commit, or reference in the repo (``master`` if not specified) |
+-------------+----------+-----------------------------------------------------------------------------------+
Source Example
^^^^^^^^^^^^^^
::
# type git
---
schema: armada/Chart/v2
metadata:
schema: metadata/Document/v1
name: blog-1
data:
release: blog-1
namespace: default
wait:
timeout: 100
labels:
component: blog
source:
type: git
location: https://github.com/namespace/repo
# type local
---
schema: armada/Chart/v2
metadata:
schema: metadata/Document/v1
name: blog-1
data:
release: blog-1
namespace: default
wait:
timeout: 100
source:
type: local
location: /path/to/charts
subpath: chart
reference: master
# type tar
---
schema: armada/Chart/v2
metadata:
schema: metadata/Document/v1
name: blog-1
data:
release: blog-1
namespace: default
wait:
timeout: 100
source:
type: tar
location: https://localhost:8879/charts/chart-0.1.0.tgz
subpath: mariadb
Simple Example
^^^^^^^^^^^^^^
::
---
schema: armada/Chart/v2
metadata:
schema: metadata/Document/v1
name: blog-1
data:
release: blog-1
namespace: default
source:
type: git
location: https://github.com/namespace/repo
subpath: blog-1
reference: new-feat
---
schema: armada/ChartGroup/v2
metadata:
schema: metadata/Document/v1
name: blog-group
data:
description: Deploys Simple Service
chart_group:
- blog-1
---
schema: armada/Manifest/v2
metadata:
schema: metadata/Document/v1
name: simple-armada
data:
release_prefix: armada
chart_groups:
- blog-group
Multichart Example
^^^^^^^^^^^^^^^^^^
::
---
schema: armada/Chart/v2
metadata:
schema: metadata/Document/v1
name: blog-1
data:
release: blog-1
namespace: default
source:
type: git
location: https://github.com/namespace/repo
subpath: blog1
reference: master
---
schema: armada/Chart/v2
metadata:
schema: metadata/Document/v1
name: blog-2
data:
release: blog-2
namespace: default
source:
type: tar
location: https://github.com/namespace/repo/blog2.tgz
subpath: blog2
---
schema: armada/Chart/v2
metadata:
schema: metadata/Document/v1
name: blog-3
data:
release: blog-3
namespace: default
source:
type: local
location: /home/user/namespace/repo/blog3
---
schema: armada/ChartGroup/v2
metadata:
schema: metadata/Document/v1
name: blog-group-1
data:
description: Deploys Simple Service
chart_group:
- blog-2
---
schema: armada/ChartGroup/v2
metadata:
schema: metadata/Document/v1
name: blog-group-2
data:
description: Deploys Simple Service
chart_group:
- blog-1
- blog-3
---
schema: armada/Manifest/v2
metadata:
schema: metadata/Document/v1
name: simple-armada
data:
release_prefix: armada
chart_groups:
- blog-group-1
- blog-group-2
References
~~~~~~~~~~
For working examples please check the examples in our repo
`here <https://github.com/openstack/airship-armada/tree/master/examples>`__

View File

@ -0,0 +1,25 @@
..
Copyright 2019 AT&T Intellectual Property.
All 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.
v2 (EXPERIMENTAL!)
==================
.. toctree::
:maxdepth: 2
:caption: Contents:
document-authoring
schemas

View File

@ -0,0 +1,114 @@
..
Copyright 2018 AT&T Intellectual Property.
All 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.
v2 Schemas
==========
Below are the schemas Armada uses to validate Charts, Chart Groups, and
Manifests.
Charts
------
Charts consist of the smallest building blocks in Armada. A ``Chart`` is
comparable to a Helm chart. Charts consist of all the labels, dependencies,
install and upgrade information, hooks and additional information needed to
convey to Tiller.
Chart Groups
------------
A ``Chart Group`` consists of a list of charts. ``Chart Group`` documents are
useful for managing a group of ``Chart`` documents together.
Manifests
---------
A ``Manifest`` is the largest building block in Armada. ``Manifest`` documents
are responsible for managing collections of ``Chart Group`` documents.
Validation Schemas
------------------
Introduction
^^^^^^^^^^^^
All schemas below are `Deckhand DataSchema`_ documents, which are essentially
JSON schemas, with additional metadata useful for Deckhand to perform
`layering`_ and `substitution`_.
The validation schemas below are used by Armada to validate all ingested
Charts, Chart Groups, and Manifests. Use the schemas below as models for
authoring Armada documents.
.. _Deckhand DataSchema: https://airship-deckhand.readthedocs.io/en/latest/document-types.html?highlight=dataschema#dataschema
.. _Helm charts: https://docs.helm.sh/developing_charts/
.. _layering: https://airship-deckhand.readthedocs.io/en/latest/layering.html
.. _substitution: https://airship-deckhand.readthedocs.io/en/latest/substitution.html
Schemas
^^^^^^^
* ``Chart`` schema.
JSON schema against which all documents with ``armada/Chart/v2``
``metadata.name`` are validated.
.. literalinclude::
../../../../../armada/schemas/armada-chart-schema-v2.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``armada/Chart/v2`` documents.
This schema is used to sanity-check all ``Chart`` documents that are passed
to Armada.
* ``Chart Group`` schema.
JSON schema against which all documents with ``armada/Chart/v2``
``metadata.name`` are validated.
.. literalinclude::
../../../../../armada/schemas/armada-chartgroup-schema-v2.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``armada/ChartGroup/v2`` documents.
This schema is used to sanity-check all ``Chart Group`` documents that are
passed to Armada.
* ``Manifest`` schema.
JSON schema against which all documents with ``armada/Manifest/v2``
``metadata.name`` are validated.
.. literalinclude::
../../../../../armada/schemas/armada-manifest-schema-v2.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``armada/Manifest/v2`` documents.
This schema is used to sanity-check all ``Manifest`` documents that are passed
to Armada.
Authoring Guidelines
--------------------
All Armada documents must use the ``deckhand/DataSchema/v1`` schema.
.. todo::
Expand on this section.

View File

@ -10,7 +10,9 @@ Kubernetes Cluster
`Tiller Service <https://github.com/kubernetes/helm>`_
`Armada.yaml <https://airship-armada.readthedocs.io/en/latest/operations/guide-build-armada-yaml.html>`_
.. todo:: point this to v2 docs once they're stable
:ref:`Armada documents <document_authoring_v1>`
.. note::

View File

@ -10,11 +10,10 @@ Operations Guide
:maxdepth: 2
:caption: Contents:
guide-build-armada-yaml
documents/index
guide-configure
guide-troubleshooting
guide-use-armada
documents
exceptions/index
guide-helm-plugin
sampleconf