Improved document validation
BREAKING CHANGE: Armada will no longer support recursive monolithic documents such that a Manifest fully defines ChartGroups inline and ChartGroups fully define Charts inline. Only name-based references to other documents is supported. - Author document schemas in standalone JSON schema files - Update validation to return all failures available - Removed unit tests for support of recursive monolithic documents Change-Id: Idb91fa552d3d7a3d7d525609d505fe7380443238
This commit is contained in:
parent
ebc71ff8ec
commit
3b879fc846
@ -18,7 +18,7 @@ import yaml
|
|||||||
|
|
||||||
from armada import api
|
from armada import api
|
||||||
from armada.common import policy
|
from armada.common import policy
|
||||||
from armada.utils.lint import validate_armada_documents
|
from armada.utils.validate import validate_armada_documents
|
||||||
from armada.handlers.document import ReferenceResolver
|
from armada.handlers.document import ReferenceResolver
|
||||||
|
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ class Validate(api.BaseResource):
|
|||||||
self.logger.debug("Validating set of %d documents."
|
self.logger.debug("Validating set of %d documents."
|
||||||
% len(documents))
|
% len(documents))
|
||||||
|
|
||||||
result = validate_armada_documents(documents)
|
result, details = validate_armada_documents(documents)
|
||||||
|
|
||||||
resp.content_type = 'application/json'
|
resp.content_type = 'application/json'
|
||||||
resp_body = {
|
resp_body = {
|
||||||
@ -58,12 +58,14 @@ class Validate(api.BaseResource):
|
|||||||
'apiVersion': 'v1.0',
|
'apiVersion': 'v1.0',
|
||||||
'metadata': {},
|
'metadata': {},
|
||||||
'reason': 'Validation',
|
'reason': 'Validation',
|
||||||
'details': {
|
'details': {},
|
||||||
'errorCount': 0,
|
|
||||||
'messageList': []
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error_details = [m for m in details if m.get('error', False)]
|
||||||
|
|
||||||
|
resp_body['details']['errorCount'] = len(error_details)
|
||||||
|
resp_body['details']['messageList'] = details
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
resp.status = falcon.HTTP_200
|
resp.status = falcon.HTTP_200
|
||||||
resp_body['status'] = 'Success'
|
resp_body['status'] = 'Success'
|
||||||
@ -74,9 +76,6 @@ class Validate(api.BaseResource):
|
|||||||
resp_body['status'] = 'Failure'
|
resp_body['status'] = 'Failure'
|
||||||
resp_body['message'] = 'Armada validations failed'
|
resp_body['message'] = 'Armada validations failed'
|
||||||
resp_body['code'] = 400
|
resp_body['code'] = 400
|
||||||
resp_body['details']['errorCount'] = 1
|
|
||||||
resp_body['details']['messageList'].\
|
|
||||||
append(dict(message='Validation failed.', error=True))
|
|
||||||
|
|
||||||
resp.body = json.dumps(resp_body)
|
resp.body = json.dumps(resp_body)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
@ -16,9 +16,7 @@ import click
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from armada.cli import CliAction
|
from armada.cli import CliAction
|
||||||
from armada.utils.lint import validate_armada_documents
|
from armada.utils.validate import validate_armada_documents
|
||||||
from armada.utils.lint import validate_armada_object
|
|
||||||
from armada.handlers.manifest import Manifest
|
|
||||||
from armada.handlers.document import ReferenceResolver
|
from armada.handlers.document import ReferenceResolver
|
||||||
|
|
||||||
|
|
||||||
@ -64,16 +62,20 @@ class ValidateManifest(CliAction):
|
|||||||
for d in doc_data:
|
for d in doc_data:
|
||||||
documents.extend(list(yaml.safe_load_all(d.decode())))
|
documents.extend(list(yaml.safe_load_all(d.decode())))
|
||||||
|
|
||||||
manifest_obj = Manifest(documents).get_manifest()
|
|
||||||
obj_check = validate_armada_object(manifest_obj)
|
|
||||||
doc_check = validate_armada_documents(documents)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if doc_check and obj_check:
|
valid, details = validate_armada_documents(documents)
|
||||||
|
|
||||||
|
if valid:
|
||||||
self.logger.info('Successfully validated: %s',
|
self.logger.info('Successfully validated: %s',
|
||||||
self.locations)
|
self.locations)
|
||||||
|
else:
|
||||||
|
self.logger.info('Validation failed: %s', self.locations)
|
||||||
|
|
||||||
|
for m in details:
|
||||||
|
self.logger.info('Validation details: %s', str(m))
|
||||||
except Exception:
|
except Exception:
|
||||||
raise Exception('Failed to validate: %s', self.locations)
|
raise Exception('Exception raised during '
|
||||||
|
'validation: %s', self.locations)
|
||||||
else:
|
else:
|
||||||
if len(self.locations) > 1:
|
if len(self.locations) > 1:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
@ -88,4 +90,7 @@ class ValidateManifest(CliAction):
|
|||||||
if resp.get('code') == 200:
|
if resp.get('code') == 200:
|
||||||
self.logger.info('Successfully validated: %s', self.locations)
|
self.logger.info('Successfully validated: %s', self.locations)
|
||||||
else:
|
else:
|
||||||
self.logger.error("Failed to validate: %s", self.locations)
|
self.logger.error("Validation failed: %s", self.locations)
|
||||||
|
|
||||||
|
for m in resp.get('details', {}).get('messageList', []):
|
||||||
|
self.logger.info("Validation details: %s", str(m))
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
from armada.exceptions import base_exception
|
from armada.exceptions import base_exception
|
||||||
|
|
||||||
|
|
||||||
class LintException(base_exception.ArmadaBaseException):
|
class ValidateException(base_exception.ArmadaBaseException):
|
||||||
'''Base class for linting exceptions and errors.'''
|
'''Base class for linting exceptions and errors.'''
|
||||||
|
|
||||||
message = 'An unknown linting error occurred.'
|
message = 'An unknown linting error occurred.'
|
||||||
|
|
||||||
|
|
||||||
class InvalidManifestException(LintException):
|
class InvalidManifestException(ValidateException):
|
||||||
'''
|
'''
|
||||||
Exception for invalid manifests.
|
Exception for invalid manifests.
|
||||||
|
|
||||||
@ -29,28 +29,29 @@ class InvalidManifestException(LintException):
|
|||||||
*Coming Soon*
|
*Coming Soon*
|
||||||
'''
|
'''
|
||||||
|
|
||||||
message = 'Armada manifest invalid.'
|
message = ('Armada manifest(s) failed validation. Details: '
|
||||||
|
'%(error_messages)s.')
|
||||||
|
|
||||||
|
|
||||||
class InvalidChartNameException(LintException):
|
class InvalidChartNameException(ValidateException):
|
||||||
'''Exception that occurs when an invalid filename is encountered.'''
|
'''Exception that occurs when an invalid filename is encountered.'''
|
||||||
|
|
||||||
message = 'Chart name must be a string.'
|
message = 'Chart name must be a string.'
|
||||||
|
|
||||||
|
|
||||||
class InvalidChartDefinitionException(LintException):
|
class InvalidChartDefinitionException(ValidateException):
|
||||||
'''Exception when invalid chart definition is encountered.'''
|
'''Exception when invalid chart definition is encountered.'''
|
||||||
|
|
||||||
message = 'Invalid chart definition. Chart definition must be array.'
|
message = 'Invalid chart definition. Chart definition must be array.'
|
||||||
|
|
||||||
|
|
||||||
class InvalidReleaseException(LintException):
|
class InvalidReleaseException(ValidateException):
|
||||||
'''Exception that occurs when a release is invalid.'''
|
'''Exception that occurs when a release is invalid.'''
|
||||||
|
|
||||||
message = 'Release needs to be a string.'
|
message = 'Release needs to be a string.'
|
||||||
|
|
||||||
|
|
||||||
class InvalidArmadaObjectException(LintException):
|
class InvalidArmadaObjectException(ValidateException):
|
||||||
'''
|
'''
|
||||||
Exception that occurs when an Armada object is not declared.
|
Exception that occurs when an Armada object is not declared.
|
||||||
|
|
||||||
@ -58,4 +59,5 @@ class InvalidArmadaObjectException(LintException):
|
|||||||
*Coming Soon*
|
*Coming Soon*
|
||||||
'''
|
'''
|
||||||
|
|
||||||
message = 'An Armada object was not declared.'
|
message = ('An Armada object failed internal validation. Details: '
|
||||||
|
'%(details)s.')
|
@ -25,11 +25,11 @@ from armada.handlers.override import Override
|
|||||||
from armada.handlers.tiller import Tiller
|
from armada.handlers.tiller import Tiller
|
||||||
from armada.exceptions import armada_exceptions
|
from armada.exceptions import armada_exceptions
|
||||||
from armada.exceptions import source_exceptions
|
from armada.exceptions import source_exceptions
|
||||||
from armada.exceptions import lint_exceptions
|
from armada.exceptions import validate_exceptions
|
||||||
from armada.exceptions import tiller_exceptions
|
from armada.exceptions import tiller_exceptions
|
||||||
from armada.utils.release import release_prefix
|
from armada.utils.release import release_prefix
|
||||||
from armada.utils import source
|
from armada.utils import source
|
||||||
from armada.utils import lint
|
from armada.utils import validate
|
||||||
from armada import const
|
from armada import const
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -44,7 +44,7 @@ class Armada(object):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
file,
|
documents,
|
||||||
disable_update_pre=False,
|
disable_update_pre=False,
|
||||||
disable_update_post=False,
|
disable_update_post=False,
|
||||||
enable_chart_cleanup=False,
|
enable_chart_cleanup=False,
|
||||||
@ -60,7 +60,7 @@ class Armada(object):
|
|||||||
'''
|
'''
|
||||||
Initialize the Armada engine and establish a connection to Tiller.
|
Initialize the Armada engine and establish a connection to Tiller.
|
||||||
|
|
||||||
:param List[dict] file: Armada documents.
|
:param List[dict] documents: Armada documents.
|
||||||
:param bool disable_update_pre: Disable pre-update Tiller operations.
|
:param bool disable_update_pre: Disable pre-update Tiller operations.
|
||||||
:param bool disable_update_post: Disable post-update Tiller
|
:param bool disable_update_post: Disable post-update Tiller
|
||||||
operations.
|
operations.
|
||||||
@ -90,9 +90,9 @@ class Armada(object):
|
|||||||
tiller_host=tiller_host, tiller_port=tiller_port,
|
tiller_host=tiller_host, tiller_port=tiller_port,
|
||||||
tiller_namespace=tiller_namespace)
|
tiller_namespace=tiller_namespace)
|
||||||
self.values = values
|
self.values = values
|
||||||
self.documents = file
|
self.documents = documents
|
||||||
self.target_manifest = target_manifest
|
self.target_manifest = target_manifest
|
||||||
self.config = self.get_armada_manifest()
|
self.manifest = self.get_armada_manifest()
|
||||||
|
|
||||||
def get_armada_manifest(self):
|
def get_armada_manifest(self):
|
||||||
return Manifest(
|
return Manifest(
|
||||||
@ -109,16 +109,26 @@ class Armada(object):
|
|||||||
return chart, values
|
return chart, values
|
||||||
|
|
||||||
def pre_flight_ops(self):
|
def pre_flight_ops(self):
|
||||||
'''
|
"""Perform a series of checks and operations to ensure proper
|
||||||
Perform a series of checks and operations to ensure proper deployment
|
deployment.
|
||||||
'''
|
"""
|
||||||
|
LOG.info("Performing pre-flight operations.")
|
||||||
|
|
||||||
# Ensure tiller is available and manifest is valid
|
# Ensure Tiller is available and manifest is valid
|
||||||
if not self.tiller.tiller_status():
|
if not self.tiller.tiller_status():
|
||||||
raise tiller_exceptions.TillerServicesUnavailableException()
|
raise tiller_exceptions.TillerServicesUnavailableException()
|
||||||
|
|
||||||
if not lint.validate_armada_documents(self.documents):
|
valid, details = validate.validate_armada_documents(self.documents)
|
||||||
raise lint_exceptions.InvalidManifestException()
|
|
||||||
|
if details:
|
||||||
|
for msg in details:
|
||||||
|
if msg.get('error', False):
|
||||||
|
LOG.error(msg.get('message', 'Unknown validation error.'))
|
||||||
|
else:
|
||||||
|
LOG.debug(msg.get('message', 'Validation succeeded.'))
|
||||||
|
if not valid:
|
||||||
|
raise validate_exceptions.InvalidManifestException(
|
||||||
|
error_messages=details)
|
||||||
|
|
||||||
# Override manifest values if --set flag is used
|
# Override manifest values if --set flag is used
|
||||||
if self.overrides or self.values:
|
if self.overrides or self.values:
|
||||||
@ -126,20 +136,21 @@ class Armada(object):
|
|||||||
self.documents, overrides=self.overrides,
|
self.documents, overrides=self.overrides,
|
||||||
values=self.values).update_manifests()
|
values=self.values).update_manifests()
|
||||||
|
|
||||||
if not lint.validate_armada_object(self.config):
|
result, msg_list = validate.validate_armada_manifests(self.documents)
|
||||||
raise lint_exceptions.InvalidArmadaObjectException()
|
if not result:
|
||||||
|
raise validate_exceptions.InvalidArmadaObjectException(
|
||||||
|
details=','.join([m.get('message') for m in msg_list]))
|
||||||
|
|
||||||
# Purge known releases that have failed and are in the current yaml
|
# Purge known releases that have failed and are in the current yaml
|
||||||
prefix = self.config.get(const.KEYWORD_ARMADA).get(
|
prefix = self.manifest.get(const.KEYWORD_ARMADA).get(
|
||||||
const.KEYWORD_PREFIX)
|
const.KEYWORD_PREFIX)
|
||||||
failed_releases = self.get_releases_by_status(const.STATUS_FAILED)
|
failed_releases = self.get_releases_by_status(const.STATUS_FAILED)
|
||||||
for release in failed_releases:
|
for release in failed_releases:
|
||||||
for group in self.config.get(const.KEYWORD_ARMADA).get(
|
for group in self.manifest.get(const.KEYWORD_ARMADA).get(
|
||||||
const.KEYWORD_GROUPS):
|
const.KEYWORD_GROUPS):
|
||||||
for ch in group.get(const.KEYWORD_CHARTS):
|
for ch in group.get(const.KEYWORD_CHARTS):
|
||||||
ch_release_name = release_prefix(prefix,
|
ch_release_name = release_prefix(
|
||||||
ch.get('chart')
|
prefix, ch.get('chart').get('chart_name'))
|
||||||
.get('chart_name'))
|
|
||||||
if release[0] == ch_release_name:
|
if release[0] == ch_release_name:
|
||||||
LOG.info('Purging failed release %s '
|
LOG.info('Purging failed release %s '
|
||||||
'before deployment', release[0])
|
'before deployment', release[0])
|
||||||
@ -150,7 +161,7 @@ class Armada(object):
|
|||||||
# We only support a git source type right now, which can also
|
# We only support a git source type right now, which can also
|
||||||
# handle git:// local paths as well
|
# handle git:// local paths as well
|
||||||
repos = {}
|
repos = {}
|
||||||
for group in self.config.get(const.KEYWORD_ARMADA).get(
|
for group in self.manifest.get(const.KEYWORD_ARMADA).get(
|
||||||
const.KEYWORD_GROUPS):
|
const.KEYWORD_GROUPS):
|
||||||
for ch in group.get(const.KEYWORD_CHARTS):
|
for ch in group.get(const.KEYWORD_CHARTS):
|
||||||
self.tag_cloned_repo(ch, repos)
|
self.tag_cloned_repo(ch, repos)
|
||||||
@ -229,12 +240,11 @@ class Armada(object):
|
|||||||
|
|
||||||
# TODO: (gardlt) we need to break up this func into
|
# TODO: (gardlt) we need to break up this func into
|
||||||
# a more cleaner format
|
# a more cleaner format
|
||||||
LOG.info("Performing Pre-Flight Operations")
|
|
||||||
self.pre_flight_ops()
|
self.pre_flight_ops()
|
||||||
|
|
||||||
# extract known charts on tiller right now
|
# extract known charts on tiller right now
|
||||||
known_releases = self.tiller.list_charts()
|
known_releases = self.tiller.list_charts()
|
||||||
prefix = self.config.get(const.KEYWORD_ARMADA).get(
|
prefix = self.manifest.get(const.KEYWORD_ARMADA).get(
|
||||||
const.KEYWORD_PREFIX)
|
const.KEYWORD_PREFIX)
|
||||||
|
|
||||||
if known_releases is None:
|
if known_releases is None:
|
||||||
@ -244,7 +254,7 @@ class Armada(object):
|
|||||||
LOG.debug("Release %s, Version %s found on Tiller", release[0],
|
LOG.debug("Release %s, Version %s found on Tiller", release[0],
|
||||||
release[1])
|
release[1])
|
||||||
|
|
||||||
for entry in self.config[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS]:
|
for entry in self.manifest[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS]:
|
||||||
|
|
||||||
chart_wait = self.wait
|
chart_wait = self.wait
|
||||||
desc = entry.get('description', 'A Chart Group')
|
desc = entry.get('description', 'A Chart Group')
|
||||||
@ -394,7 +404,7 @@ class Armada(object):
|
|||||||
if self.enable_chart_cleanup:
|
if self.enable_chart_cleanup:
|
||||||
self.tiller.chart_cleanup(
|
self.tiller.chart_cleanup(
|
||||||
prefix,
|
prefix,
|
||||||
self.config[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS])
|
self.manifest[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS])
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@ -403,7 +413,7 @@ class Armada(object):
|
|||||||
Operations to run after deployment process has terminated
|
Operations to run after deployment process has terminated
|
||||||
'''
|
'''
|
||||||
# Delete temp dirs used for deployment
|
# Delete temp dirs used for deployment
|
||||||
for group in self.config.get(const.KEYWORD_ARMADA).get(
|
for group in self.manifest.get(const.KEYWORD_ARMADA).get(
|
||||||
const.KEYWORD_GROUPS):
|
const.KEYWORD_GROUPS):
|
||||||
for ch in group.get(const.KEYWORD_CHARTS):
|
for ch in group.get(const.KEYWORD_CHARTS):
|
||||||
if ch.get('chart').get('source').get('type') == 'git':
|
if ch.get('chart').get('source').get('type') == 'git':
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# 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 copy import deepcopy
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
@ -47,8 +48,7 @@ class Manifest(object):
|
|||||||
are not found or if the document types are missing required
|
are not found or if the document types are missing required
|
||||||
properties.
|
properties.
|
||||||
"""
|
"""
|
||||||
self.config = None
|
self.documents = deepcopy(documents)
|
||||||
self.documents = documents
|
|
||||||
self.charts, self.groups, manifests = self._find_documents(
|
self.charts, self.groups, manifests = self._find_documents(
|
||||||
target_manifest)
|
target_manifest)
|
||||||
|
|
||||||
@ -66,9 +66,8 @@ class Manifest(object):
|
|||||||
error = ('Documents must be a list of documents with at least one '
|
error = ('Documents must be a list of documents with at least one '
|
||||||
'of each of the following schemas: %s and only one '
|
'of each of the following schemas: %s and only one '
|
||||||
'manifest' % expected_schemas)
|
'manifest' % expected_schemas)
|
||||||
LOG.error(error, expected_schemas)
|
LOG.error(error)
|
||||||
raise exceptions.ManifestException(
|
raise exceptions.ManifestException(details=error)
|
||||||
details=error % expected_schemas)
|
|
||||||
|
|
||||||
def _find_documents(self, target_manifest=None):
|
def _find_documents(self, target_manifest=None):
|
||||||
"""Returns the chart documents, chart group documents,
|
"""Returns the chart documents, chart group documents,
|
||||||
|
@ -18,7 +18,7 @@ import yaml
|
|||||||
|
|
||||||
from armada import const
|
from armada import const
|
||||||
from armada.exceptions import override_exceptions
|
from armada.exceptions import override_exceptions
|
||||||
from armada.utils import lint
|
from armada.utils import validate
|
||||||
|
|
||||||
|
|
||||||
class Override(object):
|
class Override(object):
|
||||||
@ -152,7 +152,7 @@ class Override(object):
|
|||||||
self.override_manifest_value(doc_path, data_path, new_value)
|
self.override_manifest_value(doc_path, data_path, new_value)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lint.validate_armada_documents(self.documents)
|
validate.validate_armada_documents(self.documents)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise override_exceptions.InvalidOverrideValueException(
|
raise override_exceptions.InvalidOverrideValueException(
|
||||||
self.overrides)
|
self.overrides)
|
||||||
|
120
armada/schemas/armada-chart-schema.yaml
Normal file
120
armada/schemas/armada-chart-schema.yaml
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# 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/v1
|
||||||
|
schema: metadata/Control/v1
|
||||||
|
data:
|
||||||
|
$schema: http://json-schema.org/schema#
|
||||||
|
definitions:
|
||||||
|
labels:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
hook_action:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
labels:
|
||||||
|
$ref: '#/definitions/labels'
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
additionalProperties: false
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
release:
|
||||||
|
type: string
|
||||||
|
chart_name:
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
type: object
|
||||||
|
dependencies:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
test:
|
||||||
|
type: boolean
|
||||||
|
timeout:
|
||||||
|
type: integer
|
||||||
|
wait:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
timeout:
|
||||||
|
type: integer
|
||||||
|
labels:
|
||||||
|
$ref: "#/definitions/labels"
|
||||||
|
additionalProperties: false
|
||||||
|
source:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
location:
|
||||||
|
type: string
|
||||||
|
subpath:
|
||||||
|
type: string
|
||||||
|
reference:
|
||||||
|
type:
|
||||||
|
- string
|
||||||
|
- "null"
|
||||||
|
required:
|
||||||
|
- location
|
||||||
|
- subpath
|
||||||
|
- type
|
||||||
|
install:
|
||||||
|
# NOTE(sh8121att) Not clear that this key is actually used
|
||||||
|
# in the code. Will leave it here for backward compatabilities
|
||||||
|
# until an additional audit is done.
|
||||||
|
type: object
|
||||||
|
upgrade:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
no_hooks:
|
||||||
|
type: boolean
|
||||||
|
pre:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
delete:
|
||||||
|
$ref: '#/definitions/hook_action'
|
||||||
|
update:
|
||||||
|
$ref: '#/definitions/hook_action'
|
||||||
|
create:
|
||||||
|
$ref: '#/definitions/hook_action'
|
||||||
|
post:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
create:
|
||||||
|
$ref: '#/definitions/hook_action'
|
||||||
|
required:
|
||||||
|
- no_hooks
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- dependencies
|
||||||
|
- namespace
|
||||||
|
- chart_name
|
||||||
|
- release
|
||||||
|
- source
|
||||||
|
additionalProperties: false
|
||||||
|
...
|
39
armada/schemas/armada-chartgroup-schema.yaml
Normal file
39
armada/schemas/armada-chartgroup-schema.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# 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/v1
|
||||||
|
schema: metadata/Control/v1
|
||||||
|
data:
|
||||||
|
$schema: http://json-schema.org/schema#
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
sequenced:
|
||||||
|
type: boolean
|
||||||
|
test_charts:
|
||||||
|
type: boolean
|
||||||
|
chart_group:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- chart_group
|
||||||
|
additionalProperties: false
|
||||||
|
...
|
34
armada/schemas/armada-manifest-schema.yaml
Normal file
34
armada/schemas/armada-manifest-schema.yaml
Normal 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.
|
||||||
|
|
||||||
|
# JSON schema for validating Armada manifests.
|
||||||
|
---
|
||||||
|
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
|
||||||
|
...
|
@ -33,3 +33,55 @@ data:
|
|||||||
release_prefix: armada
|
release_prefix: armada
|
||||||
chart_groups:
|
chart_groups:
|
||||||
- blog-group
|
- blog-group
|
||||||
|
---
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: blog-3
|
||||||
|
data:
|
||||||
|
chart_name: blog-3
|
||||||
|
release: blog-3
|
||||||
|
namespace: default
|
||||||
|
values: {}
|
||||||
|
source:
|
||||||
|
type: git
|
||||||
|
location: https://github.com/namespace/hello-world-chart
|
||||||
|
subpath: .
|
||||||
|
reference: master
|
||||||
|
dependencies: []
|
||||||
|
---
|
||||||
|
schema: armada/ChartGroup/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: blog-group3
|
||||||
|
data:
|
||||||
|
description: Deploys Simple Service
|
||||||
|
sequenced: False
|
||||||
|
chart_group:
|
||||||
|
- blog-3
|
||||||
|
---
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: blog-4
|
||||||
|
data:
|
||||||
|
chart_name: blog-4
|
||||||
|
release: blog-4
|
||||||
|
namespace: default
|
||||||
|
values: {}
|
||||||
|
source:
|
||||||
|
type: git
|
||||||
|
location: https://github.com/namespace/hello-world-chart
|
||||||
|
subpath: .
|
||||||
|
reference: master
|
||||||
|
dependencies: []
|
||||||
|
---
|
||||||
|
schema: armada/ChartGroup/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: blog-group4
|
||||||
|
data:
|
||||||
|
description: Deploys Simple Service
|
||||||
|
sequenced: False
|
||||||
|
chart_group:
|
||||||
|
- blog-4
|
||||||
|
@ -8,3 +8,55 @@ data:
|
|||||||
chart_groups:
|
chart_groups:
|
||||||
- blog-group3
|
- blog-group3
|
||||||
- blog-group4
|
- blog-group4
|
||||||
|
---
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: blog-3
|
||||||
|
data:
|
||||||
|
chart_name: blog-3
|
||||||
|
release: blog-3
|
||||||
|
namespace: default
|
||||||
|
values: {}
|
||||||
|
source:
|
||||||
|
type: git
|
||||||
|
location: https://github.com/namespace/hello-world-chart
|
||||||
|
subpath: .
|
||||||
|
reference: master
|
||||||
|
dependencies: []
|
||||||
|
---
|
||||||
|
schema: armada/ChartGroup/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: blog-group3
|
||||||
|
data:
|
||||||
|
description: Deploys Simple Service
|
||||||
|
sequenced: False
|
||||||
|
chart_group:
|
||||||
|
- blog-3
|
||||||
|
---
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: blog-4
|
||||||
|
data:
|
||||||
|
chart_name: blog-4
|
||||||
|
release: blog-4
|
||||||
|
namespace: default
|
||||||
|
values: {}
|
||||||
|
source:
|
||||||
|
type: git
|
||||||
|
location: https://github.com/namespace/hello-world-chart
|
||||||
|
subpath: .
|
||||||
|
reference: master
|
||||||
|
dependencies: []
|
||||||
|
---
|
||||||
|
schema: armada/ChartGroup/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: blog-group4
|
||||||
|
data:
|
||||||
|
description: Deploys Simple Service
|
||||||
|
sequenced: False
|
||||||
|
chart_group:
|
||||||
|
- blog-4
|
||||||
|
@ -135,10 +135,10 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assertTrue(hasattr(armada_obj, 'config'))
|
self.assertTrue(hasattr(armada_obj, 'manifest'))
|
||||||
self.assertIsInstance(armada_obj.config, dict)
|
self.assertIsInstance(armada_obj.manifest, dict)
|
||||||
self.assertIn('armada', armada_obj.config)
|
self.assertIn('armada', armada_obj.manifest)
|
||||||
self.assertEqual(expected_config, armada_obj.config)
|
self.assertEqual(expected_config, armada_obj.manifest)
|
||||||
|
|
||||||
@mock.patch.object(armada, 'source')
|
@mock.patch.object(armada, 'source')
|
||||||
@mock.patch('armada.handlers.armada.Tiller')
|
@mock.patch('armada.handlers.armada.Tiller')
|
||||||
@ -175,7 +175,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
|||||||
|
|
||||||
armada_obj.post_flight_ops()
|
armada_obj.post_flight_ops()
|
||||||
|
|
||||||
for group in armada_obj.config['armada']['chart_groups']:
|
for group in armada_obj.manifest['armada']['chart_groups']:
|
||||||
for counter, chart in enumerate(group.get('chart_group')):
|
for counter, chart in enumerate(group.get('chart_group')):
|
||||||
if chart.get('chart').get('source').get('type') == 'git':
|
if chart.get('chart').get('source').get('type') == 'git':
|
||||||
mock_source.source_cleanup.assert_called_with(
|
mock_source.source_cleanup.assert_called_with(
|
||||||
@ -193,7 +193,8 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
|||||||
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
||||||
armada_obj = armada.Armada(yaml_documents)
|
armada_obj = armada.Armada(yaml_documents)
|
||||||
|
|
||||||
charts = armada_obj.config['armada']['chart_groups'][0]['chart_group']
|
charts = armada_obj.manifest['armada']['chart_groups'][0][
|
||||||
|
'chart_group']
|
||||||
chart_1 = charts[0]['chart']
|
chart_1 = charts[0]['chart']
|
||||||
chart_2 = charts[1]['chart']
|
chart_2 = charts[1]['chart']
|
||||||
|
|
||||||
@ -208,7 +209,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
|||||||
method_calls = [
|
method_calls = [
|
||||||
mock.call(
|
mock.call(
|
||||||
mock_chartbuilder().get_helm_chart(),
|
mock_chartbuilder().get_helm_chart(),
|
||||||
"{}-{}".format(armada_obj.config['armada']['release_prefix'],
|
"{}-{}".format(armada_obj.manifest['armada']['release_prefix'],
|
||||||
chart_1['release']),
|
chart_1['release']),
|
||||||
chart_1['namespace'],
|
chart_1['namespace'],
|
||||||
dry_run=armada_obj.dry_run,
|
dry_run=armada_obj.dry_run,
|
||||||
@ -217,7 +218,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
|||||||
timeout=armada_obj.timeout),
|
timeout=armada_obj.timeout),
|
||||||
mock.call(
|
mock.call(
|
||||||
mock_chartbuilder().get_helm_chart(),
|
mock_chartbuilder().get_helm_chart(),
|
||||||
"{}-{}".format(armada_obj.config['armada']['release_prefix'],
|
"{}-{}".format(armada_obj.manifest['armada']['release_prefix'],
|
||||||
chart_2['release']),
|
chart_2['release']),
|
||||||
chart_2['namespace'],
|
chart_2['namespace'],
|
||||||
dry_run=armada_obj.dry_run,
|
dry_run=armada_obj.dry_run,
|
||||||
|
@ -21,6 +21,7 @@ import testtools
|
|||||||
from armada import const
|
from armada import const
|
||||||
from armada import exceptions
|
from armada import exceptions
|
||||||
from armada.handlers import manifest
|
from armada.handlers import manifest
|
||||||
|
from armada.utils import validate
|
||||||
|
|
||||||
|
|
||||||
class ManifestTestCase(testtools.TestCase):
|
class ManifestTestCase(testtools.TestCase):
|
||||||
@ -411,30 +412,22 @@ class ManifestNegativeTestCase(testtools.TestCase):
|
|||||||
"""Validate that attempting to build a chart that points to
|
"""Validate that attempting to build a chart that points to
|
||||||
a missing dependency fails.
|
a missing dependency fails.
|
||||||
"""
|
"""
|
||||||
armada_manifest = manifest.Manifest(self.documents)
|
|
||||||
self.documents[1]['data']['dependencies'] = ['missing-dependency']
|
self.documents[1]['data']['dependencies'] = ['missing-dependency']
|
||||||
test_chart = armada_manifest.find_chart_document('mariadb')
|
valid, details = validate.validate_armada_documents(self.documents)
|
||||||
self.assertRaises(exceptions.ManifestException,
|
self.assertFalse(valid)
|
||||||
armada_manifest.build_chart_deps,
|
|
||||||
test_chart)
|
|
||||||
|
|
||||||
def test_build_chart_group_with_missing_chart_grp_fails(self):
|
def test_build_chart_group_with_missing_chart_grp_fails(self):
|
||||||
"""Validate that attempting to build a chart group document with
|
"""Validate that attempting to build a chart group document with
|
||||||
missing chart group fails.
|
missing chart group fails.
|
||||||
"""
|
"""
|
||||||
armada_manifest = manifest.Manifest(self.documents)
|
|
||||||
self.documents[5]['data']['chart_group'] = ['missing-chart-group']
|
self.documents[5]['data']['chart_group'] = ['missing-chart-group']
|
||||||
test_chart_group = armada_manifest.find_chart_group_document(
|
valid, details = validate.validate_armada_documents(self.documents)
|
||||||
'openstack-keystone')
|
self.assertFalse(valid)
|
||||||
self.assertRaises(exceptions.ManifestException,
|
|
||||||
armada_manifest.build_chart_group,
|
|
||||||
test_chart_group)
|
|
||||||
|
|
||||||
def test_build_armada_manifest_with_missing_chart_grps_fails(self):
|
def test_build_armada_manifest_with_missing_chart_grps_fails(self):
|
||||||
"""Validate that attempting to build a manifest with missing
|
"""Validate that attempting to build a manifest with missing
|
||||||
chart groups fails.
|
chart groups fails.
|
||||||
"""
|
"""
|
||||||
armada_manifest = manifest.Manifest(self.documents)
|
|
||||||
self.documents[6]['data']['chart_groups'] = ['missing-chart-groups']
|
self.documents[6]['data']['chart_groups'] = ['missing-chart-groups']
|
||||||
self.assertRaises(exceptions.ManifestException,
|
valid, details = validate.validate_armada_documents(self.documents)
|
||||||
armada_manifest.build_armada_manifest)
|
self.assertFalse(valid)
|
||||||
|
@ -75,8 +75,12 @@ class OverrideTestCase(testtools.TestCase):
|
|||||||
self.assertNotEqual(original_documents, documents_copy)
|
self.assertNotEqual(original_documents, documents_copy)
|
||||||
# since overrides done, these documents aren't same anymore
|
# since overrides done, these documents aren't same anymore
|
||||||
self.assertNotEqual(original_documents, values_documents)
|
self.assertNotEqual(original_documents, values_documents)
|
||||||
|
target_doc = [x
|
||||||
|
for x
|
||||||
|
in ovr.documents
|
||||||
|
if x.get('metadata').get('name') == 'simple-armada'][0]
|
||||||
self.assertEqual('overridden',
|
self.assertEqual('overridden',
|
||||||
ovr.documents[-1]['data']['release_prefix'])
|
target_doc['data']['release_prefix'])
|
||||||
|
|
||||||
override = ('manifest:simple-armada:chart_groups='
|
override = ('manifest:simple-armada:chart_groups='
|
||||||
'blog-group3,blog-group4',)
|
'blog-group3,blog-group4',)
|
||||||
@ -283,8 +287,12 @@ class OverrideTestCase(testtools.TestCase):
|
|||||||
ovr = Override(documents, override)
|
ovr = Override(documents, override)
|
||||||
ovr.update_manifests()
|
ovr.update_manifests()
|
||||||
ovr_doc = ovr.find_manifest_document(doc_path)
|
ovr_doc = ovr.find_manifest_document(doc_path)
|
||||||
expect_doc = list(yaml.load_all(e.read()))[0]
|
target_docs = list(yaml.load_all(e.read()))
|
||||||
self.assertEqual(expect_doc, ovr_doc)
|
expected_doc = [x
|
||||||
|
for x
|
||||||
|
in target_docs
|
||||||
|
if x.get('schema') == 'armada/Manifest/v1'][0]
|
||||||
|
self.assertEqual(expected_doc.get('data'), ovr_doc.get('data'))
|
||||||
|
|
||||||
def test_find_manifest_document_valid(self):
|
def test_find_manifest_document_valid(self):
|
||||||
expected = "{}/templates/override-{}-expected.yaml".format(
|
expected = "{}/templates/override-{}-expected.yaml".format(
|
||||||
|
139
armada/tests/unit/resources/valid_armada_document.yaml
Normal file
139
armada/tests/unit/resources/valid_armada_document.yaml
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: helm-toolkit
|
||||||
|
data:
|
||||||
|
chart_name: helm-toolkit
|
||||||
|
release: helm-toolkit
|
||||||
|
namespace: helm-tookit
|
||||||
|
install:
|
||||||
|
no_hooks: false
|
||||||
|
upgrade:
|
||||||
|
no_hooks: false
|
||||||
|
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
|
@ -1,56 +0,0 @@
|
|||||||
---
|
|
||||||
schema: armada/Manifest/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-manifest
|
|
||||||
data:
|
|
||||||
release_prefix: example
|
|
||||||
chart_groups:
|
|
||||||
- example-group
|
|
||||||
---
|
|
||||||
schema: armada/ChartGroup/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-group
|
|
||||||
data:
|
|
||||||
description: "OpenStack Infra Services"
|
|
||||||
chart_group:
|
|
||||||
- example-chart
|
|
||||||
---
|
|
||||||
schema: armada/Chart/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-chart
|
|
||||||
data:
|
|
||||||
name: keystone
|
|
||||||
release: keystone
|
|
||||||
namespace: undercloud
|
|
||||||
timeout: 100
|
|
||||||
install:
|
|
||||||
no_hooks: false
|
|
||||||
upgrade:
|
|
||||||
no_hooks: false
|
|
||||||
values: {}
|
|
||||||
source:
|
|
||||||
type: git
|
|
||||||
location: git://github.com/example/example
|
|
||||||
subpath: example-chart
|
|
||||||
reference: master
|
|
||||||
dependencies:
|
|
||||||
- dep-chart
|
|
||||||
---
|
|
||||||
schema: armada/Chart/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: dep-chart
|
|
||||||
data:
|
|
||||||
name: dep-chart
|
|
||||||
release: null
|
|
||||||
namespace: null
|
|
||||||
values: {}
|
|
||||||
source:
|
|
||||||
type: git
|
|
||||||
location: git://github.com/example/example
|
|
||||||
subpath: dep-chart
|
|
||||||
reference: master
|
|
||||||
dependencies: []
|
|
@ -1,168 +0,0 @@
|
|||||||
# 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 unittest
|
|
||||||
import yaml
|
|
||||||
import os
|
|
||||||
|
|
||||||
from armada.utils import lint
|
|
||||||
|
|
||||||
|
|
||||||
class LintTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.basepath = os.path.join(os.path.dirname(__file__))
|
|
||||||
|
|
||||||
def test_lint_armada_yaml_pass(self):
|
|
||||||
template = '{}/templates/valid_armada_document.yaml'.format(
|
|
||||||
self.basepath)
|
|
||||||
document = yaml.safe_load_all(open(template).read())
|
|
||||||
resp = lint.validate_armada_documents(document)
|
|
||||||
self.assertTrue(resp)
|
|
||||||
|
|
||||||
def test_lint_armada_manifest_no_groups(self):
|
|
||||||
template_manifest = """
|
|
||||||
schema: armada/Manifest/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-manifest
|
|
||||||
data:
|
|
||||||
release_prefix: example
|
|
||||||
"""
|
|
||||||
document = yaml.safe_load_all(template_manifest)
|
|
||||||
with self.assertRaises(Exception):
|
|
||||||
lint.validate_armada_documents(document)
|
|
||||||
|
|
||||||
def test_lint_validate_manifest_pass(self):
|
|
||||||
template_manifest = """
|
|
||||||
schema: armada/Manifest/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-manifest
|
|
||||||
data:
|
|
||||||
release_prefix: example
|
|
||||||
chart_groups:
|
|
||||||
- example-group
|
|
||||||
"""
|
|
||||||
document = yaml.safe_load_all(template_manifest)
|
|
||||||
self.assertTrue(lint.validate_manifest_document(document))
|
|
||||||
|
|
||||||
def test_lint_validate_manifest_no_prefix(self):
|
|
||||||
template_manifest = """
|
|
||||||
schema: armada/Manifest/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-manifest
|
|
||||||
data:
|
|
||||||
chart_groups:
|
|
||||||
- example-group
|
|
||||||
"""
|
|
||||||
document = yaml.safe_load_all(template_manifest)
|
|
||||||
with self.assertRaises(Exception):
|
|
||||||
lint.validate_manifest_document(document)
|
|
||||||
|
|
||||||
def test_lint_validate_group_pass(self):
|
|
||||||
template_manifest = """
|
|
||||||
schema: armada/ChartGroup/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-manifest
|
|
||||||
data:
|
|
||||||
description: this is sample
|
|
||||||
chart_group:
|
|
||||||
- example-group
|
|
||||||
"""
|
|
||||||
document = yaml.safe_load_all(template_manifest)
|
|
||||||
self.assertTrue(lint.validate_chart_group_document(document))
|
|
||||||
|
|
||||||
def test_lint_validate_group_no_chart_group(self):
|
|
||||||
template_manifest = """
|
|
||||||
schema: armada/ChartGroup/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-manifest
|
|
||||||
data:
|
|
||||||
description: this is sample
|
|
||||||
"""
|
|
||||||
document = yaml.safe_load_all(template_manifest)
|
|
||||||
with self.assertRaises(Exception):
|
|
||||||
lint.validate_chart_group_document(document)
|
|
||||||
|
|
||||||
def test_lint_validate_chart_pass(self):
|
|
||||||
template_manifest = """
|
|
||||||
schema: armada/Chart/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-chart
|
|
||||||
data:
|
|
||||||
name: keystone
|
|
||||||
release: keystone
|
|
||||||
namespace: undercloud
|
|
||||||
timeout: 100
|
|
||||||
install:
|
|
||||||
no_hooks: false
|
|
||||||
upgrade:
|
|
||||||
no_hooks: false
|
|
||||||
values: {}
|
|
||||||
source:
|
|
||||||
type: git
|
|
||||||
location: git://github.com/example/example
|
|
||||||
subpath: example-chart
|
|
||||||
reference: master
|
|
||||||
dependencies:
|
|
||||||
- dep-chart
|
|
||||||
"""
|
|
||||||
document = yaml.safe_load_all(template_manifest)
|
|
||||||
self.assertTrue(lint.validate_chart_document(document))
|
|
||||||
|
|
||||||
def test_lint_validate_chart_no_release(self):
|
|
||||||
template_manifest = """
|
|
||||||
schema: armada/Chart/v1
|
|
||||||
metadata:
|
|
||||||
schema: metadata/Document/v1
|
|
||||||
name: example-chart
|
|
||||||
data:
|
|
||||||
name: keystone
|
|
||||||
namespace: undercloud
|
|
||||||
timeout: 100
|
|
||||||
install:
|
|
||||||
no_hooks: false
|
|
||||||
upgrade:
|
|
||||||
no_hooks: false
|
|
||||||
values: {}
|
|
||||||
source:
|
|
||||||
type: git
|
|
||||||
location: git://github.com/example/example
|
|
||||||
subpath: example-chart
|
|
||||||
reference: master
|
|
||||||
dependencies:
|
|
||||||
- dep-chart
|
|
||||||
"""
|
|
||||||
document = yaml.safe_load_all(template_manifest)
|
|
||||||
with self.assertRaises(Exception):
|
|
||||||
lint.validate_chart_document(document)
|
|
||||||
|
|
||||||
def test_lint_validate_manifest_url(self):
|
|
||||||
value = 'url'
|
|
||||||
assert lint.validate_manifest_url(value) is False
|
|
||||||
value = 'https://raw.githubusercontent.com/att-comdev/' \
|
|
||||||
'armada/master/examples/simple.yaml'
|
|
||||||
assert lint.validate_manifest_url(value) is True
|
|
||||||
|
|
||||||
def test_lint_validate_manifest_filepath(self):
|
|
||||||
value = 'filepath'
|
|
||||||
assert lint.validate_manifest_filepath(value) is False
|
|
||||||
value = '{}/templates/valid_armada_document.yaml'.format(
|
|
||||||
self.basepath)
|
|
||||||
assert lint.validate_manifest_filepath(value) is True
|
|
302
armada/tests/unit/utils/test_validate.py
Normal file
302
armada/tests/unit/utils/test_validate.py
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from armada.tests.unit import base
|
||||||
|
from armada.utils import validate
|
||||||
|
|
||||||
|
|
||||||
|
template_chart = """
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: example-chart
|
||||||
|
data:
|
||||||
|
chart_name: keystone
|
||||||
|
release: keystone
|
||||||
|
namespace: undercloud
|
||||||
|
timeout: 100
|
||||||
|
install:
|
||||||
|
no_hooks: false
|
||||||
|
upgrade:
|
||||||
|
no_hooks: false
|
||||||
|
values: {}
|
||||||
|
source:
|
||||||
|
type: git
|
||||||
|
location: git://github.com/example/example
|
||||||
|
subpath: example-chart
|
||||||
|
reference: master
|
||||||
|
dependencies: []
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_chart_group = """
|
||||||
|
schema: armada/ChartGroup/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: example-manifest
|
||||||
|
data:
|
||||||
|
description: this is sample
|
||||||
|
chart_group:
|
||||||
|
- example-chart
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_manifest = """
|
||||||
|
schema: armada/Manifest/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: example-manifest
|
||||||
|
data:
|
||||||
|
release_prefix: example
|
||||||
|
chart_groups:
|
||||||
|
- example-chart
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class BaseValidateTest(base.ArmadaTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(BaseValidateTest, self).setUp()
|
||||||
|
self.basepath = os.path.join(os.path.dirname(__file__), os.pardir)
|
||||||
|
|
||||||
|
def _build_error_message(self, document, name, message):
|
||||||
|
return "Invalid document [{}] {}: {}.".format(document, name, message)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
with open(template) as f:
|
||||||
|
documents = yaml.safe_load_all(f.read())
|
||||||
|
valid, details = validate.validate_armada_documents(list(documents))
|
||||||
|
|
||||||
|
self.assertTrue(valid)
|
||||||
|
|
||||||
|
def test_validate_manifest_passes(self):
|
||||||
|
manifest = yaml.safe_load(template_manifest)
|
||||||
|
is_valid, error = validate.validate_armada_document(manifest)
|
||||||
|
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
|
||||||
|
def test_validate_chart_group_with_values(self):
|
||||||
|
test_chart_group = """
|
||||||
|
---
|
||||||
|
schema: armada/ChartGroup/v1
|
||||||
|
metadata:
|
||||||
|
name: kubernetes-proxy
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
data:
|
||||||
|
description: Kubernetes proxy
|
||||||
|
name: kubernetes-proxy
|
||||||
|
sequenced: true
|
||||||
|
chart_group:
|
||||||
|
- proxy
|
||||||
|
---
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
name: proxy
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
data:
|
||||||
|
chart_name: proxy
|
||||||
|
timeout: 600
|
||||||
|
release: kubernetes-proxy
|
||||||
|
source:
|
||||||
|
subpath: proxy
|
||||||
|
type: local
|
||||||
|
location: "/etc/genesis/armada/assets/charts"
|
||||||
|
namespace: kube-system
|
||||||
|
upgrade:
|
||||||
|
no_hooks: true
|
||||||
|
values:
|
||||||
|
images:
|
||||||
|
tags:
|
||||||
|
proxy: gcr.io/google_containers/hyperkube-amd64:v1.8.6
|
||||||
|
network:
|
||||||
|
kubernetes_netloc: 127.0.0.1:6553
|
||||||
|
dependencies:
|
||||||
|
- helm-toolkit
|
||||||
|
---
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
name: helm-toolkit
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
data:
|
||||||
|
chart_name: helm-toolkit
|
||||||
|
wait:
|
||||||
|
timeout: 600
|
||||||
|
release: helm-toolkit
|
||||||
|
source:
|
||||||
|
reference: master
|
||||||
|
subpath: helm-toolkit
|
||||||
|
location: https://git.openstack.org/openstack/openstack-helm
|
||||||
|
type: git
|
||||||
|
namespace: helm-toolkit
|
||||||
|
upgrade:
|
||||||
|
no_hooks: true
|
||||||
|
values: {}
|
||||||
|
dependencies: []
|
||||||
|
"""
|
||||||
|
|
||||||
|
chart_group = yaml.safe_load_all(test_chart_group)
|
||||||
|
is_valid, error = validate.validate_armada_documents(list(chart_group))
|
||||||
|
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
|
||||||
|
def test_validate_group_passes(self):
|
||||||
|
chart_group = yaml.safe_load(template_chart_group)
|
||||||
|
is_valid, error = validate.validate_armada_document(chart_group)
|
||||||
|
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
|
||||||
|
def test_validate_chart_passes(self):
|
||||||
|
chart = yaml.safe_load(template_chart)
|
||||||
|
is_valid, error = validate.validate_armada_document(chart)
|
||||||
|
|
||||||
|
self.assertTrue(is_valid)
|
||||||
|
|
||||||
|
def test_validate_manifest_url(self):
|
||||||
|
value = 'url'
|
||||||
|
self.assertFalse(validate.validate_manifest_url(value))
|
||||||
|
value = 'https://raw.githubusercontent.com/att-comdev/' \
|
||||||
|
'armada/master/examples/simple.yaml'
|
||||||
|
self.assertTrue(validate.validate_manifest_url(value))
|
||||||
|
|
||||||
|
def test_validate_manifest_filepath(self):
|
||||||
|
value = 'filepath'
|
||||||
|
self.assertFalse(validate.validate_manifest_filepath(value))
|
||||||
|
value = '{}/resources/valid_armada_document.yaml'.format(
|
||||||
|
self.basepath)
|
||||||
|
self.assertTrue(validate.validate_manifest_filepath(value))
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
validate.validate_armada_documents,
|
||||||
|
['invalid'])
|
||||||
|
|
||||||
|
def test_validate_invalid_chart_armada_manifest(self):
|
||||||
|
template = '{}/resources/valid_armada_document.yaml'.format(
|
||||||
|
self.basepath)
|
||||||
|
|
||||||
|
with open(template) as f:
|
||||||
|
documents = list(yaml.safe_load_all(f.read()))
|
||||||
|
|
||||||
|
mariadb_document = [
|
||||||
|
d for d in documents if d['metadata']['name'] == 'mariadb'][0]
|
||||||
|
del mariadb_document['data']['release']
|
||||||
|
|
||||||
|
_, error_messages = validate.validate_armada_documents(documents)
|
||||||
|
expected_error = self._build_error_message(
|
||||||
|
'armada/Chart/v1', 'mariadb',
|
||||||
|
"'release' is a required property")
|
||||||
|
|
||||||
|
self.assertEqual(1, len(error_messages))
|
||||||
|
self.assertEqual(expected_error, error_messages[0]['message'])
|
||||||
|
|
||||||
|
def test_validate_validate_group_without_required_chart_group(self):
|
||||||
|
template_manifest = """
|
||||||
|
schema: armada/ChartGroup/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: example-manifest
|
||||||
|
data:
|
||||||
|
description: this is sample
|
||||||
|
"""
|
||||||
|
document = yaml.safe_load(template_manifest)
|
||||||
|
is_valid, error = validate.validate_armada_document(document)
|
||||||
|
|
||||||
|
expected_error = self._build_error_message(
|
||||||
|
'armada/ChartGroup/v1', 'example-manifest',
|
||||||
|
"'chart_group' is a required property")
|
||||||
|
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
self.assertEqual(error[0]['message'], expected_error)
|
||||||
|
|
||||||
|
def test_validate_manifest_without_required_release_prefix(self):
|
||||||
|
template_manifest = """
|
||||||
|
schema: armada/Manifest/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: example-manifest
|
||||||
|
data:
|
||||||
|
chart_groups:
|
||||||
|
- example-group
|
||||||
|
"""
|
||||||
|
document = yaml.safe_load(template_manifest)
|
||||||
|
is_valid, error = validate.validate_armada_document(document)
|
||||||
|
expected_error = self._build_error_message(
|
||||||
|
'armada/Manifest/v1', 'example-manifest',
|
||||||
|
"'release_prefix' is a required property")
|
||||||
|
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
self.assertEqual(error[0]['message'], expected_error)
|
||||||
|
|
||||||
|
def test_validate_chart_without_required_release_property(self):
|
||||||
|
template_manifest = """
|
||||||
|
schema: armada/Chart/v1
|
||||||
|
metadata:
|
||||||
|
schema: metadata/Document/v1
|
||||||
|
name: example-chart
|
||||||
|
data:
|
||||||
|
chart_name: keystone
|
||||||
|
namespace: undercloud
|
||||||
|
timeout: 100
|
||||||
|
install:
|
||||||
|
no_hooks: false
|
||||||
|
upgrade:
|
||||||
|
no_hooks: false
|
||||||
|
values: {}
|
||||||
|
source:
|
||||||
|
type: git
|
||||||
|
location: git://github.com/example/example
|
||||||
|
subpath: example-chart
|
||||||
|
reference: master
|
||||||
|
dependencies:
|
||||||
|
- dep-chart
|
||||||
|
"""
|
||||||
|
document = yaml.safe_load(template_manifest)
|
||||||
|
is_valid, error = validate.validate_armada_document(document)
|
||||||
|
expected_error = self._build_error_message(
|
||||||
|
'armada/Chart/v1', 'example-chart',
|
||||||
|
"'release' is a required property")
|
||||||
|
|
||||||
|
self.assertFalse(is_valid)
|
||||||
|
self.assertEqual(error[0]['message'], expected_error)
|
@ -1,109 +0,0 @@
|
|||||||
# 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 os
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from armada.const import DOCUMENT_CHART, DOCUMENT_GROUP, DOCUMENT_MANIFEST
|
|
||||||
from armada.const import KEYWORD_ARMADA, KEYWORD_PREFIX, KEYWORD_GROUPS, \
|
|
||||||
KEYWORD_CHARTS, KEYWORD_RELEASE
|
|
||||||
|
|
||||||
|
|
||||||
def validate_armada_documents(documents):
|
|
||||||
manifest = validate_manifest_document(documents)
|
|
||||||
group = validate_chart_group_document(documents)
|
|
||||||
chart = validate_chart_document(documents)
|
|
||||||
|
|
||||||
return manifest and group and chart
|
|
||||||
|
|
||||||
|
|
||||||
def validate_armada_object(object):
|
|
||||||
if not isinstance(object.get(KEYWORD_ARMADA, None), dict):
|
|
||||||
raise Exception("Could not find {} keyword".format(KEYWORD_ARMADA))
|
|
||||||
|
|
||||||
armada_object = object.get('armada')
|
|
||||||
|
|
||||||
if armada_object.get(KEYWORD_PREFIX, None) is None:
|
|
||||||
raise Exception("Could not find {} keyword".format(KEYWORD_PREFIX))
|
|
||||||
|
|
||||||
if not isinstance(armada_object.get(KEYWORD_GROUPS), list):
|
|
||||||
raise Exception('{} is of correct type: {} (expected: {} )'.format(
|
|
||||||
KEYWORD_GROUPS, type(armada_object.get(KEYWORD_GROUPS)), list))
|
|
||||||
|
|
||||||
for group in armada_object.get(KEYWORD_GROUPS):
|
|
||||||
for chart in group.get(KEYWORD_CHARTS):
|
|
||||||
chart_obj = chart.get('chart')
|
|
||||||
if chart_obj.get(KEYWORD_RELEASE, None) is None:
|
|
||||||
raise Exception('Could not find {} in {}'.format(
|
|
||||||
KEYWORD_RELEASE, chart_obj.get('release')))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def validate_manifest_document(documents):
|
|
||||||
manifest_documents = []
|
|
||||||
for document in documents:
|
|
||||||
if document.get('schema') == DOCUMENT_MANIFEST:
|
|
||||||
manifest_documents.append(document)
|
|
||||||
manifest_data = document.get('data')
|
|
||||||
if not manifest_data.get(KEYWORD_PREFIX, False):
|
|
||||||
raise Exception(
|
|
||||||
'Missing {} keyword in manifest'.format(KEYWORD_PREFIX))
|
|
||||||
if not isinstance(manifest_data.get('chart_groups'),
|
|
||||||
list) and not manifest_data.get(
|
|
||||||
'chart_groups', False):
|
|
||||||
raise Exception('Missing %s values. Expecting list type'.
|
|
||||||
format(KEYWORD_GROUPS))
|
|
||||||
|
|
||||||
if len(manifest_documents) > 1:
|
|
||||||
raise Exception(
|
|
||||||
'Schema {} must be unique'.format(DOCUMENT_MANIFEST))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def validate_chart_group_document(documents):
|
|
||||||
for document in documents:
|
|
||||||
if document.get('schema') == DOCUMENT_GROUP:
|
|
||||||
manifest_data = document.get('data')
|
|
||||||
if not isinstance(manifest_data.get(KEYWORD_CHARTS),
|
|
||||||
list) and not manifest_data.get(
|
|
||||||
'chart_group', False):
|
|
||||||
raise Exception('Missing %s values. Expecting a list type'.
|
|
||||||
format(KEYWORD_CHARTS))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def validate_chart_document(documents):
|
|
||||||
for document in documents:
|
|
||||||
if document.get('schema') == DOCUMENT_CHART:
|
|
||||||
manifest_data = document.get('data')
|
|
||||||
if not manifest_data.get(KEYWORD_RELEASE, False):
|
|
||||||
raise Exception(
|
|
||||||
'Missing %s values in %s. Expecting a string type'.format(
|
|
||||||
KEYWORD_RELEASE, document.get('metadata').get('name')))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def validate_manifest_url(value):
|
|
||||||
try:
|
|
||||||
return (requests.get(value).status_code == 200)
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def validate_manifest_filepath(value):
|
|
||||||
return os.path.isfile(value)
|
|
210
armada/utils/validate.py
Normal file
210
armada/utils/validate.py
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
import os
|
||||||
|
import pkg_resources
|
||||||
|
import requests
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from armada.const import KEYWORD_GROUPS, KEYWORD_CHARTS, KEYWORD_RELEASE
|
||||||
|
from armada.handlers.manifest import Manifest
|
||||||
|
from armada.exceptions.manifest_exceptions import ManifestException
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""Validates an Armada manifest file output by
|
||||||
|
:class:`armada.handlers.manifest.Manifest`.
|
||||||
|
|
||||||
|
This will do business logic validation after the input
|
||||||
|
files have be syntatically validated via jsonschema.
|
||||||
|
|
||||||
|
:param dict manifest: The manifest to validate.
|
||||||
|
|
||||||
|
:returns: A tuple of (bool, list[dict]) where the first value
|
||||||
|
indicates whether the validation succeeded or failed and
|
||||||
|
the second value is the validation details with a minimum
|
||||||
|
keyset of (message(str), error(bool))
|
||||||
|
:rtype: tuple.
|
||||||
|
|
||||||
|
"""
|
||||||
|
details = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
armada_object = manifest.get_manifest().get('armada')
|
||||||
|
except ManifestException as me:
|
||||||
|
details.append(dict(message=str(me), error=True))
|
||||||
|
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')
|
||||||
|
details.append(dict(message=message, error=True))
|
||||||
|
|
||||||
|
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'))
|
||||||
|
details.append(dict(message=message, error=True))
|
||||||
|
|
||||||
|
if len([x for x in details if x.get('error', False)]) > 0:
|
||||||
|
return False, details
|
||||||
|
|
||||||
|
return True, details
|
||||||
|
|
||||||
|
|
||||||
|
def validate_armada_manifests(documents):
|
||||||
|
"""Validate each Aramada manifest found in the document set.
|
||||||
|
|
||||||
|
:param documents: List of Armada documents to validate
|
||||||
|
:type documents: :func: `list[dict]`.
|
||||||
|
"""
|
||||||
|
messages = []
|
||||||
|
all_valid = True
|
||||||
|
|
||||||
|
for document in documents:
|
||||||
|
if document.get('schema', '') == 'armada/Manifest/v1':
|
||||||
|
target = document.get('metadata').get('name')
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def validate_armada_document(document):
|
||||||
|
"""Validates a document ingested by Armada by subjecting it to JSON schema
|
||||||
|
validation.
|
||||||
|
|
||||||
|
:param dict dictionary: The document to validate.
|
||||||
|
|
||||||
|
:returns: A tuple of (bool, list[dict]) where the first value
|
||||||
|
indicates whether the validation succeeded or failed and
|
||||||
|
the second value is the validation details with a minimum
|
||||||
|
keyset of (message(str), error(bool))
|
||||||
|
:rtype: tuple.
|
||||||
|
:raises TypeError: If ``document`` is not of type ``dict``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not isinstance(document, dict):
|
||||||
|
raise TypeError('The provided input "%s" must be a dictionary.'
|
||||||
|
% document)
|
||||||
|
|
||||||
|
schema = document.get('schema', '<missing>')
|
||||||
|
document_name = document.get('metadata', {}).get('name', None)
|
||||||
|
details = []
|
||||||
|
|
||||||
|
if schema in SCHEMAS:
|
||||||
|
try:
|
||||||
|
validator = jsonschema.Draft4Validator(SCHEMAS[schema])
|
||||||
|
for error in validator.iter_errors(document.get('data')):
|
||||||
|
msg = "Invalid document [%s] %s: %s." % \
|
||||||
|
(schema, document_name, error.message)
|
||||||
|
details.append(dict(message=msg,
|
||||||
|
error=True,
|
||||||
|
doc_schema=schema,
|
||||||
|
doc_name=document_name))
|
||||||
|
except jsonschema.SchemaError as e:
|
||||||
|
error_message = ('The built-in Armada JSON schema %s is invalid. '
|
||||||
|
'Details: %s.' % (e.schema, e.message))
|
||||||
|
LOG.error(error_message)
|
||||||
|
details.append(dict(message=error_message, error=True))
|
||||||
|
else:
|
||||||
|
error_message = (
|
||||||
|
'Document [%s] %s is not supported.' %
|
||||||
|
(schema, document_name))
|
||||||
|
LOG.info(error_message)
|
||||||
|
details.append(dict(message=error_message, error=False))
|
||||||
|
|
||||||
|
if len([x for x in details if x.get('error', False)]) > 0:
|
||||||
|
return False, details
|
||||||
|
|
||||||
|
return True, details
|
||||||
|
|
||||||
|
|
||||||
|
def validate_armada_documents(documents):
|
||||||
|
"""Validates multiple Armada documents.
|
||||||
|
|
||||||
|
:param documents: List of Armada maanifests to validate.
|
||||||
|
:type documents: :func:`list[dict]`.
|
||||||
|
|
||||||
|
:returns: A tuple of bool, list[dict] where the first value is whether
|
||||||
|
the full set of documents is valid or not and the second is the
|
||||||
|
detail messages from validation
|
||||||
|
:rtype: tuple
|
||||||
|
"""
|
||||||
|
messages = []
|
||||||
|
# Track if all the documents in the set are valid
|
||||||
|
all_valid = True
|
||||||
|
|
||||||
|
for document in documents:
|
||||||
|
is_valid, details = validate_armada_document(document)
|
||||||
|
all_valid = all_valid and is_valid
|
||||||
|
messages.extend(details)
|
||||||
|
|
||||||
|
if all_valid:
|
||||||
|
valid, details = validate_armada_manifests(documents)
|
||||||
|
all_valid = all_valid and valid
|
||||||
|
messages.extend(details)
|
||||||
|
|
||||||
|
return all_valid, messages
|
||||||
|
|
||||||
|
|
||||||
|
def validate_manifest_url(value):
|
||||||
|
try:
|
||||||
|
return (requests.get(value).status_code == 200)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_manifest_filepath(value):
|
||||||
|
return os.path.isfile(value)
|
||||||
|
|
||||||
|
|
||||||
|
# Fill the cache.
|
||||||
|
_load_schemas()
|
@ -7,6 +7,10 @@ data:
|
|||||||
chart_name: helm-toolkit
|
chart_name: helm-toolkit
|
||||||
release: helm-toolkit
|
release: helm-toolkit
|
||||||
namespace: helm-tookit
|
namespace: helm-tookit
|
||||||
|
install:
|
||||||
|
no_hooks: false
|
||||||
|
upgrade:
|
||||||
|
no_hooks: false
|
||||||
values: {}
|
values: {}
|
||||||
source:
|
source:
|
||||||
type: git
|
type: git
|
||||||
|
@ -10,6 +10,7 @@ requests
|
|||||||
supermutes==0.2.5
|
supermutes==0.2.5
|
||||||
Paste>=2.0.3
|
Paste>=2.0.3
|
||||||
PasteDeploy>=1.5.2
|
PasteDeploy>=1.5.2
|
||||||
|
jsonschema>=2.6.0
|
||||||
|
|
||||||
# API
|
# API
|
||||||
falcon
|
falcon
|
||||||
|
Loading…
Reference in New Issue
Block a user