Fixed templating of values in k8s components
[ccsdk/cds.git] / ms / blueprintsprocessor / functions / k8s-connection-plugin / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / functions / k8s / definition / template / K8sConfigTemplateComponent.kt
1 /*
2  * Copyright © 2017-2018 AT&T Intellectual Property.
3  * Modifications Copyright © 2019 IBM.
4  * Modifications Copyright © 2020 Orange.
5  * Modifications Copyright © 2020 Deutsche Telekom AG.
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.template
21
22 import com.fasterxml.jackson.databind.JsonNode
23 import com.fasterxml.jackson.databind.node.ArrayNode
24 import com.fasterxml.jackson.databind.node.ObjectNode
25 import org.apache.commons.io.FileUtils
26 import org.onap.ccsdk.cds.blueprintsprocessor.core.BlueprintPropertiesService
27 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
28 import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.K8sConnectionPluginConfiguration
29 import org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.K8sPluginDefinitionApi
30 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
31 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionService
32 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction
33 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants
34 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintProcessorException
35 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode
36 import org.onap.ccsdk.cds.controllerblueprints.core.data.ArtifactDefinition
37 import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing
38 import org.onap.ccsdk.cds.controllerblueprints.core.service.BlueprintVelocityTemplateService
39 import org.onap.ccsdk.cds.controllerblueprints.core.utils.ArchiveType
40 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BlueprintArchiveUtils
41 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
42 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
43 import org.slf4j.LoggerFactory
44 import org.springframework.beans.factory.config.ConfigurableBeanFactory
45 import org.springframework.context.annotation.Scope
46 import org.springframework.stereotype.Component
47 import java.io.File
48 import java.nio.file.Files
49 import java.nio.file.Path
50 import java.nio.file.Paths
51
52 @Component("component-k8s-config-template")
53 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
54 open class K8sConfigTemplateComponent(
55     private var bluePrintPropertiesService: BlueprintPropertiesService,
56     private val resourceResolutionService: ResourceResolutionService
57 ) :
58
59     AbstractComponentFunction() {
60
61     companion object {
62         const val INPUT_K8S_DEFINITION_NAME = "k8s-rb-definition-name"
63         const val INPUT_K8S_DEFINITION_VERSION = "k8s-rb-definition-version"
64         const val INPUT_K8S_TEMPLATE_NAME = "k8s-rb-config-template-name"
65         const val INPUT_K8S_TEMPLATE_SOURCE = "k8s-rb-config-template-source"
66         const val INPUT_RESOURCE_ASSIGNMENT_MAP = "resource-assignment-map"
67         const val INPUT_ARTIFACT_PREFIX_NAMES = "artifact-prefix-names"
68
69         const val OUTPUT_STATUSES = "statuses"
70         const val OUTPUT_SKIPPED = "skipped"
71         const val OUTPUT_UPLOADED = "uploaded"
72         const val OUTPUT_ERROR = "error"
73     }
74
75     private val log = LoggerFactory.getLogger(K8sConfigTemplateComponent::class.java)!!
76
77     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
78         log.info("Triggering K8s Config Upload component logic.")
79
80         val inputParameterNames = arrayOf(
81             INPUT_K8S_TEMPLATE_NAME,
82             INPUT_K8S_DEFINITION_NAME,
83             INPUT_K8S_DEFINITION_VERSION,
84             INPUT_K8S_TEMPLATE_SOURCE,
85             INPUT_ARTIFACT_PREFIX_NAMES
86         )
87         val outputPrefixStatuses = mutableMapOf<String, String>()
88         val inputParamsMap = mutableMapOf<String, JsonNode?>()
89
90         inputParameterNames.forEach {
91             inputParamsMap[it] = getOptionalOperationInput(it)?.returnNullIfMissing()
92         }
93
94         log.info("Getting the template prefixes")
95         val prefixList: ArrayList<String> = getTemplatePrefixList(inputParamsMap[INPUT_ARTIFACT_PREFIX_NAMES])
96
97         log.info("Iterating over prefixes in resource assignment map.")
98         for (prefix in prefixList) {
99             // Prefilling prefix sucess status
100             outputPrefixStatuses[prefix] = OUTPUT_SKIPPED
101             // Resource assignment map is organized by prefixes, in each iteraton we work only
102             // on one section of resource assignment map
103             val prefixNode: JsonNode = operationInputs[INPUT_RESOURCE_ASSIGNMENT_MAP]?.get(prefix) ?: continue
104             val assignmentMapPrefix = JacksonUtils.jsonNode(prefixNode.toPrettyString()) as ObjectNode
105
106             // We are copying the map because for each prefix it might be completed with a different data
107             val prefixInputParamsMap = inputParamsMap.toMutableMap()
108             prefixInputParamsMap.forEach { (inputParamName, value) ->
109                 if (value == null) {
110                     val mapValue = assignmentMapPrefix.get(inputParamName)
111                     log.debug("$inputParamName value was $value so we fetch $mapValue")
112                     prefixInputParamsMap[inputParamName] = mapValue
113                 }
114             }
115
116             // For clarity we pull out the required fields
117             val templateName: String? = prefixInputParamsMap[INPUT_K8S_TEMPLATE_NAME]?.returnNullIfMissing()?.asText()
118             val definitionName: String? = prefixInputParamsMap[INPUT_K8S_DEFINITION_NAME]?.returnNullIfMissing()?.asText()
119             val definitionVersion: String? = prefixInputParamsMap[INPUT_K8S_DEFINITION_VERSION]?.returnNullIfMissing()?.asText()
120
121             // rename after commit
122             val k8sConnectionPluginConfiguration = K8sConnectionPluginConfiguration(bluePrintPropertiesService)
123
124             // Creating API connector
125             val api = K8sPluginDefinitionApi(k8sConnectionPluginConfiguration)
126             if ((templateName == null) || (definitionName == null) || (definitionVersion == null)) {
127                 log.warn("Prefix $prefix does not have required data for us to continue.")
128             } else if (!api.hasDefinition(definitionName, definitionVersion)) {
129                 log.warn("K8s RB Definition ($definitionName/$definitionVersion) not found ")
130             } else if (templateName == "") {
131                 log.warn("K8s rb template name is empty! Either define template name to use or choose default")
132             } else if (api.hasTemplate(definitionName, definitionVersion, templateName)) {
133                 log.info("Template already existing - skipping upload")
134             } else {
135                 log.info("Uploading K8s template..")
136                 outputPrefixStatuses[prefix] = OUTPUT_ERROR
137                 var templateSource: String? = prefixInputParamsMap[INPUT_K8S_TEMPLATE_SOURCE]?.returnNullIfMissing()?.asText()
138                 if (templateSource == null) {
139                     templateSource = templateName
140                     log.info("Template name used instead of template source")
141                 }
142                 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
143                 val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, templateSource)
144                 if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG)
145                     throw BlueprintProcessorException(
146                         "Unexpected template artifact type for template source $templateSource. Expecting: $artifact.type"
147                     )
148                 val template = K8sTemplate()
149                 template.templateName = templateName
150                 template.description = templateSource
151
152                 val templateFilePath: Path = prepareTemplateFile(templateName, templateSource, artifact.file)
153                 api.createTemplate(definitionName, definitionVersion, template)
154                 api.uploadConfigTemplateContent(definitionName, definitionVersion, template, templateFilePath)
155
156                 log.info("K8s Config Upload Completed")
157                 outputPrefixStatuses[prefix] = OUTPUT_UPLOADED
158             }
159         }
160         bluePrintRuntimeService.setNodeTemplateAttributeValue(
161             nodeTemplateName,
162             OUTPUT_STATUSES,
163             outputPrefixStatuses.asJsonNode()
164         )
165     }
166
167     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
168         addError(runtimeException.message!!)
169     }
170
171     private fun getTemplatePrefixList(node: JsonNode?): ArrayList<String> {
172         val result = ArrayList<String>()
173         when (node) {
174             is ArrayNode -> {
175                 val arrayNode = node.toList()
176                 for (prefixNode in arrayNode)
177                     result.add(prefixNode.asText())
178             }
179             is ObjectNode -> {
180                 result.add(node.asText())
181             }
182         }
183         return result
184     }
185
186     private suspend fun prepareTemplateFile(k8sRbTemplateName: String, ks8ConfigSource: String, k8sConfigLocation: String): Path {
187         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
188         val bluePrintBasePath: String = bluePrintContext.rootPath
189         val configeSourceFileFolderPath: Path = Paths.get(
190             bluePrintBasePath.plus(File.separator).plus(k8sConfigLocation)
191         )
192
193         if (configeSourceFileFolderPath.toFile().exists() && !configeSourceFileFolderPath.toFile().isDirectory)
194             return configeSourceFileFolderPath
195         else if (configeSourceFileFolderPath.toFile().exists()) {
196             log.info("Config building started from source $ks8ConfigSource")
197             val properties: MutableMap<String, Any> = mutableMapOf()
198             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false
199             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = ""
200             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = ""
201             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = ""
202             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1
203             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false
204             val resolutionResult: Pair<String, MutableList<ResourceAssignment>> = resourceResolutionService.resolveResources(
205                 bluePrintRuntimeService,
206                 nodeTemplateName,
207                 ks8ConfigSource,
208                 properties
209             )
210             val tempMainPath: File = createTempDir("k8s-profile-", "")
211             val tempPath: File = createTempDir("content-", "", tempMainPath)
212
213             val resolvedJsonContent = resolutionResult.second
214                 .associateBy({ it.name }, { it.property?.value })
215                 .asJsonNode()
216
217             try {
218                 templateLocation(configeSourceFileFolderPath.toFile(), resolvedJsonContent, tempPath)
219                 // Preparation of the final config content
220                 val finalTemplateFilePath = Paths.get(
221                     tempMainPath.toString().plus(File.separator).plus(
222                         "$k8sRbTemplateName.tar.gz"
223                     )
224                 )
225                 if (!BlueprintArchiveUtils.compress(tempPath, finalTemplateFilePath.toFile(), ArchiveType.TarGz)) {
226                     throw BlueprintProcessorException("Config template compression has failed")
227                 }
228                 FileUtils.deleteDirectory(tempPath)
229
230                 return finalTemplateFilePath
231             } catch (t: Throwable) {
232                 FileUtils.deleteDirectory(tempMainPath)
233                 throw t
234             }
235         } else
236             throw BlueprintProcessorException("Config source $k8sConfigLocation is missing in CBA folder")
237     }
238
239     private fun templateLocation(location: File, params: JsonNode, destinationFolder: File) {
240         val directoryListing: Array<File>? = location.listFiles()
241         if (directoryListing != null) {
242             for (child in directoryListing) {
243                 var newDestinationFolder = destinationFolder.toPath()
244                 if (child.isDirectory)
245                     newDestinationFolder = Paths.get(destinationFolder.toString().plus(File.separator).plus(child.name))
246
247                 templateLocation(child, params, newDestinationFolder.toFile())
248             }
249         } else if (!location.isDirectory) {
250             if (location.extension.toLowerCase() == "vtl") {
251                 templateFile(location, params, destinationFolder)
252             }
253         }
254     }
255
256     private fun templateFile(templateFile: File, params: JsonNode, destinationFolder: File) {
257         val finalFile = File(destinationFolder.path.plus(File.separator).plus(templateFile.nameWithoutExtension))
258         val fileContent = templateFile.bufferedReader().readText()
259         val finalFileContent = BlueprintVelocityTemplateService.generateContent(
260             fileContent,
261             params.toString(), true
262         )
263         if (!destinationFolder.exists())
264             Files.createDirectories(destinationFolder.toPath())
265         finalFile.bufferedWriter().use { out -> out.write(finalFileContent) }
266     }
267 }