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.profile.upload
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.resource.resolution.ResourceResolutionConstants
29 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionService
30 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
32 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
33 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode
34 import org.onap.ccsdk.cds.controllerblueprints.core.data.ArtifactDefinition
35 import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing
36 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintVelocityTemplateService
37 import org.onap.ccsdk.cds.controllerblueprints.core.utils.ArchiveType
38 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils
39 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
40 import org.slf4j.LoggerFactory
41 import org.springframework.beans.factory.config.ConfigurableBeanFactory
42 import org.springframework.context.annotation.Scope
43 import org.springframework.stereotype.Component
44 import org.yaml.snakeyaml.Yaml
46 import java.nio.file.Files
47 import java.nio.file.Path
48 import java.nio.file.Paths
50 @Component("component-k8s-profile-upload")
51 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
52 open class K8sProfileUploadComponent(
53 private var bluePrintPropertiesService: BluePrintPropertiesService,
54 private val resourceResolutionService: ResourceResolutionService
57 AbstractComponentFunction() {
61 const val INPUT_K8S_PROFILE_NAME = "k8s-rb-profile-name"
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_PROFILE_NAMESPACE = "k8s-rb-profile-namespace"
65 const val INPUT_K8S_PROFILE_SOURCE = "k8s-rb-profile-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(K8sProfileUploadComponent::class.java)!!
77 override suspend fun processNB(executionRequest: ExecutionServiceInput) {
78 log.info("Triggering K8s Profile Upload component logic.")
80 val inputParameterNames = arrayOf(
81 INPUT_K8S_PROFILE_NAME,
82 INPUT_K8S_DEFINITION_NAME,
83 INPUT_K8S_DEFINITION_VERSION,
84 INPUT_K8S_PROFILE_NAMESPACE,
85 INPUT_K8S_PROFILE_SOURCE,
86 INPUT_ARTIFACT_PREFIX_NAMES
88 var outputPrefixStatuses = mutableMapOf<String, String>()
89 var inputParamsMap = mutableMapOf<String, JsonNode?>()
91 inputParameterNames.forEach {
92 inputParamsMap[it] = getOptionalOperationInput(it)?.returnNullIfMissing()
95 log.info("Getting the template prefixes")
96 val prefixList: ArrayList<String> = getTemplatePrefixList(inputParamsMap[INPUT_ARTIFACT_PREFIX_NAMES])
98 log.info("Iterating over prefixes in resource assignment map.")
99 for (prefix in prefixList) {
100 // Prefilling prefix sucess status
101 outputPrefixStatuses.put(prefix, OUTPUT_SKIPPED)
102 // Resource assignment map is organized by prefixes, in each iteraton we work only
103 // on one section of resource assignment map
104 val prefixNode: JsonNode = operationInputs[INPUT_RESOURCE_ASSIGNMENT_MAP]?.get(prefix) ?: continue
105 val assignmentMapPrefix = JacksonUtils.jsonNode(prefixNode.toPrettyString()) as ObjectNode
107 // We are copying the map because for each prefix it might be completed with a different data
108 var prefixInputParamsMap = inputParamsMap.toMutableMap()
109 prefixInputParamsMap.forEach { (inputParamName, value) ->
111 val mapValue = assignmentMapPrefix?.get(inputParamName)
112 log.debug("$inputParamName value was $value so we fetch $mapValue")
113 prefixInputParamsMap[inputParamName] = mapValue
117 // For clarity we pull out the required fields
118 val profileName: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_NAME]?.returnNullIfMissing()?.asText()
119 val definitionName: String? = prefixInputParamsMap[INPUT_K8S_DEFINITION_NAME]?.returnNullIfMissing()?.asText()
120 val definitionVersion: String? = prefixInputParamsMap[INPUT_K8S_DEFINITION_VERSION]?.returnNullIfMissing()?.asText()
122 val k8sProfileUploadConfiguration = K8sProfileUploadConfiguration(bluePrintPropertiesService)
124 // Creating API connector
125 var api = K8sPluginApi(
126 k8sProfileUploadConfiguration.getProperties().username,
127 k8sProfileUploadConfiguration.getProperties().password,
128 k8sProfileUploadConfiguration.getProperties().url,
133 if ((profileName == null) || (definitionName == null) || (definitionVersion == null)) {
134 log.warn("Prefix $prefix does not have required data for us to continue.")
135 } else if (!api.hasDefinition()) {
136 log.warn("K8s RB Definition ($definitionName/$definitionVersion) not found ")
137 } else if (profileName == "") {
138 log.warn("K8s rb profile name is empty! Either define profile name to use or choose default")
139 } else if (api.hasProfile(profileName)) {
140 log.info("Profile Already Existing - skipping upload")
142 log.info("Uploading K8s Profile..")
143 outputPrefixStatuses.put(prefix, OUTPUT_ERROR)
144 val profileNamespace: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_NAMESPACE]?.returnNullIfMissing()?.asText()
145 var profileSource: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_SOURCE]?.returnNullIfMissing()?.asText()
146 if (profileNamespace == null)
147 throw BluePrintProcessorException("Profile $profileName namespace is missing")
148 if (profileSource == null) {
149 profileSource = profileName
150 log.info("Profile name used instead of profile source")
152 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
153 val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, profileSource)
154 if (artifact.type != BluePrintConstants.MODEL_TYPE_ARTIFACT_K8S_PROFILE)
155 throw BluePrintProcessorException(
156 "Unexpected profile artifact type for profile source " +
157 "$profileSource. Expecting: $artifact.type"
159 var profile = K8sProfile()
160 profile.profileName = profileName
161 profile.rbName = definitionName
162 profile.rbVersion = definitionVersion
163 profile.namespace = profileNamespace
164 val profileFilePath: Path = prepareProfileFile(profileName, profileSource, artifact.file)
165 api.createProfile(profile)
166 api.uploadProfileContent(profile, profileFilePath)
168 log.info("K8s Profile Upload Completed")
169 outputPrefixStatuses.put(prefix, OUTPUT_UPLOADED)
172 bluePrintRuntimeService.setNodeTemplateAttributeValue(
175 outputPrefixStatuses.asJsonNode()
179 override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
180 bluePrintRuntimeService.getBluePrintError().addError(runtimeException.message!!)
183 private fun getTemplatePrefixList(node: JsonNode?): ArrayList<String> {
184 var result = ArrayList<String>()
187 val arrayNode = node.toList()
188 for (prefixNode in arrayNode)
189 result.add(prefixNode.asText())
192 result.add(node.asText())
198 private suspend fun prepareProfileFile(k8sRbProfileName: String, ks8ProfileSource: String, ks8ProfileLocation: String): Path {
199 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
200 val bluePrintBasePath: String = bluePrintContext.rootPath
201 val profileSourceFileFolderPath: String = bluePrintBasePath.plus(File.separator)
202 .plus(ks8ProfileLocation)
203 val profileFilePathTarGz: String = profileSourceFileFolderPath.plus(".tar.gz")
204 val profileFilePathTgz: String = profileSourceFileFolderPath.plus(".tgz")
206 if (Paths.get(profileFilePathTarGz).toFile().exists())
207 return Paths.get(profileFilePathTarGz)
208 else if (Paths.get(profileFilePathTgz).toFile().exists())
209 return Paths.get(profileFilePathTgz)
210 else if (Paths.get(profileSourceFileFolderPath).toFile().exists()) {
211 log.info("Profile building started from source $ks8ProfileSource")
212 val properties: MutableMap<String, Any> = mutableMapOf()
213 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false
214 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = ""
215 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = ""
216 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = ""
217 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1
218 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false
219 val resolutionResult: Pair<String, JsonNode> = resourceResolutionService.resolveResources(
220 bluePrintRuntimeService,
225 val tempMainPath: File = createTempDir("k8s-profile-", "")
226 val tempProfilePath: File = createTempDir("content-", "", tempMainPath)
229 val manifestFiles: ArrayList<File>? = readManifestFiles(
230 Paths.get(profileSourceFileFolderPath).toFile(),
233 if (manifestFiles != null) {
235 Paths.get(profileSourceFileFolderPath).toFile(), resolutionResult.second,
236 tempProfilePath, manifestFiles
239 throw BluePrintProcessorException("Manifest file is missing")
240 // Preparation of the final profile content
241 val finalProfileFilePath = Paths.get(
242 tempMainPath.toString().plus(File.separator).plus(
243 "$k8sRbProfileName.tar.gz"
246 if (!BluePrintArchiveUtils.compress(
247 tempProfilePath, finalProfileFilePath.toFile(),
251 throw BluePrintProcessorException("Profile compression has failed")
253 FileUtils.deleteDirectory(tempProfilePath)
255 return finalProfileFilePath
256 } catch (t: Throwable) {
257 FileUtils.deleteDirectory(tempMainPath)
261 throw BluePrintProcessorException("Profile source $ks8ProfileSource is missing in CBA folder")
264 private fun readManifestFiles(profileSource: File, destinationFolder: File): ArrayList<File>? {
265 val directoryListing: Array<File>? = profileSource.listFiles()
266 var result: ArrayList<File>? = null
267 if (directoryListing != null) {
268 for (child in directoryListing) {
269 if (!child.isDirectory && child.name.toLowerCase() == "manifest.yaml") {
270 child.bufferedReader().use { inr ->
271 val manifestYaml = Yaml()
272 val manifestObject: Map<String, Any> = manifestYaml.load(inr)
273 val typeObject: MutableMap<String, Any>? = manifestObject["type"] as MutableMap<String, Any>?
274 if (typeObject != null) {
275 result = ArrayList<File>()
276 val valuesObject = typeObject["values"]
277 if (valuesObject != null) {
278 result!!.add(File(destinationFolder.toString().plus(File.separator).plus(valuesObject)))
279 result!!.add(File(destinationFolder.toString().plus(File.separator).plus(child.name)))
281 (typeObject["configresource"] as ArrayList<*>?)?.forEach { item ->
282 val fileInfo: Map<String, Any> = item as Map<String, Any>
283 val filePath = fileInfo["filepath"]
284 val chartPath = fileInfo["chartpath"]
285 if (filePath == null || chartPath == null)
286 log.error("One configresource in manifest was skipped because of the wrong format")
288 result!!.add(File(destinationFolder.toString().plus(File.separator).plus(filePath)))
300 private fun templateLocation(
303 destinationFolder: File,
304 manifestFiles: ArrayList<File>
306 val directoryListing: Array<File>? = location.listFiles()
307 if (directoryListing != null) {
308 for (child in directoryListing) {
309 var newDestinationFolder = destinationFolder.toPath()
310 if (child.isDirectory)
311 newDestinationFolder = Paths.get(destinationFolder.toString().plus(File.separator).plus(child.name))
313 templateLocation(child, params, newDestinationFolder.toFile(), manifestFiles)
315 } else if (!location.isDirectory) {
316 if (location.extension.toLowerCase() == "vtl") {
317 templateFile(location, params, destinationFolder, manifestFiles)
319 val finalFilePath = Paths.get(
320 destinationFolder.path.plus(File.separator)
323 if (isFileInTheManifestFiles(finalFilePath, manifestFiles)) {
324 if (!destinationFolder.exists())
325 Files.createDirectories(destinationFolder.toPath())
326 FileUtils.copyFile(location, finalFilePath)
332 private fun isFileInTheManifestFiles(file: File, manifestFiles: ArrayList<File>): Boolean {
333 manifestFiles.forEach { fileFromManifest ->
334 if (fileFromManifest.toString().toLowerCase() == file.toString().toLowerCase())
340 private fun templateFile(
343 destinationFolder: File,
344 manifestFiles: ArrayList<File>
346 val finalFile = File(
347 destinationFolder.path.plus(File.separator)
348 .plus(templatedFile.nameWithoutExtension)
350 if (!isFileInTheManifestFiles(finalFile, manifestFiles))
352 val fileContent = templatedFile.bufferedReader().readText()
353 val finalFileContent = BluePrintVelocityTemplateService.generateContent(
357 if (!destinationFolder.exists())
358 Files.createDirectories(destinationFolder.toPath())
359 finalFile.bufferedWriter().use { out -> out.write(finalFileContent) }