Revert "Renaming Files having BluePrint to have Blueprint"
[ccsdk/cds.git] / ms / blueprintsprocessor / functions / k8s-connection-plugin / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / functions / k8s / definition / template / K8sConfigValueComponent.kt
1 package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.template
2
3 import com.fasterxml.jackson.databind.JsonNode
4 import com.fasterxml.jackson.databind.ObjectMapper
5 import com.fasterxml.jackson.databind.node.ArrayNode
6 import com.fasterxml.jackson.databind.node.ObjectNode
7 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
8 import com.fasterxml.jackson.module.kotlin.convertValue
9 import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintPropertiesService
10 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
11 import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sConnectionPluginConfiguration
12 import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sConfigValueRequest
13 import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.instance.K8sPluginInstanceApi
14 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
15 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionService
16 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction
17 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
18 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
19 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode
20 import org.onap.ccsdk.cds.controllerblueprints.core.data.ArtifactDefinition
21 import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing
22 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintVelocityTemplateService
23 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
24 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
25 import org.slf4j.LoggerFactory
26 import org.springframework.beans.factory.config.ConfigurableBeanFactory
27 import org.springframework.context.annotation.Scope
28 import org.springframework.stereotype.Component
29 import java.io.File
30 import java.nio.file.Path
31 import java.nio.file.Paths
32
33 @Component("component-k8s-config-value")
34 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
35 open class K8sConfigValueComponent(
36     private var bluePrintPropertiesService: BluePrintPropertiesService,
37     private val resourceResolutionService: ResourceResolutionService
38 ) : AbstractComponentFunction() {
39
40     private val log = LoggerFactory.getLogger(K8sConfigValueComponent::class.java)!!
41
42     companion object {
43         const val INPUT_RESOURCE_ASSIGNMENT_MAP = "resource-assignment-map"
44         const val INPUT_ARTIFACT_PREFIX_NAMES = "artifact-prefix-names"
45         const val INPUT_K8S_RB_CONFIG_TEMPLATE_NAME = "k8s-rb-config-template-name"
46         const val INPUT_K8S_RB_CONFIG_NAME = "k8s-rb-config-name"
47         const val INPUT_K8S_INSTANCE_ID = "k8s-instance-id"
48         const val INPUT_K8S_CONFIG_VALUE_SOURCE = "k8s-rb-config-value-source"
49         const val INPUT_K8S_CONFIG_OPERATION_TYPE = "k8s-config-operation-type"
50
51         const val OUTPUT_STATUSES = "statuses"
52         const val OUTPUT_SKIPPED = "skipped"
53         const val OUTPUT_UPLOADED = "uploaded"
54         const val OUTPUT_ERROR = "error"
55     }
56
57     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
58         log.info("Triggering K8s Config Value component logic.")
59         val inputParameterNames = arrayOf(
60             INPUT_K8S_RB_CONFIG_TEMPLATE_NAME,
61             INPUT_K8S_RB_CONFIG_NAME,
62             INPUT_K8S_INSTANCE_ID,
63             INPUT_K8S_CONFIG_OPERATION_TYPE,
64             INPUT_K8S_CONFIG_VALUE_SOURCE,
65             INPUT_ARTIFACT_PREFIX_NAMES
66         )
67         val outputPrefixStatuses = mutableMapOf<String, String>()
68         val inputParamsMap = mutableMapOf<String, JsonNode?>()
69
70         inputParameterNames.forEach {
71             inputParamsMap[it] = getOptionalOperationInput(it)?.returnNullIfMissing()
72         }
73
74         log.info("Getting the template prefixes")
75         val prefixList: ArrayList<String> = getTemplatePrefixList(inputParamsMap[INPUT_ARTIFACT_PREFIX_NAMES])
76
77         log.info("Iterating over prefixes in resource assignment map.")
78         for (prefix in prefixList) {
79             outputPrefixStatuses[prefix] = OUTPUT_SKIPPED
80             val prefixNode: JsonNode = operationInputs[INPUT_RESOURCE_ASSIGNMENT_MAP]?.get(prefix) ?: continue
81             val assignmentMapPrefix = JacksonUtils.jsonNode(prefixNode.toPrettyString()) as ObjectNode
82             val prefixInputParamsMap = inputParamsMap.toMutableMap()
83             prefixInputParamsMap.forEach { (inputParamName, value) ->
84                 if (value == null) {
85                     val mapValue = assignmentMapPrefix.get(inputParamName)
86                     log.debug("$inputParamName value was $value so we fetch $mapValue")
87                     prefixInputParamsMap[inputParamName] = mapValue
88                 }
89             }
90
91             val templateName: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_TEMPLATE_NAME]?.returnNullIfMissing()?.asText()
92             val configName: String? = prefixInputParamsMap[INPUT_K8S_RB_CONFIG_NAME]?.returnNullIfMissing()?.asText()
93             val instanceId: String? = prefixInputParamsMap[INPUT_K8S_INSTANCE_ID]?.returnNullIfMissing()?.asText()
94             var valueSource: String? = prefixInputParamsMap[INPUT_K8S_CONFIG_VALUE_SOURCE]?.returnNullIfMissing()?.asText()
95             val operationType = prefixInputParamsMap[INPUT_K8S_CONFIG_OPERATION_TYPE]?.returnNullIfMissing()?.asText()?.toUpperCase()
96
97             if (valueSource == null) {
98                 valueSource = configName
99                 log.info("Config name used instead of value source")
100             }
101             if (operationType == null || operationType == OperationType.CREATE.toString())
102                 createOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName)
103             else if (operationType == OperationType.UPDATE.toString())
104                 updateOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName)
105             else if (operationType == OperationType.DELETE.toString())
106                 deleteOperation(instanceId, configName)
107             else
108                 throw BluePrintProcessorException("Unknown operation type: $operationType")
109         }
110     }
111
112     private suspend fun createOperation(templateName: String?, instanceId: String?, valueSource: String?, outputPrefixStatuses: MutableMap<String, String>, prefix: String, configName: String?) {
113         val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService))
114         if (templateName == null || configName == null || instanceId == null || valueSource == null) {
115             log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME or $INPUT_K8S_INSTANCE_ID or $INPUT_K8S_CONFIG_VALUE_SOURCE or $INPUT_K8S_RB_CONFIG_NAME is null - skipping create")
116         } else if (templateName.isEmpty()) {
117             log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME is empty - skipping create")
118         } else if (configName.isEmpty()) {
119             log.warn("$INPUT_K8S_RB_CONFIG_NAME is empty - skipping create")
120         } else if (api.hasConfigurationValues(instanceId, configName)) {
121             log.info("Configuration already exists - skipping create")
122         } else {
123             log.info("Uploading K8s config..")
124             outputPrefixStatuses[prefix] = OUTPUT_ERROR
125             val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
126             val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, valueSource)
127             if (artifact.type != BluePrintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG)
128                 throw BluePrintProcessorException(
129                     "Unexpected config artifact type for config value source $valueSource. Expecting: $artifact.type"
130                 )
131             val configValueRequest = K8sConfigValueRequest()
132             configValueRequest.templateName = templateName
133             configValueRequest.configName = configName
134             configValueRequest.description = valueSource
135             configValueRequest.values = parseResult(valueSource, artifact.file)
136             api.createConfigurationValues(configValueRequest, instanceId)
137         }
138     }
139
140     private suspend fun updateOperation(templateName: String?, instanceId: String?, valueSource: String?, outputPrefixStatuses: MutableMap<String, String>, prefix: String, configName: String?) {
141         val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService))
142         if (templateName == null || configName == null || instanceId == null || valueSource == null) {
143             log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME or $INPUT_K8S_INSTANCE_ID or $INPUT_K8S_CONFIG_VALUE_SOURCE or $INPUT_K8S_RB_CONFIG_NAME is null - skipping update")
144         } else if (templateName.isEmpty()) {
145             log.warn("$INPUT_K8S_RB_CONFIG_TEMPLATE_NAME is empty - skipping update")
146         } else if (configName.isEmpty()) {
147             log.warn("$INPUT_K8S_RB_CONFIG_NAME is empty - skipping update")
148         } else if (!api.hasConfigurationValues(instanceId, configName)) {
149             log.info("Configuration does not exist - doing create instead")
150             createOperation(templateName, instanceId, valueSource, outputPrefixStatuses, prefix, configName)
151         } else {
152             log.info("Updating K8s config..")
153             outputPrefixStatuses[prefix] = OUTPUT_ERROR
154             val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
155             val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, valueSource)
156             if (artifact.type != BluePrintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG)
157                 throw BluePrintProcessorException(
158                     "Unexpected config artifact type for config value source $valueSource. Expecting: $artifact.type"
159                 )
160             if (api.hasConfigurationValues(instanceId, configName)) {
161                 val configValueRequest = K8sConfigValueRequest()
162                 configValueRequest.templateName = templateName
163                 configValueRequest.configName = configName
164                 configValueRequest.description = valueSource
165                 configValueRequest.values = parseResult(valueSource, artifact.file)
166                 api.editConfigurationValues(configValueRequest, instanceId, configName)
167             } else {
168                 throw BluePrintProcessorException("Error while getting configuration value")
169             }
170         }
171     }
172
173     private fun deleteOperation(instanceId: String?, configName: String?) {
174         val api = K8sPluginInstanceApi(K8sConnectionPluginConfiguration(bluePrintPropertiesService))
175         if (instanceId == null || configName == null) {
176             log.warn("$INPUT_K8S_INSTANCE_ID or $INPUT_K8S_RB_CONFIG_NAME is null - skipping delete")
177         } else if (api.hasConfigurationValues(instanceId, configName)) {
178             log.info("Configuration does not exists - skipping delete")
179         } else {
180             api.deleteConfigurationValues(instanceId, configName)
181         }
182     }
183
184     private suspend fun parseResult(templateValueSource: String, k8sConfigLocation: String): Any {
185         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
186         val bluePrintBasePath: String = bluePrintContext.rootPath
187         val configeValueSourceFilePath: Path = Paths.get(
188             bluePrintBasePath.plus(File.separator).plus(k8sConfigLocation)
189         )
190
191         if (!configeValueSourceFilePath.toFile().exists() || configeValueSourceFilePath.toFile().isDirectory)
192             throw BluePrintProcessorException("Specified config value source $k8sConfigLocation is not a file")
193
194         var obj: Any? = null
195         val yamlReader = ObjectMapper(YAMLFactory())
196         if (configeValueSourceFilePath.toFile().extension.toLowerCase() == "vtl") {
197             log.info("Config building started from source $templateValueSource")
198             val properties: MutableMap<String, Any> = mutableMapOf()
199             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false
200             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = ""
201             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = ""
202             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = ""
203             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1
204             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false
205             val resolutionResult: Pair<String, MutableList<ResourceAssignment>> = resourceResolutionService.resolveResources(
206                 bluePrintRuntimeService,
207                 nodeTemplateName,
208                 templateValueSource,
209                 properties
210             )
211
212             val resolvedJsonContent = resolutionResult.second
213                 .associateBy({ it.name }, { it.property?.value })
214                 .asJsonNode()
215
216             val newContent: String = templateValues(configeValueSourceFilePath.toFile(), resolvedJsonContent)
217             obj = yamlReader.readValue(newContent, Any::class.java)
218         } else {
219             val ymlSourceFile = getYmlSourceFile(k8sConfigLocation)
220             obj = yamlReader.readValue(ymlSourceFile, Any::class.java)
221         }
222         val jsonWriter = ObjectMapper()
223         return jsonWriter.convertValue(obj)
224     }
225
226     private fun templateValues(templateFile: File, params: JsonNode): String {
227         val fileContent = templateFile.bufferedReader().readText()
228         return BluePrintVelocityTemplateService.generateContent(
229             fileContent,
230             params.toString(), true
231         )
232     }
233
234     private fun getYmlSourceFile(templateValueSource: String): File {
235         val bluePrintBasePath: String = bluePrintRuntimeService.bluePrintContext().rootPath
236         val profileSourceFileFolderPath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus(templateValueSource))
237
238         if (profileSourceFileFolderPath.toFile().exists() && !profileSourceFileFolderPath.toFile().isDirectory)
239             return profileSourceFileFolderPath.toFile()
240         else
241             throw BluePrintProcessorException("Template value $profileSourceFileFolderPath is missing in CBA folder")
242     }
243
244     private fun getTemplatePrefixList(node: JsonNode?): ArrayList<String> {
245         val result = ArrayList<String>()
246         when (node) {
247             is ArrayNode -> {
248                 val arrayNode = node.toList()
249                 for (prefixNode in arrayNode)
250                     result.add(prefixNode.asText())
251             }
252             is ObjectNode -> {
253                 result.add(node.asText())
254             }
255         }
256         return result
257     }
258
259     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
260         addError(runtimeException.message!!)
261     }
262
263     private enum class OperationType {
264         CREATE, UPDATE, DELETE
265     }
266 }