Vault Client Rest API implementation for sensitive data
[ccsdk/cds.git] / ms / blueprintsprocessor / functions / resource-resolution / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / functions / resource / resolution / utils / ResourceAssignmentUtils.kt
1 /*
2  * Copyright © 2017-2018 AT&T Intellectual Property.
3  * Modifications Copyright (c) 2019 IBM, Bell Canada.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils
19
20 import com.fasterxml.jackson.databind.JsonNode
21 import com.fasterxml.jackson.databind.ObjectMapper
22 import com.fasterxml.jackson.databind.node.ArrayNode
23 import com.fasterxml.jackson.databind.node.NullNode
24 import com.fasterxml.jackson.databind.node.ObjectNode
25 import com.fasterxml.jackson.databind.node.TextNode
26 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceAssignmentRuntimeService
27 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
28 import org.onap.ccsdk.cds.controllerblueprints.core.*
29 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService
30 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonReactorUtils
31 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
32 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
33 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition
34 import org.slf4j.LoggerFactory
35 import java.util.*
36
37 class ResourceAssignmentUtils {
38     companion object {
39
40         private val logger = LoggerFactory.getLogger(ResourceAssignmentUtils::class.toString())
41
42         suspend fun resourceDefinitions(blueprintBasePath: String): MutableMap<String, ResourceDefinition> {
43             val dictionaryFile = normalizedFile(blueprintBasePath, BluePrintConstants.TOSCA_DEFINITIONS_DIR,
44                     ResourceResolutionConstants.FILE_NAME_RESOURCE_DEFINITION_TYPES)
45             checkFileExists(dictionaryFile) { "resource definition file(${dictionaryFile.absolutePath}) is missing" }
46             return JacksonReactorUtils.getMapFromFile(dictionaryFile, ResourceDefinition::class.java)
47         }
48
49         @Throws(BluePrintProcessorException::class)
50         fun setResourceDataValue(resourceAssignment: ResourceAssignment,
51                                  raRuntimeService: ResourceAssignmentRuntimeService, value: Any?) {
52             // TODO("See if Validation is needed in future with respect to conversion and Types")
53             return setResourceDataValue(resourceAssignment, raRuntimeService, value.asJsonType())
54         }
55
56         @Throws(BluePrintProcessorException::class)
57         fun setResourceDataValue(resourceAssignment: ResourceAssignment,
58                                  raRuntimeService: ResourceAssignmentRuntimeService, value: JsonNode) {
59             val resourceProp = checkNotNull(resourceAssignment.property) {
60                 "Failed in setting resource value for resource mapping $resourceAssignment"
61             }
62             checkNotEmpty(resourceAssignment.name) {
63                 "Failed in setting resource value for resource mapping $resourceAssignment"
64             }
65
66             if (resourceAssignment.dictionaryName.isNullOrEmpty()) {
67                 resourceAssignment.dictionaryName = resourceAssignment.name
68                 logger.warn("Missing dictionary key, setting with template key (${resourceAssignment.name}) " +
69                         "as dictionary key (${resourceAssignment.dictionaryName})")
70             }
71
72             try {
73                 if (resourceProp.type.isNotEmpty()) {
74                     if (resourceAssignment.dictionarySource in ResourceResolutionConstants.DATA_DICTIONARY_SECRET_SOURCE_TYPES) {
75                         logger.info("Setting Resource Value (*********) for Resource Name " +
76                                 "(${resourceAssignment.name}) of type (${resourceProp.type})")
77                     }
78                     else {
79                         logger.info("Setting Resource Value ($value) for Resource Name " +
80                                 "(${resourceAssignment.name}), definition(${resourceAssignment.dictionaryName}) " +
81                                 "of type (${resourceProp.type})")
82                     }
83                     setResourceValue(resourceAssignment, raRuntimeService, value)
84                     resourceAssignment.updatedDate = Date()
85                     resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
86                     resourceAssignment.status = BluePrintConstants.STATUS_SUCCESS
87                 }
88             } catch (e: Exception) {
89                 throw BluePrintProcessorException("Failed in setting value for template key " +
90                         "(${resourceAssignment.name}) and dictionary key (${resourceAssignment.dictionaryName}) of " +
91                         "type (${resourceProp.type}) with error message (${e.message})", e)
92             }
93         }
94
95         private fun setResourceValue(resourceAssignment: ResourceAssignment,
96                                      raRuntimeService: ResourceAssignmentRuntimeService, value: JsonNode) {
97             // TODO("See if Validation is needed wrt to type before storing")
98             raRuntimeService.putResolutionStore(resourceAssignment.name, value)
99             raRuntimeService.putDictionaryStore(resourceAssignment.dictionaryName!!, value)
100             resourceAssignment.property!!.value = value
101         }
102
103         fun setFailedResourceDataValue(resourceAssignment: ResourceAssignment, message: String?) {
104             if (isNotEmpty(resourceAssignment.name)) {
105                 resourceAssignment.updatedDate = Date()
106                 resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
107                 resourceAssignment.status = BluePrintConstants.STATUS_FAILURE
108                 resourceAssignment.message = message
109             }
110         }
111
112         @Throws(BluePrintProcessorException::class)
113         fun assertTemplateKeyValueNotNull(resourceAssignment: ResourceAssignment) {
114             val resourceProp = checkNotNull(resourceAssignment.property) {
115                 "Failed to populate mandatory resource resource mapping $resourceAssignment"
116             }
117             if (resourceProp.required != null && resourceProp.required!!
118                     && (resourceProp.value == null || resourceProp.value!!.returnNullIfMissing() == null)) {
119                 logger.error("failed to populate mandatory resource mapping ($resourceAssignment)")
120                 throw BluePrintProcessorException("failed to populate mandatory resource mapping ($resourceAssignment)")
121             }
122         }
123
124         @Throws(BluePrintProcessorException::class)
125         fun generateResourceDataForAssignments(assignments: List<ResourceAssignment>): String {
126             val result: String
127             try {
128                 val mapper = ObjectMapper()
129                 val root: ObjectNode = mapper.createObjectNode()
130
131                 var containsSecret = false
132                 assignments.forEach {
133                     if (isNotEmpty(it.name) && it.property != null) {
134                         val rName = it.name
135                         val type = nullToEmpty(it.property?.type).toLowerCase()
136                         val value = useDefaultValueIfNull(it, rName)
137                         if (it.dictionarySource in ResourceResolutionConstants.DATA_DICTIONARY_SECRET_SOURCE_TYPES) {
138                             logger.info("Generating Resource name ($rName), type ($type), value (************)")
139                             containsSecret = true
140                         }
141                         else {
142                             logger.info("Generating Resource name ($rName), type ($type), value ($value)")
143                         }
144                         root.set(rName, value)
145                     }
146                 }
147                 result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root)
148                 if (containsSecret) {
149                     logger.info("Generated Resource Param Data (***********)")
150                 }
151                 else{
152                     logger.info("Generated Resource Param Data ($result)")
153                 }
154             } catch (e: Exception) {
155                 throw BluePrintProcessorException("Resource Assignment is failed with $e.message", e)
156             }
157
158             return result
159         }
160
161         @Throws(BluePrintProcessorException::class)
162         fun generateResourceForAssignments(assignments: List<ResourceAssignment>): MutableMap<String, JsonNode> {
163             val data: MutableMap<String, JsonNode> = hashMapOf()
164             assignments.forEach {
165                 if (isNotEmpty(it.name) && it.property != null) {
166                     val rName = it.name
167                     val type = nullToEmpty(it.property?.type).toLowerCase()
168                     val value = useDefaultValueIfNull(it, rName)
169                     if (it.dictionarySource in ResourceResolutionConstants.DATA_DICTIONARY_SECRET_SOURCE_TYPES) {
170                         logger.trace("Generating Resource name ($rName), type ($type), value (************)")
171                     }
172                     else {
173                         logger.trace("Generating Resource name ($rName), type ($type), value ($value)")
174                     }
175                     data[rName] = value
176                 }
177             }
178             return data
179         }
180
181         private fun useDefaultValueIfNull(resourceAssignment: ResourceAssignment, resourceAssignmentName: String): JsonNode {
182             if (resourceAssignment.property?.value == null) {
183                 val defaultValue = "\${$resourceAssignmentName}"
184                 return TextNode(defaultValue)
185             } else {
186                 return resourceAssignment.property!!.value!!
187             }
188         }
189
190         fun transformToRARuntimeService(blueprintRuntimeService: BluePrintRuntimeService<*>,
191                                         templateArtifactName: String): ResourceAssignmentRuntimeService {
192
193             val resourceAssignmentRuntimeService = ResourceAssignmentRuntimeService(blueprintRuntimeService.id(),
194                     blueprintRuntimeService.bluePrintContext())
195             resourceAssignmentRuntimeService.createUniqueId(templateArtifactName)
196             resourceAssignmentRuntimeService.setExecutionContext(blueprintRuntimeService.getExecutionContext() as MutableMap<String, JsonNode>)
197
198             return resourceAssignmentRuntimeService
199         }
200
201         @Throws(BluePrintProcessorException::class)
202         fun getPropertyType(raRuntimeService: ResourceAssignmentRuntimeService, dataTypeName: String,
203                             propertyName: String): String {
204             lateinit var type: String
205             try {
206                 val dataTypeProps = checkNotNull(raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties)
207
208                 val propertyDefinition = checkNotNull(dataTypeProps[propertyName])
209                 type = checkNotEmpty(propertyDefinition.type) { "Couldn't get data type ($dataTypeName)" }
210                 logger.trace("Data type({})'s property ({}) is ({})", dataTypeName, propertyName, type)
211             } catch (e: Exception) {
212                 logger.error("couldn't get data type($dataTypeName)'s property ($propertyName), error message $e")
213                 throw BluePrintProcessorException("${e.message}", e)
214             }
215             return type
216         }
217
218         @Throws(BluePrintProcessorException::class)
219         fun parseResponseNode(responseNode: JsonNode, resourceAssignment: ResourceAssignment,
220                               raRuntimeService: ResourceAssignmentRuntimeService, outputKeyMapping: MutableMap<String, String>): JsonNode {
221             try {
222                 if ((resourceAssignment.property?.type).isNullOrEmpty()) {
223                     throw BluePrintProcessorException("Couldn't get data dictionary type for dictionary name (${resourceAssignment.name})")
224                 }
225                 val type = resourceAssignment.property!!.type
226
227                 if (resourceAssignment.dictionarySource in ResourceResolutionConstants.DATA_DICTIONARY_SECRET_SOURCE_TYPES) {
228                     logger.info("For template key (${resourceAssignment.name}) setting value as (***************)")
229                 }
230                 else {
231                     logger.info("For template key (${resourceAssignment.name}) setting value as ($responseNode)")
232                 }
233                 
234                 return when (type) {
235                     in BluePrintTypes.validPrimitiveTypes() -> {
236                         parseResponseNodeForPrimitiveTypes(responseNode, outputKeyMapping)
237                     }
238                     in BluePrintTypes.validCollectionTypes() -> {
239                         // Array Types
240                         parseResponseNodeForCollection(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
241                     }
242                     else -> {
243                         // Complex Types
244                         parseResponseNodeForComplexType(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
245                     }
246                 }
247             } catch (e: Exception) {
248                 logger.error("Fail to parse response data, error message $e")
249                 throw BluePrintProcessorException("${e.message}", e)
250             }
251         }
252
253         private fun parseResponseNodeForPrimitiveTypes(responseNode: JsonNode,
254                                                        outputKeyMapping: MutableMap<String, String>): JsonNode {
255             var result: JsonNode? = responseNode
256             if (responseNode.isComplexType()) {
257                 val key = outputKeyMapping.keys.firstOrNull()
258                 var returnNode: JsonNode? = responseNode
259                 if (responseNode is ArrayNode) {
260                     val arrayNode = responseNode.toList()
261                     val firstElement = if (key.isNullOrEmpty()) {
262                         arrayNode.first()
263                     }
264                     else{
265                         arrayNode.firstOrNull { element ->
266                             element.isComplexType() && element.has(outputKeyMapping[key])
267                         }
268                     }
269
270                     if (firstElement.isNull() || (firstElement!!.isComplexType() && !firstElement!!.has(outputKeyMapping[key]))
271                             || (!result!!.isComplexType() && result is NullNode)) {
272                         if (key.isNullOrEmpty()) {
273                             throw BluePrintProcessorException("Fail to find mapping in the responseNode.")
274                         }
275                         else {
276                             throw BluePrintProcessorException("Fail to find response with output key mapping ($key) in result.")
277                         }
278                     }
279                     returnNode = firstElement
280                 }
281                 result = if (returnNode!!.isComplexType()) {
282                     returnNode[outputKeyMapping[key]]
283                 }
284                 else {
285                     returnNode
286                 }
287             }
288             return result!!
289         }
290
291         private fun parseResponseNodeForCollection(responseNode: JsonNode, resourceAssignment: ResourceAssignment,
292                                                    raRuntimeService: ResourceAssignmentRuntimeService,
293                                                    outputKeyMapping: MutableMap<String, String>): JsonNode {
294             val dName = resourceAssignment.dictionaryName
295             val dSource = resourceAssignment.dictionarySource
296             if ((resourceAssignment.property?.entrySchema?.type).isNullOrEmpty()) {
297                 throw BluePrintProcessorException("Couldn't get data type for dictionary type " +
298                         "(${resourceAssignment.property!!.type}) and dictionary name ($dName)")
299             }
300             val entrySchemaType = resourceAssignment.property!!.entrySchema!!.type
301
302             var arrayNode = JacksonUtils.objectMapper.createArrayNode()
303
304             if (outputKeyMapping.isNotEmpty()) {
305                 when (responseNode) {
306                     is ArrayNode -> {
307                         val responseArrayNode = responseNode.toList()
308                         for (responseSingleJsonNode in responseArrayNode) {
309                             val arrayChildNode = parseArrayNodeElementWithOutputKeyMapping(raRuntimeService, responseSingleJsonNode,
310                                     outputKeyMapping, entrySchemaType, dSource!!)
311                             arrayNode.add(arrayChildNode)
312                         }
313                     }
314                     is ObjectNode -> {
315                         val responseArrayNode = responseNode.rootFieldsToMap()
316                         val arrayNodeResult = parseObjectNodeWithOutputKeyMapping(responseArrayNode,
317                                 outputKeyMapping, entrySchemaType, dSource!!)
318                         arrayNode.addAll(arrayNodeResult)
319                     }
320                     else -> {
321                         throw BluePrintProcessorException("Key-value response expected to match the responseNode.")
322                     }
323                 }
324             }
325             else {
326                 when (responseNode) {
327                     is ArrayNode -> {
328                         responseNode.forEach { elementNode ->
329                             arrayNode.add(elementNode)
330                         }
331                     }
332                     is ObjectNode -> {
333                         val responseArrayNode = responseNode.rootFieldsToMap()
334                         for ((key, responseSingleJsonNode) in responseArrayNode) {
335                             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
336                             JacksonUtils.populateJsonNodeValues(key, responseSingleJsonNode, entrySchemaType, arrayChildNode)
337                             arrayNode.add(arrayChildNode)
338                         }
339                     }
340                     else -> {
341                         arrayNode.add(responseNode)
342                     }
343                 }
344             }
345             return arrayNode
346         }
347
348         private fun parseResponseNodeForComplexType(responseNode: JsonNode, resourceAssignment: ResourceAssignment,
349                                                     raRuntimeService: ResourceAssignmentRuntimeService,
350                                                     outputKeyMapping: MutableMap<String, String>): JsonNode {
351             val entrySchemaType = resourceAssignment.property!!.type
352             val dictionarySource = resourceAssignment.dictionarySource!!
353             val dictionaryName = resourceAssignment.dictionaryName!!
354
355             var result: ObjectNode
356             if (checkOutputKeyMappingInDataTypeProperties(entrySchemaType, outputKeyMapping, raRuntimeService))
357             {
358                 result = parseArrayNodeElementWithOutputKeyMapping(raRuntimeService, responseNode, outputKeyMapping, entrySchemaType, dictionarySource!!)
359             }
360             else {
361                 val childNode = JacksonUtils.objectMapper.createObjectNode()
362                 if (outputKeyMapping.isNotEmpty()) {
363                     outputKeyMapping.map {
364                         val responseKeyValue = if (responseNode.has(it.key)) {
365                             responseNode.get(it.key)
366                         }
367                         else {
368                             NullNode.getInstance()
369                         }
370
371                         logKeyValueResolvedResource(it.key, responseKeyValue, entrySchemaType, dictionarySource)
372
373                         JacksonUtils.populateJsonNodeValues(it.value,
374                                 responseKeyValue, entrySchemaType, childNode)
375                     }
376                 }
377                 else {
378                     JacksonUtils.populateJsonNodeValues(dictionaryName, responseNode, entrySchemaType, childNode)
379                 }
380                 result = childNode
381             }
382             return result
383         }
384
385         private fun parseArrayNodeElementWithOutputKeyMapping(raRuntimeService: ResourceAssignmentRuntimeService,
386                                                               responseSingleJsonNode: JsonNode, outputKeyMapping:
387                                                               MutableMap<String, String>, entrySchemaType: String,
388                                                               dictionarySource: String): ObjectNode {
389             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
390
391             outputKeyMapping.map {
392                 val responseKeyValue = if (responseSingleJsonNode.has(it.key)) {
393                     responseSingleJsonNode.get(it.key)
394                 }
395                 else {
396                     NullNode.getInstance()
397                 }
398                 val propertyTypeForDataType = ResourceAssignmentUtils
399                         .getPropertyType(raRuntimeService, entrySchemaType, it.key)
400
401                 logKeyValueResolvedResource(it.key, responseKeyValue, propertyTypeForDataType, dictionarySource)
402
403                 JacksonUtils.populateJsonNodeValues(it.value,
404                         responseKeyValue, propertyTypeForDataType, arrayChildNode)
405             }
406             return arrayChildNode
407         }
408
409         private fun parseObjectNodeWithOutputKeyMapping(responseArrayNode: MutableMap<String, JsonNode>,
410                                                         outputKeyMapping: MutableMap<String, String>,
411                                                         entrySchemaType: String,
412                                                         dictionarySource: String): ArrayNode {
413             val arrayNode = JacksonUtils.objectMapper.createArrayNode()
414             outputKeyMapping.map {
415                 val objectNode = JacksonUtils.objectMapper.createObjectNode()
416                 val responseSingleJsonNode = responseArrayNode.filterKeys { key -> key == it.key }.entries.firstOrNull()
417
418                 val responseNode = responseSingleJsonNode?.value ?: NullNode.getInstance()
419
420                 logKeyValueResolvedResource(it.key, responseNode, entrySchemaType, dictionarySource)
421
422                 JacksonUtils.populateJsonNodeValues(it.value, responseNode, entrySchemaType, objectNode)
423
424                 arrayNode.add(objectNode)
425             }
426
427             return arrayNode
428         }
429
430         private fun checkOutputKeyMappingInDataTypeProperties(dataTypeName: String, outputKeyMapping: MutableMap<String, String>,
431                                                               raRuntimeService: ResourceAssignmentRuntimeService): Boolean {
432             val dataTypeProps = raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties
433             val result = outputKeyMapping.filterKeys { !dataTypeProps!!.containsKey(it) }.keys.firstOrNull()
434             return result == null
435         }
436
437         private fun logKeyValueResolvedResource(key: String, value: JsonNode, type: String, dictionarySource: String) {
438             if (dictionarySource in ResourceResolutionConstants.DATA_DICTIONARY_SECRET_SOURCE_TYPES) {
439                 logger.info("For List Type Resource: key ($key), value (****************), " +
440                         "type  ({$type})")
441             }
442             else {
443                 logger.info("For List Type Resource: key ($key), value ($value), " +
444                         "type  ({$type})")
445             }
446         }
447     }
448 }