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.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
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ class Validate(api.BaseResource):
|
||||
self.logger.debug("Validating set of %d documents."
|
||||
% len(documents))
|
||||
|
||||
result = validate_armada_documents(documents)
|
||||
result, details = validate_armada_documents(documents)
|
||||
|
||||
resp.content_type = 'application/json'
|
||||
resp_body = {
|
||||
@ -58,12 +58,14 @@ class Validate(api.BaseResource):
|
||||
'apiVersion': 'v1.0',
|
||||
'metadata': {},
|
||||
'reason': 'Validation',
|
||||
'details': {
|
||||
'errorCount': 0,
|
||||
'messageList': []
|
||||
},
|
||||
'details': {},
|
||||
}
|
||||
|
||||
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:
|
||||
resp.status = falcon.HTTP_200
|
||||
resp_body['status'] = 'Success'
|
||||
@ -74,9 +76,6 @@ class Validate(api.BaseResource):
|
||||
resp_body['status'] = 'Failure'
|
||||
resp_body['message'] = 'Armada validations failed'
|
||||
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)
|
||||
except Exception as ex:
|
||||
|
@ -16,9 +16,7 @@ import click
|
||||
import yaml
|
||||
|
||||
from armada.cli import CliAction
|
||||
from armada.utils.lint import validate_armada_documents
|
||||
from armada.utils.lint import validate_armada_object
|
||||
from armada.handlers.manifest import Manifest
|
||||
from armada.utils.validate import validate_armada_documents
|
||||
from armada.handlers.document import ReferenceResolver
|
||||
|
||||
|
||||
@ -64,16 +62,20 @@ class ValidateManifest(CliAction):
|
||||
for d in doc_data:
|
||||
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:
|
||||
if doc_check and obj_check:
|
||||
valid, details = validate_armada_documents(documents)
|
||||
|
||||
if valid:
|
||||
self.logger.info('Successfully validated: %s',
|
||||
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:
|
||||
raise Exception('Failed to validate: %s', self.locations)
|
||||
raise Exception('Exception raised during '
|
||||
'validation: %s', self.locations)
|
||||
else:
|
||||
if len(self.locations) > 1:
|
||||
self.logger.error(
|
||||
@ -88,4 +90,7 @@ class ValidateManifest(CliAction):
|
||||
if resp.get('code') == 200:
|
||||
self.logger.info('Successfully validated: %s', self.locations)
|
||||
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
|
||||
|
||||
|
||||
class LintException(base_exception.ArmadaBaseException):
|
||||
class ValidateException(base_exception.ArmadaBaseException):
|
||||
'''Base class for linting exceptions and errors.'''
|
||||
|
||||
message = 'An unknown linting error occurred.'
|
||||
|
||||
|
||||
class InvalidManifestException(LintException):
|
||||
class InvalidManifestException(ValidateException):
|
||||
'''
|
||||
Exception for invalid manifests.
|
||||
|
||||
@ -29,28 +29,29 @@ class InvalidManifestException(LintException):
|
||||
*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.'''
|
||||
|
||||
message = 'Chart name must be a string.'
|
||||
|
||||
|
||||
class InvalidChartDefinitionException(LintException):
|
||||
class InvalidChartDefinitionException(ValidateException):
|
||||
'''Exception when invalid chart definition is encountered.'''
|
||||
|
||||
message = 'Invalid chart definition. Chart definition must be array.'
|
||||
|
||||
|
||||
class InvalidReleaseException(LintException):
|
||||
class InvalidReleaseException(ValidateException):
|
||||
'''Exception that occurs when a release is invalid.'''
|
||||
|
||||
message = 'Release needs to be a string.'
|
||||
|
||||
|
||||
class InvalidArmadaObjectException(LintException):
|
||||
class InvalidArmadaObjectException(ValidateException):
|
||||
'''
|
||||
Exception that occurs when an Armada object is not declared.
|
||||
|
||||
@ -58,4 +59,5 @@ class InvalidArmadaObjectException(LintException):
|
||||
*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.exceptions import armada_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.utils.release import release_prefix
|
||||
from armada.utils import source
|
||||
from armada.utils import lint
|
||||
from armada.utils import validate
|
||||
from armada import const
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -44,7 +44,7 @@ class Armada(object):
|
||||
'''
|
||||
|
||||
def __init__(self,
|
||||
file,
|
||||
documents,
|
||||
disable_update_pre=False,
|
||||
disable_update_post=False,
|
||||
enable_chart_cleanup=False,
|
||||
@ -60,7 +60,7 @@ class Armada(object):
|
||||
'''
|
||||
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_post: Disable post-update Tiller
|
||||
operations.
|
||||
@ -90,9 +90,9 @@ class Armada(object):
|
||||
tiller_host=tiller_host, tiller_port=tiller_port,
|
||||
tiller_namespace=tiller_namespace)
|
||||
self.values = values
|
||||
self.documents = file
|
||||
self.documents = documents
|
||||
self.target_manifest = target_manifest
|
||||
self.config = self.get_armada_manifest()
|
||||
self.manifest = self.get_armada_manifest()
|
||||
|
||||
def get_armada_manifest(self):
|
||||
return Manifest(
|
||||
@ -109,16 +109,26 @@ class Armada(object):
|
||||
return chart, values
|
||||
|
||||
def pre_flight_ops(self):
|
||||
'''
|
||||
Perform a series of checks and operations to ensure proper deployment
|
||||
'''
|
||||
"""Perform a series of checks and operations to ensure proper
|
||||
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():
|
||||
raise tiller_exceptions.TillerServicesUnavailableException()
|
||||
|
||||
if not lint.validate_armada_documents(self.documents):
|
||||
raise lint_exceptions.InvalidManifestException()
|
||||
valid, details = validate.validate_armada_documents(self.documents)
|
||||
|
||||
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
|
||||
if self.overrides or self.values:
|
||||
@ -126,20 +136,21 @@ class Armada(object):
|
||||
self.documents, overrides=self.overrides,
|
||||
values=self.values).update_manifests()
|
||||
|
||||
if not lint.validate_armada_object(self.config):
|
||||
raise lint_exceptions.InvalidArmadaObjectException()
|
||||
result, msg_list = validate.validate_armada_manifests(self.documents)
|
||||
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
|
||||
prefix = self.config.get(const.KEYWORD_ARMADA).get(
|
||||
prefix = self.manifest.get(const.KEYWORD_ARMADA).get(
|
||||
const.KEYWORD_PREFIX)
|
||||
failed_releases = self.get_releases_by_status(const.STATUS_FAILED)
|
||||
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):
|
||||
for ch in group.get(const.KEYWORD_CHARTS):
|
||||
ch_release_name = release_prefix(prefix,
|
||||
ch.get('chart')
|
||||
.get('chart_name'))
|
||||
ch_release_name = release_prefix(
|
||||
prefix, ch.get('chart').get('chart_name'))
|
||||
if release[0] == ch_release_name:
|
||||
LOG.info('Purging failed release %s '
|
||||
'before deployment', release[0])
|
||||
@ -150,7 +161,7 @@ class Armada(object):
|
||||
# We only support a git source type right now, which can also
|
||||
# handle git:// local paths as well
|
||||
repos = {}
|
||||
for group in self.config.get(const.KEYWORD_ARMADA).get(
|
||||
for group in self.manifest.get(const.KEYWORD_ARMADA).get(
|
||||
const.KEYWORD_GROUPS):
|
||||
for ch in group.get(const.KEYWORD_CHARTS):
|
||||
self.tag_cloned_repo(ch, repos)
|
||||
@ -229,12 +240,11 @@ class Armada(object):
|
||||
|
||||
# TODO: (gardlt) we need to break up this func into
|
||||
# a more cleaner format
|
||||
LOG.info("Performing Pre-Flight Operations")
|
||||
self.pre_flight_ops()
|
||||
|
||||
# extract known charts on tiller right now
|
||||
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)
|
||||
|
||||
if known_releases is None:
|
||||
@ -244,7 +254,7 @@ class Armada(object):
|
||||
LOG.debug("Release %s, Version %s found on Tiller", release[0],
|
||||
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
|
||||
desc = entry.get('description', 'A Chart Group')
|
||||
@ -394,7 +404,7 @@ class Armada(object):
|
||||
if self.enable_chart_cleanup:
|
||||
self.tiller.chart_cleanup(
|
||||
prefix,
|
||||
self.config[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS])
|
||||
self.manifest[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS])
|
||||
|
||||
return msg
|
||||
|
||||
@ -403,7 +413,7 @@ class Armada(object):
|
||||
Operations to run after deployment process has terminated
|
||||
'''
|
||||
# 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):
|
||||
for ch in group.get(const.KEYWORD_CHARTS):
|
||||
if ch.get('chart').get('source').get('type') == 'git':
|
||||
|
@ -11,6 +11,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from copy import deepcopy
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
@ -47,8 +48,7 @@ class Manifest(object):
|
||||
are not found or if the document types are missing required
|
||||
properties.
|
||||
"""
|
||||
self.config = None
|
||||
self.documents = documents
|
||||
self.documents = deepcopy(documents)
|
||||
self.charts, self.groups, manifests = self._find_documents(
|
||||
target_manifest)
|
||||
|
||||
@ -66,9 +66,8 @@ class Manifest(object):
|
||||
error = ('Documents must be a list of documents with at least one '
|
||||
'of each of the following schemas: %s and only one '
|
||||
'manifest' % expected_schemas)
|
||||
LOG.error(error, expected_schemas)
|
||||
raise exceptions.ManifestException(
|
||||
details=error % expected_schemas)
|
||||
LOG.error(error)
|
||||
raise exceptions.ManifestException(details=error)
|
||||
|
||||
def _find_documents(self, target_manifest=None):
|
||||
"""Returns the chart documents, chart group documents,
|
||||
|
@ -18,7 +18,7 @@ import yaml
|
||||
|
||||
from armada import const
|
||||
from armada.exceptions import override_exceptions
|
||||
from armada.utils import lint
|
||||
from armada.utils import validate
|
||||
|
||||
|
||||
class Override(object):
|
||||
@ -152,7 +152,7 @@ class Override(object):
|
||||
self.override_manifest_value(doc_path, data_path, new_value)
|
||||
|
||||
try:
|
||||
lint.validate_armada_documents(self.documents)
|
||||
validate.validate_armada_documents(self.documents)
|
||||
except Exception:
|
||||
raise override_exceptions.InvalidOverrideValueException(
|
||||
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
|
||||
chart_groups:
|
||||
- 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:
|
||||
- blog-group3
|
||||
- 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.assertIsInstance(armada_obj.config, dict)
|
||||
self.assertIn('armada', armada_obj.config)
|
||||
self.assertEqual(expected_config, armada_obj.config)
|
||||
self.assertTrue(hasattr(armada_obj, 'manifest'))
|
||||
self.assertIsInstance(armada_obj.manifest, dict)
|
||||
self.assertIn('armada', armada_obj.manifest)
|
||||
self.assertEqual(expected_config, armada_obj.manifest)
|
||||
|
||||
@mock.patch.object(armada, 'source')
|
||||
@mock.patch('armada.handlers.armada.Tiller')
|
||||
@ -175,7 +175,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
||||
|
||||
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')):
|
||||
if chart.get('chart').get('source').get('type') == 'git':
|
||||
mock_source.source_cleanup.assert_called_with(
|
||||
@ -193,7 +193,8 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
||||
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
|
||||
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_2 = charts[1]['chart']
|
||||
|
||||
@ -208,7 +209,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
||||
method_calls = [
|
||||
mock.call(
|
||||
mock_chartbuilder().get_helm_chart(),
|
||||
"{}-{}".format(armada_obj.config['armada']['release_prefix'],
|
||||
"{}-{}".format(armada_obj.manifest['armada']['release_prefix'],
|
||||
chart_1['release']),
|
||||
chart_1['namespace'],
|
||||
dry_run=armada_obj.dry_run,
|
||||
@ -217,7 +218,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
|
||||
timeout=armada_obj.timeout),
|
||||
mock.call(
|
||||
mock_chartbuilder().get_helm_chart(),
|
||||
"{}-{}".format(armada_obj.config['armada']['release_prefix'],
|
||||
"{}-{}".format(armada_obj.manifest['armada']['release_prefix'],
|
||||
chart_2['release']),
|
||||
chart_2['namespace'],
|
||||
dry_run=armada_obj.dry_run,
|
||||
|
@ -21,6 +21,7 @@ import testtools
|
||||
from armada import const
|
||||
from armada import exceptions
|
||||
from armada.handlers import manifest
|
||||
from armada.utils import validate
|
||||
|
||||
|
||||
class ManifestTestCase(testtools.TestCase):
|
||||
@ -411,30 +412,22 @@ class ManifestNegativeTestCase(testtools.TestCase):
|
||||
"""Validate that attempting to build a chart that points to
|
||||
a missing dependency fails.
|
||||
"""
|
||||
armada_manifest = manifest.Manifest(self.documents)
|
||||
self.documents[1]['data']['dependencies'] = ['missing-dependency']
|
||||
test_chart = armada_manifest.find_chart_document('mariadb')
|
||||
self.assertRaises(exceptions.ManifestException,
|
||||
armada_manifest.build_chart_deps,
|
||||
test_chart)
|
||||
valid, details = validate.validate_armada_documents(self.documents)
|
||||
self.assertFalse(valid)
|
||||
|
||||
def test_build_chart_group_with_missing_chart_grp_fails(self):
|
||||
"""Validate that attempting to build a chart group document with
|
||||
missing chart group fails.
|
||||
"""
|
||||
armada_manifest = manifest.Manifest(self.documents)
|
||||
self.documents[5]['data']['chart_group'] = ['missing-chart-group']
|
||||
test_chart_group = armada_manifest.find_chart_group_document(
|
||||
'openstack-keystone')
|
||||
self.assertRaises(exceptions.ManifestException,
|
||||
armada_manifest.build_chart_group,
|
||||
test_chart_group)
|
||||
valid, details = validate.validate_armada_documents(self.documents)
|
||||
self.assertFalse(valid)
|
||||
|
||||
def test_build_armada_manifest_with_missing_chart_grps_fails(self):
|
||||
"""Validate that attempting to build a manifest with missing
|
||||
chart groups fails.
|
||||
"""
|
||||
armada_manifest = manifest.Manifest(self.documents)
|
||||
self.documents[6]['data']['chart_groups'] = ['missing-chart-groups']
|
||||
self.assertRaises(exceptions.ManifestException,
|
||||
armada_manifest.build_armada_manifest)
|
||||
valid, details = validate.validate_armada_documents(self.documents)
|
||||
self.assertFalse(valid)
|
||||
|
@ -75,8 +75,12 @@ class OverrideTestCase(testtools.TestCase):
|
||||
self.assertNotEqual(original_documents, documents_copy)
|
||||
# since overrides done, these documents aren't same anymore
|
||||
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',
|
||||
ovr.documents[-1]['data']['release_prefix'])
|
||||
target_doc['data']['release_prefix'])
|
||||
|
||||
override = ('manifest:simple-armada:chart_groups='
|
||||
'blog-group3,blog-group4',)
|
||||
@ -283,8 +287,12 @@ class OverrideTestCase(testtools.TestCase):
|
||||
ovr = Override(documents, override)
|
||||
ovr.update_manifests()
|
||||
ovr_doc = ovr.find_manifest_document(doc_path)
|
||||
expect_doc = list(yaml.load_all(e.read()))[0]
|
||||
self.assertEqual(expect_doc, ovr_doc)
|
||||
target_docs = list(yaml.load_all(e.read()))
|
||||
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):
|
||||
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
|
||||
release: helm-toolkit
|
||||
namespace: helm-tookit
|
||||
install:
|
||||
no_hooks: false
|
||||
upgrade:
|
||||
no_hooks: false
|
||||
values: {}
|
||||
source:
|
||||
type: git
|
||||
|
@ -10,6 +10,7 @@ requests
|
||||
supermutes==0.2.5
|
||||
Paste>=2.0.3
|
||||
PasteDeploy>=1.5.2
|
||||
jsonschema>=2.6.0
|
||||
|
||||
# API
|
||||
falcon
|
||||
|
Loading…
Reference in New Issue
Block a user