Renaming Files having BluePrint to have Blueprint
[ccsdk/cds.git] / ms / blueprintsprocessor / functions / k8s-profile-upload / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / functions / k8s / profile / upload / K8sProfileUploadComponent.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.profile.upload
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.controllerblueprints.resource.dict.ResourceAssignment
29 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
30 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionService
31 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction
32 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants
33 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintProcessorException
34 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode
35 import org.onap.ccsdk.cds.controllerblueprints.core.data.ArtifactDefinition
36 import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing
37 import org.onap.ccsdk.cds.controllerblueprints.core.service.BlueprintVelocityTemplateService
38 import org.onap.ccsdk.cds.controllerblueprints.core.utils.ArchiveType
39 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BlueprintArchiveUtils
40 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
41 import org.slf4j.LoggerFactory
42 import org.springframework.beans.factory.config.ConfigurableBeanFactory
43 import org.springframework.context.annotation.Scope
44 import org.springframework.stereotype.Component
45 import org.yaml.snakeyaml.Yaml
46 import java.io.File
47 import java.nio.file.Files
48 import java.nio.file.Path
49 import java.nio.file.Paths
50
51 @Component("component-k8s-profile-upload")
52 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
53 open class K8sProfileUploadComponent(
54     private var bluePrintPropertiesService: BlueprintPropertiesService,
55     private val resourceResolutionService: ResourceResolutionService
56 ) :
57
58     AbstractComponentFunction() {
59
60     companion object {
61
62         const val INPUT_K8S_PROFILE_NAME = "k8s-rb-profile-name"
63         const val INPUT_K8S_DEFINITION_NAME = "k8s-rb-definition-name"
64         const val INPUT_K8S_DEFINITION_VERSION = "k8s-rb-definition-version"
65         const val INPUT_K8S_PROFILE_NAMESPACE = "k8s-rb-profile-namespace"
66         const val INPUT_K8S_PROFILE_SOURCE = "k8s-rb-profile-source"
67         const val INPUT_RESOURCE_ASSIGNMENT_MAP = "resource-assignment-map"
68         const val INPUT_ARTIFACT_PREFIX_NAMES = "artifact-prefix-names"
69
70         const val OUTPUT_STATUSES = "statuses"
71         const val OUTPUT_SKIPPED = "skipped"
72         const val OUTPUT_UPLOADED = "uploaded"
73         const val OUTPUT_ERROR = "error"
74     }
75
76     private val log = LoggerFactory.getLogger(K8sProfileUploadComponent::class.java)!!
77
78     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
79         log.info("Triggering K8s Profile Upload component logic.")
80
81         val inputParameterNames = arrayOf(
82             INPUT_K8S_PROFILE_NAME,
83             INPUT_K8S_DEFINITION_NAME,
84             INPUT_K8S_DEFINITION_VERSION,
85             INPUT_K8S_PROFILE_NAMESPACE,
86             INPUT_K8S_PROFILE_SOURCE,
87             INPUT_ARTIFACT_PREFIX_NAMES
88         )
89         var outputPrefixStatuses = mutableMapOf<String, String>()
90         var inputParamsMap = mutableMapOf<String, JsonNode?>()
91
92         inputParameterNames.forEach {
93             inputParamsMap[it] = getOptionalOperationInput(it)?.returnNullIfMissing()
94         }
95
96         log.info("Getting the template prefixes")
97         val prefixList: ArrayList<String> = getTemplatePrefixList(inputParamsMap[INPUT_ARTIFACT_PREFIX_NAMES])
98
99         log.info("Iterating over prefixes in resource assignment map.")
100         for (prefix in prefixList) {
101             // Prefilling prefix sucess status
102             outputPrefixStatuses.put(prefix, OUTPUT_SKIPPED)
103             // Resource assignment map is organized by prefixes, in each iteraton we work only
104             // on one section of resource assignment map
105             val prefixNode: JsonNode = operationInputs[INPUT_RESOURCE_ASSIGNMENT_MAP]?.get(prefix) ?: continue
106             val assignmentMapPrefix = JacksonUtils.jsonNode(prefixNode.toPrettyString()) as ObjectNode
107
108             // We are copying the map because for each prefix it might be completed with a different data
109             var prefixInputParamsMap = inputParamsMap.toMutableMap()
110             prefixInputParamsMap.forEach { (inputParamName, value) ->
111                 if (value == null) {
112                     val mapValue = assignmentMapPrefix?.get(inputParamName)
113                     log.debug("$inputParamName value was $value so we fetch $mapValue")
114                     prefixInputParamsMap[inputParamName] = mapValue
115                 }
116             }
117
118             // For clarity we pull out the required fields
119             val profileName: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_NAME]?.returnNullIfMissing()?.asText()
120             val definitionName: String? = prefixInputParamsMap[INPUT_K8S_DEFINITION_NAME]?.returnNullIfMissing()?.asText()
121             val definitionVersion: String? = prefixInputParamsMap[INPUT_K8S_DEFINITION_VERSION]?.returnNullIfMissing()?.asText()
122
123             val k8sProfileUploadConfiguration = K8sProfileUploadConfiguration(bluePrintPropertiesService)
124
125             // Creating API connector
126             var api = K8sPluginApi(
127                 k8sProfileUploadConfiguration.getProperties().username,
128                 k8sProfileUploadConfiguration.getProperties().password,
129                 k8sProfileUploadConfiguration.getProperties().url,
130                 definitionName,
131                 definitionVersion
132             )
133
134             if ((profileName == null) || (definitionName == null) || (definitionVersion == null)) {
135                 log.warn("Prefix $prefix does not have required data for us to continue.")
136             } else if (!api.hasDefinition()) {
137                 log.warn("K8s RB Definition ($definitionName/$definitionVersion) not found ")
138             } else if (profileName == "") {
139                 log.warn("K8s rb profile name is empty! Either define profile name to use or choose default")
140             } else if (api.hasProfile(profileName)) {
141                 log.info("Profile Already Existing - skipping upload")
142             } else {
143                 log.info("Uploading K8s Profile..")
144                 outputPrefixStatuses.put(prefix, OUTPUT_ERROR)
145                 val profileNamespace: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_NAMESPACE]?.returnNullIfMissing()?.asText()
146                 var profileSource: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_SOURCE]?.returnNullIfMissing()?.asText()
147                 if (profileNamespace == null)
148                     throw BlueprintProcessorException("Profile $profileName namespace is missing")
149                 if (profileSource == null) {
150                     profileSource = profileName
151                     log.info("Profile name used instead of profile source")
152                 }
153                 val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
154                 val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, profileSource)
155                 if (artifact.type != BlueprintConstants.MODEL_TYPE_ARTIFACT_K8S_PROFILE)
156                     throw BlueprintProcessorException(
157                         "Unexpected profile artifact type for profile source " +
158                             "$profileSource. Expecting: $artifact.type"
159                     )
160                 var profile = K8sProfile()
161                 profile.profileName = profileName
162                 profile.rbName = definitionName
163                 profile.rbVersion = definitionVersion
164                 profile.namespace = profileNamespace
165                 val profileFilePath: Path = prepareProfileFile(profileName, profileSource, artifact.file)
166                 api.createProfile(profile)
167                 api.uploadProfileContent(profile, profileFilePath)
168
169                 log.info("K8s Profile Upload Completed")
170                 outputPrefixStatuses.put(prefix, OUTPUT_UPLOADED)
171             }
172         }
173         bluePrintRuntimeService.setNodeTemplateAttributeValue(
174             nodeTemplateName,
175             OUTPUT_STATUSES,
176             outputPrefixStatuses.asJsonNode()
177         )
178     }
179
180     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
181         bluePrintRuntimeService.getBlueprintError().addError(runtimeException.message!!)
182     }
183
184     private fun getTemplatePrefixList(node: JsonNode?): ArrayList<String> {
185         var result = ArrayList<String>()
186         when (node) {
187             is ArrayNode -> {
188                 val arrayNode = node.toList()
189                 for (prefixNode in arrayNode)
190                     result.add(prefixNode.asText())
191             }
192             is ObjectNode -> {
193                 result.add(node.asText())
194             }
195         }
196         return result
197     }
198
199     private suspend fun prepareProfileFile(k8sRbProfileName: String, ks8ProfileSource: String, ks8ProfileLocation: String): Path {
200         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
201         val bluePrintBasePath: String = bluePrintContext.rootPath
202         val profileSourceFileFolderPath: Path = Paths.get(
203             bluePrintBasePath.plus(File.separator).plus(ks8ProfileLocation)
204         )
205
206         if (profileSourceFileFolderPath.toFile().exists() && !profileSourceFileFolderPath.toFile().isDirectory)
207             return profileSourceFileFolderPath
208         else if (profileSourceFileFolderPath.toFile().exists()) {
209             log.info("Profile building started from source $ks8ProfileSource")
210             val properties: MutableMap<String, Any> = mutableMapOf()
211             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false
212             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = ""
213             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = ""
214             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = ""
215             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1
216             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false
217             val resolutionResult: Pair<String, MutableList<ResourceAssignment>> = resourceResolutionService.resolveResources(
218                 bluePrintRuntimeService,
219                 nodeTemplateName,
220                 ks8ProfileSource,
221                 properties
222             )
223             val tempMainPath: File = createTempDir("k8s-profile-", "")
224             val tempProfilePath: File = createTempDir("content-", "", tempMainPath)
225
226             val resolvedJsonContent = resolutionResult.second
227                 .associateBy({ it.name }, { it.property?.value })
228                 .asJsonNode()
229
230             try {
231                 val manifestFiles: ArrayList<File>? = readManifestFiles(
232                     profileSourceFileFolderPath.toFile(),
233                     tempProfilePath
234                 )
235                 if (manifestFiles != null) {
236                     templateLocation(
237                         profileSourceFileFolderPath.toFile(), resolvedJsonContent,
238                         tempProfilePath, manifestFiles
239                     )
240                 } else
241                     throw BlueprintProcessorException("Manifest file is missing")
242                 // Preparation of the final profile content
243                 val finalProfileFilePath = Paths.get(
244                     tempMainPath.toString().plus(File.separator).plus(
245                         "$k8sRbProfileName.tar.gz"
246                     )
247                 )
248                 if (!BlueprintArchiveUtils.compress(
249                         tempProfilePath, finalProfileFilePath.toFile(),
250                         ArchiveType.TarGz
251                     )
252                 ) {
253                     throw BlueprintProcessorException("Profile compression has failed")
254                 }
255                 FileUtils.deleteDirectory(tempProfilePath)
256
257                 return finalProfileFilePath
258             } catch (t: Throwable) {
259                 FileUtils.deleteDirectory(tempMainPath)
260                 throw t
261             }
262         } else
263             throw BlueprintProcessorException("Profile source $ks8ProfileLocation is missing in CBA folder")
264     }
265
266     private fun readManifestFiles(profileSource: File, destinationFolder: File): ArrayList<File>? {
267         val directoryListing: Array<File>? = profileSource.listFiles()
268         var result: ArrayList<File>? = null
269         if (directoryListing != null) {
270             for (child in directoryListing) {
271                 if (!child.isDirectory && child.name.toLowerCase() == "manifest.yaml") {
272                     child.bufferedReader().use { inr ->
273                         val manifestYaml = Yaml()
274                         val manifestObject: Map<String, Any> = manifestYaml.load(inr)
275                         val typeObject: MutableMap<String, Any>? = manifestObject["type"] as MutableMap<String, Any>?
276                         if (typeObject != null) {
277                             result = ArrayList<File>()
278                             val valuesObject = typeObject["values"]
279                             if (valuesObject != null) {
280                                 result!!.add(File(destinationFolder.toString().plus(File.separator).plus(valuesObject)))
281                                 result!!.add(File(destinationFolder.toString().plus(File.separator).plus(child.name)))
282                             }
283                             (typeObject["configresource"] as ArrayList<*>?)?.forEach { item ->
284                                 val fileInfo: Map<String, Any> = item as Map<String, Any>
285                                 val filePath = fileInfo["filepath"]
286                                 val chartPath = fileInfo["chartpath"]
287                                 if (filePath == null || chartPath == null)
288                                     log.error("One configresource in manifest was skipped because of the wrong format")
289                                 else {
290                                     result!!.add(File(destinationFolder.toString().plus(File.separator).plus(filePath)))
291                                 }
292                             }
293                         }
294                     }
295                     break
296                 }
297             }
298         }
299         return result
300     }
301
302     private fun templateLocation(
303         location: File,
304         params: JsonNode,
305         destinationFolder: File,
306         manifestFiles: ArrayList<File>
307     ) {
308         val directoryListing: Array<File>? = location.listFiles()
309         if (directoryListing != null) {
310             for (child in directoryListing) {
311                 var newDestinationFolder = destinationFolder.toPath()
312                 if (child.isDirectory)
313                     newDestinationFolder = Paths.get(destinationFolder.toString().plus(File.separator).plus(child.name))
314
315                 templateLocation(child, params, newDestinationFolder.toFile(), manifestFiles)
316             }
317         } else if (!location.isDirectory) {
318             if (location.extension.toLowerCase() == "vtl") {
319                 templateFile(location, params, destinationFolder, manifestFiles)
320             } else {
321                 val finalFilePath = Paths.get(
322                     destinationFolder.path.plus(File.separator)
323                         .plus(location.name)
324                 ).toFile()
325                 if (isFileInTheManifestFiles(finalFilePath, manifestFiles)) {
326                     if (!destinationFolder.exists())
327                         Files.createDirectories(destinationFolder.toPath())
328                     FileUtils.copyFile(location, finalFilePath)
329                 }
330             }
331         }
332     }
333
334     private fun isFileInTheManifestFiles(file: File, manifestFiles: ArrayList<File>): Boolean {
335         manifestFiles.forEach { fileFromManifest ->
336             if (fileFromManifest.toString().toLowerCase() == file.toString().toLowerCase())
337                 return true
338         }
339         return false
340     }
341
342     private fun templateFile(
343         templatedFile: File,
344         params: JsonNode,
345         destinationFolder: File,
346         manifestFiles: ArrayList<File>
347     ) {
348         val finalFile = File(
349             destinationFolder.path.plus(File.separator)
350                 .plus(templatedFile.nameWithoutExtension)
351         )
352         if (!isFileInTheManifestFiles(finalFile, manifestFiles))
353             return
354         val fileContent = templatedFile.bufferedReader().readText()
355         val finalFileContent = BlueprintVelocityTemplateService.generateContent(
356             fileContent,
357             params, true
358         )
359         if (!destinationFolder.exists())
360             Files.createDirectories(destinationFolder.toPath())
361         finalFile.bufferedWriter().use { out -> out.write(finalFileContent) }
362     }
363 }