Merge "Validate existence of "deployment-version" during create configdocs"
This commit is contained in:
commit
d479d6b969
@ -430,6 +430,10 @@ conf:
|
||||
# and validates.
|
||||
deployment_strategy_schema: shipyard/DeploymentStrategy/v1
|
||||
validations:
|
||||
# Control the severity of the deployment-version document validation
|
||||
# that Shipyard performs during create configdocs.
|
||||
# Possible values are Skip, Info, Warning, and Error
|
||||
deployment_version_create: Skip
|
||||
# Control the severity of the deployment-version document validation
|
||||
# that Shipyard performs during commit configdocs.
|
||||
# Possible values are Skip, Info, Warning, and Error
|
||||
|
@ -441,8 +441,18 @@
|
||||
# From shipyard_api
|
||||
#
|
||||
|
||||
# Control the severity of the deployment-version validation during create
|
||||
# configdocs. (string value)
|
||||
# Possible values:
|
||||
# Skip - Skip the validation altogether
|
||||
# Info - Print an Info level message if the validation fails
|
||||
# Warning - Print a Warning level message if the validation fails
|
||||
# Error - Return an error when the validation fails and prevent the configdocs
|
||||
# create from proceeding
|
||||
#deployment_version_create = Skip
|
||||
|
||||
# Control the severity of the deployment-version validation validation during
|
||||
# commit configdocs. (string value)
|
||||
# commit configdocs. (string value)
|
||||
# Possible values:
|
||||
# Skip - Skip the validation altogether
|
||||
# Info - Print an Info level message if the validation fails
|
||||
|
@ -441,8 +441,18 @@
|
||||
# From shipyard_api
|
||||
#
|
||||
|
||||
# Control the severity of the deployment-version validation during create
|
||||
# configdocs. (string value)
|
||||
# Possible values:
|
||||
# Skip - Skip the validation altogether
|
||||
# Info - Print an Info level message if the validation fails
|
||||
# Warning - Print a Warning level message if the validation fails
|
||||
# Error - Return an error when the validation fails and prevent the configdocs
|
||||
# create from proceeding
|
||||
#deployment_version_create = Skip
|
||||
|
||||
# Control the severity of the deployment-version validation validation during
|
||||
# commit configdocs. (string value)
|
||||
# commit configdocs. (string value)
|
||||
# Possible values:
|
||||
# Skip - Skip the validation altogether
|
||||
# Info - Print an Info level message if the validation fails
|
||||
|
@ -330,11 +330,26 @@ SECTIONS = [
|
||||
name='validations',
|
||||
title='Validation Configurations',
|
||||
options=[
|
||||
cfg.StrOpt(
|
||||
'deployment_version_create',
|
||||
default='Skip',
|
||||
help=('Control the severity of the deployment-version '
|
||||
'validation during create configdocs.'),
|
||||
ignore_case=True,
|
||||
choices=[('Skip', 'Skip the validation altogether'),
|
||||
('Info', 'Print an Info level message if the '
|
||||
'validation fails'),
|
||||
('Warning', 'Print a Warning level message if the '
|
||||
'validation fails'),
|
||||
('Error', 'Return an error when the validation fails '
|
||||
'and prevent the configdocs create from '
|
||||
'proceeding')]
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'deployment_version_commit',
|
||||
default='Skip',
|
||||
help=('Control the severity of the deployment-version '
|
||||
'validation validation during commit configdocs. '),
|
||||
'validation validation during commit configdocs.'),
|
||||
ignore_case=True,
|
||||
choices=[('Skip', 'Skip the validation altogether'),
|
||||
('Info', 'Print an Info level message if the '
|
||||
|
@ -24,7 +24,7 @@ from shipyard_airflow.control.api_lock import (api_lock, ApiLockType)
|
||||
from shipyard_airflow.control.base import BaseResource
|
||||
from shipyard_airflow.control.helpers import configdocs_helper
|
||||
from shipyard_airflow.control.helpers.configdocs_helper import (
|
||||
ConfigdocsHelper)
|
||||
ConfigdocsHelper, add_messages_to_validation_status)
|
||||
from shipyard_airflow.errors import ApiError
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -33,6 +33,8 @@ VERSION_VALUES = ['buffer',
|
||||
'committed',
|
||||
'last_site_action',
|
||||
'successful_site_action']
|
||||
DEPLOYMENT_DATA_DOC = {'name': CONF.document_info.deployment_version_name,
|
||||
'schema': CONF.document_info.deployment_version_schema}
|
||||
|
||||
|
||||
class ConfigDocsStatusResource(BaseResource):
|
||||
@ -155,17 +157,57 @@ class ConfigDocsResource(BaseResource):
|
||||
return helper.get_collection_docs(version, collection_id,
|
||||
cleartext_secrets)
|
||||
|
||||
def _validate_deployment_version(self, helper, document_data):
|
||||
"""
|
||||
Validate that the received documents include a deployment version doc.
|
||||
This function should only be called if needed, and will not do any
|
||||
checking to see if shipyard is configured to skip this check.
|
||||
Return True if the deployment version doc is present, False otherwise.
|
||||
"""
|
||||
LOG.info("Validating deployment data")
|
||||
LOG.debug("Searching for schema: %s and name: %s",
|
||||
DEPLOYMENT_DATA_DOC['schema'], DEPLOYMENT_DATA_DOC['name'])
|
||||
return helper.check_for_document(document_data,
|
||||
DEPLOYMENT_DATA_DOC['name'],
|
||||
DEPLOYMENT_DATA_DOC['schema'])
|
||||
|
||||
def post_collection(self,
|
||||
helper,
|
||||
collection_id,
|
||||
document_data,
|
||||
buffer_mode_param=None,
|
||||
empty_collection=False):
|
||||
"""
|
||||
Ingest the collection after checking preconditions
|
||||
"""
|
||||
"""Ingest the collection after checking preconditions"""
|
||||
extra_messages = {'warning': [], 'info': []}
|
||||
validation_status = None
|
||||
buffer_mode = ConfigdocsHelper.get_buffer_mode(buffer_mode_param)
|
||||
|
||||
# Validate that a deployment version document was provided, unless we
|
||||
# were told to skip this check
|
||||
ver_validation_cfg = CONF.validations.deployment_version_create.lower()
|
||||
if empty_collection or ver_validation_cfg == 'skip':
|
||||
LOG.debug('Skipping deployment version document validation')
|
||||
else:
|
||||
if not self._validate_deployment_version(helper, document_data):
|
||||
title = 'Deployment version document missing from collection'
|
||||
error_msg = ('Expected document to be present with schema: {} '
|
||||
'and name: {}').format(
|
||||
DEPLOYMENT_DATA_DOC['schema'],
|
||||
DEPLOYMENT_DATA_DOC['name'])
|
||||
|
||||
if ver_validation_cfg in ['info', 'warning']:
|
||||
extra_messages[ver_validation_cfg].append('{}. {}'.format(
|
||||
title, error_msg))
|
||||
else: # Error
|
||||
raise ApiError(
|
||||
title=title,
|
||||
description=('Collection rejected due to missing '
|
||||
'deployment data document'),
|
||||
status=falcon.HTTP_400,
|
||||
error_list=[{'message': error_msg}],
|
||||
retry=False,
|
||||
)
|
||||
|
||||
if helper.is_buffer_valid_for_bucket(collection_id, buffer_mode):
|
||||
buffer_revision = helper.add_collection(collection_id,
|
||||
document_data)
|
||||
@ -190,7 +232,8 @@ class ConfigDocsResource(BaseResource):
|
||||
retry=False,
|
||||
)
|
||||
else:
|
||||
return helper.get_deckhand_validation_status(buffer_revision)
|
||||
validation_status = helper.get_deckhand_validation_status(
|
||||
buffer_revision)
|
||||
else:
|
||||
raise ApiError(
|
||||
title='Invalid collection specified for buffer',
|
||||
@ -205,6 +248,13 @@ class ConfigDocsResource(BaseResource):
|
||||
retry=False,
|
||||
)
|
||||
|
||||
for level, messages in extra_messages.items():
|
||||
if len(messages):
|
||||
add_messages_to_validation_status(validation_status,
|
||||
messages,
|
||||
level)
|
||||
return validation_status
|
||||
|
||||
|
||||
class CommitConfigDocsResource(BaseResource):
|
||||
"""
|
||||
|
@ -19,6 +19,7 @@ bucket for Shipyard
|
||||
import enum
|
||||
import logging
|
||||
import threading
|
||||
import yaml
|
||||
|
||||
import falcon
|
||||
from oslo_config import cfg
|
||||
@ -57,25 +58,21 @@ ROLLBACK_COMMIT = 'rollback_commit'
|
||||
|
||||
|
||||
class BufferMode(enum.Enum):
|
||||
"""
|
||||
Enumeration of the valid values for BufferMode
|
||||
"""
|
||||
"""Enumeration of the valid values for BufferMode"""
|
||||
REJECTONCONTENTS = 'rejectoncontents'
|
||||
APPEND = 'append'
|
||||
REPLACE = 'replace'
|
||||
|
||||
|
||||
class ConfigdocsHelper(object):
|
||||
"""
|
||||
ConfigdocsHelper provides a layer to represent the buffer and committed
|
||||
"""ConfigdocsHelper provides a layer to represent the buffer and committed
|
||||
versions of design documents.
|
||||
A new configdocs_helper is intended to be used for each invocation of the
|
||||
service.
|
||||
"""
|
||||
|
||||
def __init__(self, context):
|
||||
"""
|
||||
Sets up this Configdocs helper with the supplied
|
||||
"""Sets up this Configdocs helper with the supplied
|
||||
request context
|
||||
"""
|
||||
self.deckhand = DeckhandClient(context.request_id,
|
||||
@ -125,9 +122,7 @@ class ConfigdocsHelper(object):
|
||||
return True
|
||||
|
||||
def is_collection_in_buffer(self, collection_id):
|
||||
"""
|
||||
Returns if the collection is represented in the buffer
|
||||
"""
|
||||
"""Returns if the collection is represented in the buffer"""
|
||||
if self.is_buffer_empty():
|
||||
return False
|
||||
|
||||
@ -152,8 +147,7 @@ class ConfigdocsHelper(object):
|
||||
retry=False, )
|
||||
|
||||
def is_buffer_valid_for_bucket(self, collection_id, buffermode):
|
||||
"""
|
||||
Indicates if the buffer as it currently is, may be written to
|
||||
"""Indicates if the buffer as it currently is, may be written to
|
||||
for the specified collection, based on the buffermode.
|
||||
"""
|
||||
# can always write if buffer is empty.
|
||||
@ -180,6 +174,52 @@ class ConfigdocsHelper(object):
|
||||
self.deckhand.rollback(committed_rev_id)
|
||||
return True
|
||||
|
||||
def parse_received_doc_data(self, document_data):
|
||||
"""Parse and return the document data shipyard receives
|
||||
document_data should be a "bytes" type of one or more yaml documents
|
||||
Return the parsed documents as a list. If bad YAML was provided log a
|
||||
warning and return an empty list
|
||||
"""
|
||||
try:
|
||||
yaml_doc_list = list(yaml.safe_load_all(document_data))
|
||||
LOG.debug('Loaded %s YAML documents from provided data',
|
||||
len(yaml_doc_list))
|
||||
except yaml.YAMLError as exc:
|
||||
yaml_doc_list = []
|
||||
LOG.warning(('Invalid YAML provided to Shipyard. Syntax error(s): '
|
||||
'{}').format(exc))
|
||||
|
||||
return yaml_doc_list
|
||||
|
||||
def get_doc_names_and_schemas(self, document_data):
|
||||
"""Given the document_data shipyard receives, return a list of tuples
|
||||
denoting each documents' name and schema (name, schema)
|
||||
"""
|
||||
parsed_docs = self.parse_received_doc_data(document_data)
|
||||
names_and_schemas = []
|
||||
|
||||
for doc in parsed_docs:
|
||||
try:
|
||||
schema = doc['schema']
|
||||
except (TypeError, KeyError):
|
||||
schema = ''
|
||||
LOG.warning('Document recevied with no schema')
|
||||
try:
|
||||
name = doc['metadata']['name']
|
||||
except (TypeError, KeyError):
|
||||
name = ''
|
||||
LOG.warning('Document recevied with no name')
|
||||
|
||||
names_and_schemas.append((name, schema))
|
||||
return names_and_schemas
|
||||
|
||||
def check_for_document(self, document_data, name, schema):
|
||||
"""Given the document data shipyard recevies, see if the given
|
||||
name/schmea combination exists in the list of documents
|
||||
Return True if the document exists, False otherwise
|
||||
"""
|
||||
return (name, schema) in self.get_doc_names_and_schemas(document_data)
|
||||
|
||||
def get_configdocs_status(self, versions=None):
|
||||
"""
|
||||
:param versions: A list of 2 versions. Defaults to buffer and
|
||||
@ -244,8 +284,7 @@ class ConfigdocsHelper(object):
|
||||
return configdocs_status
|
||||
|
||||
def _get_revision_dict(self):
|
||||
"""
|
||||
Returns a dictionary with values representing the revisions in
|
||||
"""Returns a dictionary with values representing the revisions in
|
||||
Deckhand that Shipyard cares about - committed, buffer, latest,
|
||||
last_site_action and successful_site_action, as well as a count
|
||||
of revisions.
|
||||
@ -326,7 +365,7 @@ class ConfigdocsHelper(object):
|
||||
return self.revision_dict
|
||||
|
||||
def _get_revision(self, target_revision):
|
||||
# Helper to drill down to the target revision
|
||||
"""Helper to drill down to the target revision"""
|
||||
return self._get_revision_dict().get(target_revision)
|
||||
|
||||
def get_revision_id(self, target_revision):
|
||||
@ -336,8 +375,7 @@ class ConfigdocsHelper(object):
|
||||
|
||||
def get_collection_docs(self, version, collection_id,
|
||||
cleartext_secrets=False):
|
||||
"""
|
||||
Returns the requested collection of docs based on the version
|
||||
"""Returns the requested collection of docs based on the version
|
||||
specifier. The default is set as buffer.
|
||||
"""
|
||||
LOG.info('Retrieving collection %s from %s', collection_id, version)
|
||||
@ -349,8 +387,7 @@ class ConfigdocsHelper(object):
|
||||
cleartext_secrets=cleartext_secrets)
|
||||
|
||||
def _get_doc_from_buffer(self, collection_id, cleartext_secrets=False):
|
||||
"""
|
||||
Returns the collection if it exists in the buffer.
|
||||
"""Returns the collection if it exists in the buffer.
|
||||
If the buffer contains the collection, the latest
|
||||
representation is what we want.
|
||||
"""
|
||||
@ -373,8 +410,7 @@ class ConfigdocsHelper(object):
|
||||
|
||||
def _get_target_docs(self, collection_id, target_rev,
|
||||
cleartext_secrets=False):
|
||||
"""
|
||||
Returns the collection if it exists as committed, last_site_action
|
||||
"""Returns the collection if it exists as committed, last_site_action
|
||||
or successful_site_action.
|
||||
"""
|
||||
revision_id = self.get_revision_id(target_rev)
|
||||
@ -392,8 +428,7 @@ class ConfigdocsHelper(object):
|
||||
retry=False)
|
||||
|
||||
def get_rendered_configdocs(self, version=BUFFER, cleartext_secrets=False):
|
||||
"""
|
||||
Returns the rendered configuration documents for the specified
|
||||
"""Returns the rendered configuration documents for the specified
|
||||
revision (by name BUFFER, COMMITTED, LAST_SITE_ACTION,
|
||||
SUCCESSFUL_SITE_ACTION)
|
||||
"""
|
||||
@ -519,7 +554,7 @@ class ConfigdocsHelper(object):
|
||||
return _format_validations_to_status(resp_msgs, error_count)
|
||||
|
||||
def _get_shipyard_validations(self, revision_id):
|
||||
# Run Shipyard's own validations.
|
||||
"""Run Shipyard's own validations."""
|
||||
try:
|
||||
sy_val_mgr = DocumentValidationManager(
|
||||
service_clients.deckhand_client(),
|
||||
@ -564,9 +599,7 @@ class ConfigdocsHelper(object):
|
||||
return resp_msgs
|
||||
|
||||
def tag_buffer(self, tag):
|
||||
"""
|
||||
Convenience method to tag the buffer version.
|
||||
"""
|
||||
"""Convenience method to tag the buffer version."""
|
||||
buffer_rev_id = self.get_revision_id(BUFFER)
|
||||
if buffer_rev_id is None:
|
||||
raise AppError(
|
||||
@ -578,14 +611,11 @@ class ConfigdocsHelper(object):
|
||||
self.tag_revision(buffer_rev_id, tag)
|
||||
|
||||
def tag_revision(self, revision_id, tag):
|
||||
"""
|
||||
Tags the specified revision with the specified tag
|
||||
"""
|
||||
"""Tags the specified revision with the specified tag"""
|
||||
self.deckhand.tag_revision(revision_id=revision_id, tag=tag)
|
||||
|
||||
def add_collection(self, collection_id, document_string):
|
||||
"""
|
||||
Triggers a call to Deckhand to add a collection(bucket)
|
||||
"""Triggers a call to Deckhand to add a collection(bucket)
|
||||
Documents are assumed to be a string input, not a
|
||||
collection.
|
||||
Returns the id of the buffer version.
|
||||
@ -653,7 +683,7 @@ class ConfigdocsHelper(object):
|
||||
return False
|
||||
|
||||
def _get_ordered_versions(self, versions=None):
|
||||
"""returns a list of ordered versions"""
|
||||
"""Returns a list of ordered versions"""
|
||||
|
||||
# Default ordering
|
||||
def_order = [SUCCESSFUL_SITE_ACTION,
|
||||
@ -744,8 +774,30 @@ class ConfigdocsHelper(object):
|
||||
# functions for module.
|
||||
#
|
||||
|
||||
def add_messages_to_validation_status(status, msgs, level):
|
||||
"""Given a status retrieved from _format_validations_to_status and a list
|
||||
of messages at a given level (Error, Warning, Info), add messages to the
|
||||
status
|
||||
"""
|
||||
code = falcon.HTTP_200
|
||||
if str(level).lower() == 'error':
|
||||
code = falcon.HTTP_400
|
||||
status['status'] = 'Failure'
|
||||
status['message'] = 'Validations failed'
|
||||
status['code'] = code
|
||||
status['details']['errorCount'] += len(msgs)
|
||||
|
||||
formatted_messages = []
|
||||
for msg in msgs:
|
||||
formatted_messages.append({'code': code,
|
||||
'message': msg,
|
||||
'status': str(level).capitalize(),
|
||||
'level': str(level).lower()})
|
||||
status['details']['messageList'] += formatted_messages
|
||||
|
||||
|
||||
def _get_validation_endpoints():
|
||||
# returns the list of validation endpoint supported
|
||||
"""Returns the list of validation endpoint supported"""
|
||||
val_ep = '{}/validatedesign'
|
||||
return [
|
||||
{
|
||||
@ -764,7 +816,7 @@ def _get_validation_endpoints():
|
||||
|
||||
|
||||
def _get_validation_threads(validation_endpoints, ctx, design_ref):
|
||||
# create a list of validation threads from the endpoints
|
||||
"""Create a list of validation threads from the endpoints"""
|
||||
validation_threads = []
|
||||
for endpoint in validation_endpoints:
|
||||
# create a holder for things we need back from the threads
|
||||
@ -798,7 +850,7 @@ def _get_validation_threads(validation_endpoints, ctx, design_ref):
|
||||
def _get_validations_for_component(url, design_reference, response,
|
||||
exception, context_marker, thread_name,
|
||||
**kwargs):
|
||||
# Invoke the POST for validation
|
||||
"""Invoke the POST for validation"""
|
||||
try:
|
||||
headers = {
|
||||
'X-Context-Marker': context_marker,
|
||||
@ -840,11 +892,12 @@ def _get_validations_for_component(url, design_reference, response,
|
||||
|
||||
|
||||
def _generate_dh_val_msg(msg, dh_result_name):
|
||||
# Maps a deckhand validation response to a ValidationMessage.
|
||||
# Result name is used if the msg doesn't specify a name field.
|
||||
# Deckhand may provide the following fields:
|
||||
# 'validation_schema', 'schema_path', 'name', 'schema', 'path',
|
||||
# 'error_section', 'message'
|
||||
"""Maps a deckhand validation response to a ValidationMessage.
|
||||
Result name is used if the msg doesn't specify a name field.
|
||||
Deckhand may provide the following fields:
|
||||
'validation_schema', 'schema_path', 'name', 'schema', 'path',
|
||||
'error_section', 'message'
|
||||
"""
|
||||
not_spec = 'not specified'
|
||||
if 'diagnostic' not in msg:
|
||||
# format path, error_section, validation_schema, and schema_path
|
||||
@ -871,12 +924,13 @@ def _generate_dh_val_msg(msg, dh_result_name):
|
||||
|
||||
|
||||
def _generate_validation_message(msg, **kwargs):
|
||||
# Special note about kwargs: the values provided via kwargs are used
|
||||
# as defaults, not overrides. Values in the msg will take precedence.
|
||||
#
|
||||
# Using a compatible message, transform it into a ValidationMessage.
|
||||
# By combining it with the default values passed via kwargs. The values
|
||||
# used from kwargs match the fields listed below.
|
||||
"""Special note about kwargs: the values provided via kwargs are used
|
||||
as defaults, not overrides. Values in the msg will take precedence.
|
||||
|
||||
Using a compatible message, transform it into a ValidationMessage.
|
||||
By combining it with the default values passed via kwargs. The values
|
||||
used from kwargs match the fields listed below.
|
||||
"""
|
||||
|
||||
fields = ['message', 'error', 'name', 'documents', 'level', 'diagnostic',
|
||||
'source']
|
||||
@ -894,7 +948,7 @@ def _generate_validation_message(msg, **kwargs):
|
||||
|
||||
|
||||
def _error_to_level(error):
|
||||
"""Convert a boolean error field to 'Error' or 'Info' """
|
||||
"""Convert a boolean error field to 'Error' or 'Info'"""
|
||||
if error:
|
||||
return 'Error'
|
||||
else:
|
||||
@ -902,9 +956,9 @@ def _error_to_level(error):
|
||||
|
||||
|
||||
def _format_validations_to_status(val_msgs, error_count):
|
||||
# Using a list of validation messages and an error count,
|
||||
# formulates and returns a status response dict
|
||||
|
||||
"""Using a list of validation messages and an error count,
|
||||
formulates and returns a status response dict
|
||||
"""
|
||||
status = 'Success'
|
||||
message = 'Validations succeeded'
|
||||
code = falcon.HTTP_200
|
||||
|
@ -16,12 +16,14 @@ import json
|
||||
from unittest import mock
|
||||
from unittest.mock import ANY, patch
|
||||
|
||||
from oslo_config import cfg
|
||||
import pytest
|
||||
|
||||
from shipyard_airflow.control.base import ShipyardRequestContext
|
||||
from shipyard_airflow.control.configdocs.configdocs_api import (
|
||||
CommitConfigDocsResource,
|
||||
ConfigDocsResource
|
||||
ConfigDocsResource,
|
||||
DEPLOYMENT_DATA_DOC
|
||||
)
|
||||
from shipyard_airflow.control.helpers import configdocs_helper
|
||||
from shipyard_airflow.control.helpers.configdocs_helper import \
|
||||
@ -31,6 +33,7 @@ from shipyard_airflow.errors import ApiError
|
||||
from tests.unit.control import common
|
||||
|
||||
CTX = ShipyardRequestContext()
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestConfigDocsStatusResource():
|
||||
@ -88,6 +91,21 @@ class TestConfigDocsResource():
|
||||
# should not raise an exception.
|
||||
assert False
|
||||
|
||||
def test__validate_deployment_version(self):
|
||||
"""Test of the validate deployment version function
|
||||
"""
|
||||
helper = None
|
||||
with patch.object(
|
||||
ConfigdocsHelper, 'check_for_document') as mock_method:
|
||||
cdr = ConfigDocsResource()
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
cdr._validate_deployment_version(helper, 'oranges')
|
||||
|
||||
mock_method.assert_called_once_with('oranges',
|
||||
DEPLOYMENT_DATA_DOC['name'],
|
||||
DEPLOYMENT_DATA_DOC['schema'])
|
||||
|
||||
|
||||
def test_get_collection(self):
|
||||
helper = None
|
||||
with patch.object(
|
||||
@ -100,21 +118,22 @@ class TestConfigDocsResource():
|
||||
|
||||
@patch.object(ConfigdocsHelper, 'is_collection_in_buffer',
|
||||
lambda x, y: True)
|
||||
@patch.object(ConfigdocsHelper, 'is_buffer_valid_for_bucket',
|
||||
lambda x, y, z: True)
|
||||
@patch.object(ConfigdocsHelper, 'get_deckhand_validation_status',
|
||||
lambda x, y: configdocs_helper.
|
||||
_format_validations_to_status([], 0))
|
||||
def test_post_collection(self):
|
||||
"""
|
||||
Tests the post collection method of the ConfigdocsResource
|
||||
"""
|
||||
CONF.set_override('deployment_version_create', 'Skip', 'validations')
|
||||
helper = None
|
||||
collection_id = 'trees'
|
||||
document_data = 'lots of info'
|
||||
with patch.object(ConfigdocsHelper, 'add_collection') as mock_method:
|
||||
cdr = ConfigDocsResource()
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
helper.is_buffer_valid_for_bucket = lambda a, b: True
|
||||
helper.get_deckhand_validation_status = (
|
||||
lambda a: configdocs_helper._format_validations_to_status([],
|
||||
0)
|
||||
)
|
||||
cdr.post_collection(helper=helper,
|
||||
collection_id=collection_id,
|
||||
document_data=document_data)
|
||||
@ -125,10 +144,14 @@ class TestConfigDocsResource():
|
||||
lambda x, y: True)
|
||||
@patch.object(ConfigdocsHelper, 'is_buffer_valid_for_bucket',
|
||||
lambda x, y, z: False)
|
||||
@patch.object(ConfigdocsHelper, 'get_deckhand_validation_status',
|
||||
lambda x, y: configdocs_helper.
|
||||
_format_validations_to_status([], 0))
|
||||
def test_post_collection_not_valid_for_buffer(self):
|
||||
"""
|
||||
Tests the post collection method of the ConfigdocsResource
|
||||
"""
|
||||
CONF.set_override('deployment_version_create', 'Skip', 'validations')
|
||||
helper = None
|
||||
collection_id = 'trees'
|
||||
document_data = 'lots of info'
|
||||
@ -136,10 +159,6 @@ class TestConfigDocsResource():
|
||||
cdr = ConfigDocsResource()
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
# not valid for bucket
|
||||
helper.get_deckhand_validation_status = (
|
||||
lambda a: configdocs_helper._format_validations_to_status([],
|
||||
0)
|
||||
)
|
||||
cdr.post_collection(helper=helper,
|
||||
collection_id=collection_id,
|
||||
document_data=document_data)
|
||||
@ -149,28 +168,134 @@ class TestConfigDocsResource():
|
||||
lambda x, y: False)
|
||||
@patch.object(ConfigdocsHelper, 'is_buffer_valid_for_bucket',
|
||||
lambda x, y, z: True)
|
||||
@patch.object(ConfigdocsHelper, 'get_deckhand_validation_status',
|
||||
lambda x, y: configdocs_helper.
|
||||
_format_validations_to_status([], 0))
|
||||
def test_post_collection_not_added(self):
|
||||
"""
|
||||
Tests the post collection method of the ConfigdocsResource
|
||||
"""
|
||||
CONF.set_override('deployment_version_create', 'Skip', 'validations')
|
||||
helper = None
|
||||
collection_id = 'trees'
|
||||
document_data = 'lots of info'
|
||||
with patch.object(ConfigdocsHelper, 'add_collection') as mock_method:
|
||||
cdr = ConfigDocsResource()
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
helper.get_deckhand_validation_status = (
|
||||
lambda a: configdocs_helper._format_validations_to_status([],
|
||||
0)
|
||||
)
|
||||
with pytest.raises(ApiError) as apie:
|
||||
cdr.post_collection(helper=helper,
|
||||
collection_id=collection_id,
|
||||
document_data=document_data)
|
||||
|
||||
assert apie.value.status == '400 Bad Request'
|
||||
assert apie.value.title == ('Collection {} not added to Shipyard '
|
||||
'buffer'.format(collection_id))
|
||||
mock_method.assert_called_once_with(collection_id, document_data)
|
||||
|
||||
def test_post_collection_deployment_version_missing_error(self):
|
||||
"""
|
||||
Tests the post collection method of the ConfigdocsResource
|
||||
"""
|
||||
# Make sure that the configuration value is handled case-insensitively
|
||||
CONF.set_override('deployment_version_create', 'eRRoR', 'validations')
|
||||
helper = None
|
||||
collection_id = 'trees'
|
||||
document_data = 'lots of info'
|
||||
cdr = ConfigDocsResource()
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
with pytest.raises(ApiError) as apie:
|
||||
cdr.post_collection(helper=helper,
|
||||
collection_id=collection_id,
|
||||
document_data=document_data)
|
||||
|
||||
assert apie.value.status == '400 Bad Request'
|
||||
assert apie.value.title == ('Deployment version document missing from '
|
||||
'collection')
|
||||
|
||||
@patch.object(ConfigdocsHelper, 'is_collection_in_buffer',
|
||||
lambda x, y: True)
|
||||
@patch.object(ConfigdocsHelper, 'is_buffer_valid_for_bucket',
|
||||
lambda x, y, z: True)
|
||||
@patch.object(ConfigdocsHelper, 'get_deckhand_validation_status',
|
||||
lambda x, y: configdocs_helper.
|
||||
_format_validations_to_status([], 0))
|
||||
def test_post_collection_deployment_version_missing_warning(self):
|
||||
"""
|
||||
Tests the post collection method of the ConfigdocsResource
|
||||
"""
|
||||
# Make sure that the configuration value is handled case-insensitively
|
||||
CONF.set_override('deployment_version_create', 'warnING',
|
||||
'validations')
|
||||
helper = None
|
||||
collection_id = 'trees'
|
||||
document_data = 'lots of info'
|
||||
with patch.object(ConfigdocsHelper, 'add_collection') as mock_method:
|
||||
cdr = ConfigDocsResource()
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
status = cdr.post_collection(helper=helper,
|
||||
collection_id=collection_id,
|
||||
document_data=document_data)
|
||||
assert len(status['details']['messageList']) == 1
|
||||
assert status['details']['messageList'][0]['level'] == 'warning'
|
||||
|
||||
mock_method.assert_called_once_with(collection_id, document_data)
|
||||
|
||||
@patch.object(ConfigdocsHelper, 'is_collection_in_buffer',
|
||||
lambda x, y: True)
|
||||
@patch.object(ConfigdocsHelper, 'is_buffer_valid_for_bucket',
|
||||
lambda x, y, z: True)
|
||||
@patch.object(ConfigdocsHelper, 'get_deckhand_validation_status',
|
||||
lambda x, y: configdocs_helper.
|
||||
_format_validations_to_status([], 0))
|
||||
def test_post_collection_deployment_version_missing_info(self):
|
||||
"""
|
||||
Tests the post collection method of the ConfigdocsResource
|
||||
"""
|
||||
# Make sure that the configuration value is handled case-insensitively
|
||||
CONF.set_override('deployment_version_create', 'iNfO', 'validations')
|
||||
helper = None
|
||||
collection_id = 'trees'
|
||||
document_data = 'lots of info'
|
||||
with patch.object(ConfigdocsHelper, 'add_collection') as mock_method:
|
||||
cdr = ConfigDocsResource()
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
status = cdr.post_collection(helper=helper,
|
||||
collection_id=collection_id,
|
||||
document_data=document_data)
|
||||
assert len(status['details']['messageList']) == 1
|
||||
assert status['details']['messageList'][0]['level'] == 'info'
|
||||
|
||||
mock_method.assert_called_once_with(collection_id, document_data)
|
||||
|
||||
@patch.object(ConfigdocsHelper, 'is_collection_in_buffer',
|
||||
lambda x, y: True)
|
||||
@patch.object(ConfigdocsHelper, 'is_buffer_valid_for_bucket',
|
||||
lambda x, y, z: True)
|
||||
@patch.object(ConfigdocsHelper, 'get_deckhand_validation_status',
|
||||
lambda x, y: configdocs_helper.
|
||||
_format_validations_to_status([], 0))
|
||||
def test_post_collection_deployment_version_missing_skip(self):
|
||||
"""
|
||||
Tests the post collection method of the ConfigdocsResource
|
||||
"""
|
||||
# Make sure that the configuration value is handled case-insensitively
|
||||
CONF.set_override('deployment_version_create', 'SKip', 'validations')
|
||||
helper = None
|
||||
collection_id = 'trees'
|
||||
document_data = 'lots of info'
|
||||
with patch.object(ConfigdocsHelper, 'add_collection') as mock_method0,\
|
||||
patch.object(ConfigDocsResource,
|
||||
'_validate_deployment_version') as mock_method1:
|
||||
cdr = ConfigDocsResource()
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
status = cdr.post_collection(helper=helper,
|
||||
collection_id=collection_id,
|
||||
document_data=document_data)
|
||||
assert len(status['details']['messageList']) == 0
|
||||
|
||||
mock_method0.assert_called_once_with(collection_id, document_data)
|
||||
mock_method1.assert_not_called
|
||||
|
||||
|
||||
class TestCommitConfigDocsResource():
|
||||
@mock.patch.object(ApiLock, 'release')
|
||||
|
@ -20,11 +20,12 @@ import pytest
|
||||
import responses
|
||||
import yaml
|
||||
|
||||
import falcon
|
||||
from .fake_response import FakeResponse
|
||||
from shipyard_airflow.control.base import ShipyardRequestContext
|
||||
from shipyard_airflow.control.helpers import configdocs_helper
|
||||
from shipyard_airflow.control.helpers.configdocs_helper import (
|
||||
BufferMode, ConfigdocsHelper)
|
||||
BufferMode, ConfigdocsHelper, add_messages_to_validation_status)
|
||||
from shipyard_airflow.control.helpers.deckhand_client import (
|
||||
DeckhandClient, DeckhandResponseError,
|
||||
NoRevisionsExistError)
|
||||
@ -1057,3 +1058,143 @@ def test_check_intermediate_commit():
|
||||
assert not helper_no_revs.check_intermediate_commit()
|
||||
assert not helper_no_intermidiate_commits.check_intermediate_commit()
|
||||
assert helper_with_intermidiate_commits.check_intermediate_commit()
|
||||
|
||||
def test_parse_received_doc_data():
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
yaml = """
|
||||
---
|
||||
document1:
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
---
|
||||
document2:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
...
|
||||
"""
|
||||
parsed = helper.parse_received_doc_data(yaml)
|
||||
assert type(parsed) == list
|
||||
assert len(parsed) == 2
|
||||
|
||||
def test_parse_received_doc_data_bad_yaml():
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
yaml = "--- asdfjklsemicolon:"
|
||||
parsed = helper.parse_received_doc_data(yaml)
|
||||
assert type(parsed) == list
|
||||
assert len(parsed) == 0
|
||||
|
||||
def test_get_doc_names_and_schemas():
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
yaml = """
|
||||
---
|
||||
doc_that_does_not_have_a: name or schema
|
||||
---
|
||||
schema: mycool/Document/v1
|
||||
metadata:
|
||||
schema: metadata/Document/v1
|
||||
name: cool-doc
|
||||
storagePolicy: cleartext
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: global
|
||||
data:
|
||||
hello world
|
||||
---
|
||||
schema: notascool/Document/v1
|
||||
metadata:
|
||||
schema: metadata/Document/v1
|
||||
name: average-document
|
||||
storagePolicy: cleartext
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: global
|
||||
data:
|
||||
goodbye space
|
||||
...
|
||||
"""
|
||||
names_and_schemas = helper.get_doc_names_and_schemas(yaml)
|
||||
assert type(names_and_schemas) == list
|
||||
assert len(names_and_schemas) == 3
|
||||
assert type(names_and_schemas[0]) == tuple
|
||||
assert names_and_schemas[0][0] == ''
|
||||
assert names_and_schemas[0][1] == ''
|
||||
assert names_and_schemas[1][0] == 'cool-doc'
|
||||
assert names_and_schemas[1][1] == 'mycool/Document/v1'
|
||||
assert names_and_schemas[2][0] == 'average-document'
|
||||
assert names_and_schemas[2][1] == 'notascool/Document/v1'
|
||||
|
||||
def test_check_for_document():
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
yaml = """
|
||||
---
|
||||
schema: mycool/Document/v1
|
||||
metadata:
|
||||
schema: metadata/Document/v1
|
||||
name: cool-doc
|
||||
storagePolicy: cleartext
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: global
|
||||
data:
|
||||
hello world
|
||||
...
|
||||
"""
|
||||
assert helper.check_for_document(yaml, 'cool-doc', 'mycool/Document/v1')
|
||||
assert not helper.check_for_document(yaml, 'cool-doc', 'nope')
|
||||
assert not helper.check_for_document(yaml, 'nope', 'mycool/Document/v1')
|
||||
assert not helper.check_for_document(yaml, 'nope', 'nope')
|
||||
|
||||
def test_add_messages_to_validation_status():
|
||||
helper = ConfigdocsHelper(CTX)
|
||||
status = {
|
||||
"kind": "Status",
|
||||
"apiVersion": "v1.0",
|
||||
"metadata": {},
|
||||
"status": "Success",
|
||||
"message": "Validations succeeded",
|
||||
"reason": "Validation",
|
||||
"details": {
|
||||
"errorCount": 0,
|
||||
"messageList": [],
|
||||
},
|
||||
"code": falcon.HTTP_200
|
||||
}
|
||||
info_messages = ['message 1', 'message 2']
|
||||
warn_messages = ['message 3']
|
||||
error_messages = ['message 4', 'message 5']
|
||||
|
||||
add_messages_to_validation_status(status, info_messages, 'info')
|
||||
assert status['details']['errorCount'] == 0
|
||||
assert len(status['details']['messageList']) == 2
|
||||
assert type(status['details']['messageList'][0]) == dict
|
||||
assert status['details']['messageList'][0]['code'] == falcon.HTTP_200
|
||||
assert status['details']['messageList'][0]['message'] == 'message 1'
|
||||
assert status['details']['messageList'][0]['status'] == 'Info'
|
||||
assert status['details']['messageList'][0]['level'] == 'info'
|
||||
assert status["status"] == "Success"
|
||||
assert status["message"] == "Validations succeeded"
|
||||
assert status["code"] == falcon.HTTP_200
|
||||
|
||||
add_messages_to_validation_status(status, warn_messages, 'warning')
|
||||
assert status['details']['errorCount'] == 0
|
||||
assert len(status['details']['messageList']) == 3
|
||||
assert status['details']['messageList'][2]['code'] == falcon.HTTP_200
|
||||
assert status['details']['messageList'][2]['message'] == 'message 3'
|
||||
assert status['details']['messageList'][2]['status'] == 'Warning'
|
||||
assert status['details']['messageList'][2]['level'] == 'warning'
|
||||
assert status["status"] == "Success"
|
||||
assert status["message"] == "Validations succeeded"
|
||||
assert status["code"] == falcon.HTTP_200
|
||||
|
||||
add_messages_to_validation_status(status, error_messages, 'error')
|
||||
assert status['details']['errorCount'] == 2
|
||||
assert len(status['details']['messageList']) == 5
|
||||
assert status['details']['messageList'][3]['code'] == falcon.HTTP_400
|
||||
assert status['details']['messageList'][3]['message'] == 'message 4'
|
||||
assert status['details']['messageList'][3]['status'] == 'Error'
|
||||
assert status['details']['messageList'][3]['level'] == 'error'
|
||||
assert status["status"] == "Failure"
|
||||
assert status["message"] == "Validations failed"
|
||||
assert status["code"] == falcon.HTTP_400
|
@ -178,11 +178,10 @@ def _format_basic_message(message):
|
||||
|
||||
Returns a single string with embedded newlines
|
||||
"""
|
||||
level = str(message.get('level', 'Info')).capitalize()
|
||||
if message.get('error', False):
|
||||
resp = '\n- Error: {}'.format(message.get('message'))
|
||||
else:
|
||||
resp = '\n- Info: {}'.format(message.get('message'))
|
||||
return resp
|
||||
level = 'Error' # Force showing "Error"
|
||||
return '\n- {}: {}'.format(level, message.get('message'))
|
||||
|
||||
|
||||
def raw_format_response_handler(response):
|
||||
|
@ -52,4 +52,5 @@ deployment_strategy_schema = shipyard/DeploymentStrategy/v1
|
||||
deployment_version_name = deployment-version
|
||||
deployment_version_schema = pegleg/DeploymentData/v1
|
||||
[validations]
|
||||
deployment_version_create=Skip
|
||||
deployment_version_commit=Skip
|
Loading…
x
Reference in New Issue
Block a user