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.
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.definition.template
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
48 import java.nio.file.Files
49 import java.nio.file.Path
50 import java.nio.file.Paths
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
59 AbstractComponentFunction() {
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"
69 const val OUTPUT_STATUSES = "statuses"
70 const val OUTPUT_SKIPPED = "skipped"
71 const val OUTPUT_UPLOADED = "uploaded"
72 const val OUTPUT_ERROR = "error"
75 private val log = LoggerFactory.getLogger(K8sConfigTemplateComponent::class.java)!!
77 override suspend fun processNB(executionRequest: ExecutionServiceInput) {
78 log.info("Triggering K8s Config Upload component logic.")
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
87 val outputPrefixStatuses = mutableMapOf<String, String>()
88 val inputParamsMap = mutableMapOf<String, JsonNode?>()
90 inputParameterNames.forEach {
91 inputParamsMap[it] = getOptionalOperationInput(it)?.returnNullIfMissing()
94 log.info("Getting the template prefixes")
95 val prefixList: ArrayList<String> = getTemplatePrefixList(inputParamsMap[INPUT_ARTIFACT_PREFIX_NAMES])
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
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) ->
110 val mapValue = assignmentMapPrefix.get(inputParamName)
111 log.debug("$inputParamName value was $value so we fetch $mapValue")
112 prefixInputParamsMap[inputParamName] = mapValue
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()
121 // rename after commit
122 val k8sConnectionPluginConfiguration = K8sConnectionPluginConfiguration(bluePrintPropertiesService)
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")
135 log.info("Uploading K8s template..")
136 outputPrefixStatuses[prefix] = OUTPUT_ERROR
137 var templateSource: String? = prefixInputParamsMap[INPUT_K8S_TEMPLATE_SOURCE]?.returnNullIfMissing()?.asText()
139 var templateFilePath: Path? = null
140 if (templateSource != null && templateSource != "") {
141 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
142 val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, templateSource)
143 if (artifact.type != BluePrintConstants.MODEL_TYPE_ARTIFACT_K8S_CONFIG)
144 throw BluePrintProcessorException(
145 "Unexpected template artifact type for template source $templateSource. Expecting: $artifact.type"
147 templateFilePath = prepareTemplateFile(templateName, templateSource, artifact.file)
150 log.info("Configuration template without content. RB definition content will be used instead")
152 val template = K8sTemplate()
153 template.templateName = templateName
154 template.description = templateSource
156 api.createTemplate(definitionName, definitionVersion, template)
157 if (templateFilePath != null)
158 api.uploadConfigTemplateContent(definitionName, definitionVersion, template, templateFilePath)
160 log.info("K8s Config Upload Completed")
161 outputPrefixStatuses[prefix] = OUTPUT_UPLOADED
164 bluePrintRuntimeService.setNodeTemplateAttributeValue(
167 outputPrefixStatuses.asJsonNode()
171 override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
172 addError(runtimeException.message!!)
175 private fun getTemplatePrefixList(node: JsonNode?): ArrayList<String> {
176 val result = ArrayList<String>()
179 val arrayNode = node.toList()
180 for (prefixNode in arrayNode)
181 result.add(prefixNode.asText())
184 result.add(node.asText())
190 private suspend fun prepareTemplateFile(k8sRbTemplateName: String, ks8ConfigSource: String, k8sConfigLocation: String): Path {
191 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
192 val bluePrintBasePath: String = bluePrintContext.rootPath
193 val configeSourceFileFolderPath: Path = Paths.get(
194 bluePrintBasePath.plus(File.separator).plus(k8sConfigLocation)
197 if (configeSourceFileFolderPath.toFile().exists() && !configeSourceFileFolderPath.toFile().isDirectory)
198 return configeSourceFileFolderPath
199 else if (configeSourceFileFolderPath.toFile().exists()) {
200 log.info("Config building started from source $ks8ConfigSource")
201 val properties: MutableMap<String, Any> = mutableMapOf()
202 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false
203 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = ""
204 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = ""
205 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = ""
206 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1
207 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false
208 val resolutionResult: Pair<String, MutableList<ResourceAssignment>> = resourceResolutionService.resolveResources(
209 bluePrintRuntimeService,
214 val tempMainPath: File = createTempDir("k8s-profile-", "")
215 val tempPath: File = createTempDir("content-", "", tempMainPath)
217 val resolvedJsonContent = resolutionResult.second
218 .associateBy({ it.name }, { it.property?.value })
222 templateLocation(configeSourceFileFolderPath.toFile(), resolvedJsonContent, tempPath)
223 // Preparation of the final config content
224 val finalTemplateFilePath = Paths.get(
225 tempMainPath.toString().plus(File.separator).plus(
226 "$k8sRbTemplateName.tar.gz"
229 if (!BluePrintArchiveUtils.compress(tempPath, finalTemplateFilePath.toFile(), ArchiveType.TarGz)) {
230 throw BluePrintProcessorException("Config template compression has failed")
232 FileUtils.deleteDirectory(tempPath)
234 return finalTemplateFilePath
235 } catch (t: Throwable) {
236 FileUtils.deleteDirectory(tempMainPath)
240 throw BluePrintProcessorException("Config source $k8sConfigLocation is missing in CBA folder")
243 private fun templateLocation(location: File, params: JsonNode, destinationFolder: File) {
244 val directoryListing: Array<File>? = location.listFiles()
245 if (directoryListing != null) {
246 for (child in directoryListing) {
247 var newDestinationFolder = destinationFolder.toPath()
248 if (child.isDirectory)
249 newDestinationFolder = Paths.get(destinationFolder.toString().plus(File.separator).plus(child.name))
251 templateLocation(child, params, newDestinationFolder.toFile())
253 } else if (!location.isDirectory) {
254 if (location.extension.toLowerCase() == "vtl") {
255 templateFile(location, params, destinationFolder)
260 private fun templateFile(templateFile: File, params: JsonNode, destinationFolder: File) {
261 val finalFile = File(destinationFolder.path.plus(File.separator).plus(templateFile.nameWithoutExtension))
262 val fileContent = templateFile.bufferedReader().readText()
263 val finalFileContent = BluePrintVelocityTemplateService.generateContent(
265 params.toString(), true
267 if (!destinationFolder.exists())
268 Files.createDirectories(destinationFolder.toPath())
269 finalFile.bufferedWriter().use { out -> out.write(finalFileContent) }