Fix Promenade: Introduce flag to only warn on missing sub source

This PS introduces a flag to only warn on missing substitution
sources because right now Promenade is failing on that. However,
a PS will also have to be added to Promenade to set the new flag
-- `fail_on_missing_sub_src` -- to False during genesis.

Change-Id: I462f721c41e23d2e5e3e698c0bd452b6764d51eb
This commit is contained in:
Felipe Monteiro 2018-02-21 21:40:19 +00:00 committed by Tin Lam
parent 3d278a2b08
commit 87d7f94134
3 changed files with 52 additions and 8 deletions

View File

@ -265,7 +265,8 @@ class DocumentLayering(object):
details='The following pre-validation errors occurred ' details='The following pre-validation errors occurred '
'(schema, name, error): %s.' % val_errors) '(schema, name, error): %s.' % val_errors)
def __init__(self, documents, substitution_sources=None, validate=True): def __init__(self, documents, substitution_sources=None, validate=True,
fail_on_missing_sub_src=True):
"""Contructor for ``DocumentLayering``. """Contructor for ``DocumentLayering``.
:param layering_policy: The document with schema :param layering_policy: The document with schema
@ -280,6 +281,9 @@ class DocumentLayering(object):
:param validate: Whether to pre-validate documents using built-in :param validate: Whether to pre-validate documents using built-in
schema validation. Default is True. schema validation. Default is True.
:type validate: bool :type validate: bool
:param fail_on_missing_sub_src: Whether to fail on a missing
substitution source. Default is True.
:type fail_on_missing_sub_src: bool
:raises LayeringPolicyNotFound: If no LayeringPolicy was found among :raises LayeringPolicyNotFound: If no LayeringPolicy was found among
list of ``documents``. list of ``documents``.
@ -349,7 +353,8 @@ class DocumentLayering(object):
self._calc_all_document_children() self._calc_all_document_children()
self._substitution_sources = substitution_sources or [] self._substitution_sources = substitution_sources or []
self.secrets_substitution = secrets_manager.SecretsSubstitution( self.secrets_substitution = secrets_manager.SecretsSubstitution(
self._substitution_sources) self._substitution_sources,
fail_on_missing_sub_src=fail_on_missing_sub_src)
del self._documents_by_layer del self._documents_by_layer
del self._documents_by_labels del self._documents_by_labels

View File

@ -105,7 +105,8 @@ class SecretsManager(object):
class SecretsSubstitution(object): class SecretsSubstitution(object):
"""Class for document substitution logic for YAML files.""" """Class for document substitution logic for YAML files."""
def __init__(self, substitution_sources=None): def __init__(self, substitution_sources=None,
fail_on_missing_sub_src=True):
"""SecretSubstitution constructor. """SecretSubstitution constructor.
This class will automatically detect documents that require This class will automatically detect documents that require
@ -115,9 +116,12 @@ class SecretsSubstitution(object):
:param substitution_sources: List of documents that are potential :param substitution_sources: List of documents that are potential
sources for substitution. Should only include concrete documents. sources for substitution. Should only include concrete documents.
:type substitution_sources: List[dict] :type substitution_sources: List[dict]
:param bool fail_on_missing_sub_src: Whether to fail on a missing
substitution source. Default is True.
""" """
self._substitution_sources = {} self._substitution_sources = {}
self._fail_on_missing_sub_src = fail_on_missing_sub_src
for document in substitution_sources: for document in substitution_sources:
if not isinstance(document, document_wrapper.DocumentDict): if not isinstance(document, document_wrapper.DocumentDict):
@ -175,11 +179,15 @@ class SecretsSubstitution(object):
message = ('Could not find substitution source document ' message = ('Could not find substitution source document '
'[%s] %s among the provided ' '[%s] %s among the provided '
'`substitution_sources`.', src_schema, src_name) '`substitution_sources`.', src_schema, src_name)
LOG.error(message) if self._fail_on_missing_sub_src:
raise errors.SubstitutionSourceNotFound( LOG.error(message)
src_schema=src_schema, src_name=src_name, raise errors.SubstitutionSourceNotFound(
document_schema=document.schema, src_schema=src_schema, src_name=src_name,
document_name=document.name) document_schema=document.schema,
document_name=document.name)
else:
LOG.warning(message)
continue
# If the data is a dictionary, retrieve the nested secret # If the data is a dictionary, retrieve the nested secret
# via jsonpath_parse, else the secret is the primitive/string # via jsonpath_parse, else the secret is the primitive/string

View File

@ -14,7 +14,10 @@
import yaml import yaml
import mock
from deckhand.engine import layering from deckhand.engine import layering
from deckhand.engine import secrets_manager
from deckhand import errors from deckhand import errors
from deckhand import factories from deckhand import factories
from deckhand.tests.unit import base as test_base from deckhand.tests.unit import base as test_base
@ -88,6 +91,34 @@ class TestDocumentLayering(test_base.DeckhandTestCase):
self.assertEmpty(global_docs) self.assertEmpty(global_docs)
class TestDocumentLayeringScenarios(TestDocumentLayering):
@mock.patch.object(secrets_manager, 'LOG', autospec=True)
def test_layering_with_missing_substitution_source_log_warning(self,
m_log):
"""Validate that a missing substitution source document fails."""
mapping = {
"_SITE_SUBSTITUTIONS_1_": [{
"dest": {
"path": ".c"
},
"src": {
"schema": "example/Kind/v1",
"name": "nowhere-to-be-found",
"path": "."
}
}]
}
doc_factory = factories.DocumentFactory(2, [1, 1])
documents = doc_factory.gen_test(mapping, site_abstract=False)
self._test_layering(documents, site_expected={},
fail_on_missing_sub_src=False)
self.assertTrue(m_log.warning.called)
self.assertRegex(m_log.warning.mock_calls[0][1][0][0],
r'Could not find substitution source document .*')
class TestDocumentLayering2Layers(TestDocumentLayering): class TestDocumentLayering2Layers(TestDocumentLayering):
def test_layering_default_scenario(self): def test_layering_default_scenario(self):