diff --git a/deckhand/engine/secrets_manager.py b/deckhand/engine/secrets_manager.py index 85a624a9..fa3079a3 100644 --- a/deckhand/engine/secrets_manager.py +++ b/deckhand/engine/secrets_manager.py @@ -383,40 +383,52 @@ class SecretsSubstitution(object): src_secret = self.get_encrypted_data(src_secret, src_doc, document) - dest_path = sub['dest']['path'] - dest_pattern = sub['dest'].get('pattern', None) + if not isinstance(sub['dest'], list): + dest_array = [sub['dest']] + else: + dest_array = sub['dest'] - LOG.debug('Substituting from schema=%s layer=%s name=%s ' - 'src_path=%s into dest_path=%s, dest_pattern=%s', - src_schema, src_doc.layer, src_name, src_path, - dest_path, dest_pattern) - try: - exc_message = '' - substituted_data = utils.jsonpath_replace( - document['data'], src_secret, dest_path, dest_pattern) - if (isinstance(document['data'], dict) - and isinstance(substituted_data, dict)): - document['data'].update(substituted_data) - elif substituted_data: - document['data'] = substituted_data - else: - exc_message = ( - 'Failed to create JSON path "%s" in the ' - 'destination document [%s, %s] %s. No data was ' - 'substituted.' % (dest_path, document.schema, - document.layer, document.name)) - except Exception as e: - LOG.error('Unexpected exception occurred while attempting ' - 'substitution using source document [%s, %s] %s ' - 'referenced in [%s, %s] %s. Details: %s', - src_schema, src_name, src_doc.layer, - document.schema, document.layer, document.name, - six.text_type(e)) - exc_message = six.text_type(e) - finally: - if exc_message: - self._handle_unknown_substitution_exc( - exc_message, src_doc, document) + for each_dest_path in dest_array: + dest_path = each_dest_path['path'] + dest_pattern = each_dest_path.get('pattern', None) + + LOG.debug('Substituting from schema=%s layer=%s name=%s ' + 'src_path=%s into dest_path=%s, dest_pattern=%s', + src_schema, src_doc.layer, src_name, src_path, + dest_path, dest_pattern) + + try: + exc_message = '' + substituted_data = utils.jsonpath_replace( + document['data'], src_secret, + dest_path, dest_pattern) + if (isinstance(document['data'], dict) + and isinstance(substituted_data, dict)): + document['data'].update(substituted_data) + elif substituted_data: + document['data'] = substituted_data + else: + exc_message = ( + 'Failed to create JSON path "%s" in the ' + 'destination document [%s, %s] %s. ' + 'No data was substituted.' % ( + dest_path, document.schema, + document.layer, document.name)) + except Exception as e: + LOG.error('Unexpected exception occurred ' + 'while attempting ' + 'substitution using ' + 'source document [%s, %s] %s ' + 'referenced in [%s, %s] %s. Details: %s', + src_schema, src_name, src_doc.layer, + document.schema, document.layer, + document.name, + six.text_type(e)) + exc_message = six.text_type(e) + finally: + if exc_message: + self._handle_unknown_substitution_exc( + exc_message, src_doc, document) yield document diff --git a/deckhand/tests/unit/engine/test_secrets_manager.py b/deckhand/tests/unit/engine/test_secrets_manager.py index 3179aca6..3df81d39 100644 --- a/deckhand/tests/unit/engine/test_secrets_manager.py +++ b/deckhand/tests/unit/engine/test_secrets_manager.py @@ -327,6 +327,43 @@ class TestSecretsSubstitution(test_base.TestDbBase): self._test_doc_substitution( document_mapping, [certificate], expected_data) + def test_doc_substitution_single_source_feeds_multiple_destinations(self): + certificate = self.secrets_factory.gen_test( + 'Certificate', 'cleartext', data='CERTIFICATE DATA') + certificate['metadata']['name'] = 'example-cert' + + document_mapping = { + "_GLOBAL_SUBSTITUTIONS_1_": [ + { + "dest": [ + { + "path": ".chart[0].values.tls.certificate" + }, + { + "path": ".chart[0].values.tls.same_certificate" + } + ], + "src": { + "schema": "deckhand/Certificate/v1", + "name": "example-cert", + "path": "." + } + } + ] + } + expected_data = { + 'chart': [{ + 'values': { + 'tls': { + 'certificate': 'CERTIFICATE DATA', + 'same_certificate': 'CERTIFICATE DATA', + } + } + }] + } + self._test_doc_substitution( + document_mapping, [certificate], expected_data) + def test_create_destination_path_with_nested_arrays(self): # Validate that the destination data will be populated with an array # that contains yet another array.