aboutsummaryrefslogtreecommitdiffstats
path: root/ms/blueprintsprocessor/functions/k8s-profile-upload/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/profile/upload/K8sProfileUploadComponent.kt
blob: a98c9e9d14cd64cfc9bfe0be65e077f18196a064 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/*
 * Copyright © 2017-2018 AT&T Intellectual Property.
 * Modifications Copyright © 2019 IBM.
 * Modifications Copyright © 2020 Orange.
 * Modifications Copyright © 2020 Deutsche Telekom AG.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s.profile.upload

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import org.apache.commons.io.FileUtils
import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintPropertiesService
import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionService
import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction
import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode
import org.onap.ccsdk.cds.controllerblueprints.core.data.ArtifactDefinition
import org.onap.ccsdk.cds.controllerblueprints.core.returnNullIfMissing
import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintVelocityTemplateService
import org.onap.ccsdk.cds.controllerblueprints.core.utils.ArchiveType
import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils
import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.context.annotation.Scope
import org.springframework.stereotype.Component
import org.yaml.snakeyaml.Yaml
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

@Component("component-k8s-profile-upload")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
open class K8sProfileUploadComponent(
    private var bluePrintPropertiesService: BluePrintPropertiesService,
    private val resourceResolutionService: ResourceResolutionService
) :

    AbstractComponentFunction() {

    companion object {

        const val INPUT_K8S_PROFILE_NAME = "k8s-rb-profile-name"
        const val INPUT_K8S_DEFINITION_NAME = "k8s-rb-definition-name"
        const val INPUT_K8S_DEFINITION_VERSION = "k8s-rb-definition-version"
        const val INPUT_K8S_PROFILE_NAMESPACE = "k8s-rb-profile-namespace"
        const val INPUT_K8S_PROFILE_K8S_VERSION = "k8s-rb-profile-k8s-version"
        const val INPUT_K8S_PROFILE_SOURCE = "k8s-rb-profile-source"
        const val INPUT_RESOURCE_ASSIGNMENT_MAP = "resource-assignment-map"
        const val INPUT_ARTIFACT_PREFIX_NAMES = "artifact-prefix-names"

        const val OUTPUT_STATUSES = "statuses"
        const val OUTPUT_SKIPPED = "skipped"
        const val OUTPUT_UPLOADED = "uploaded"
        const val OUTPUT_ERROR = "error"
    }

    private val log = LoggerFactory.getLogger(K8sProfileUploadComponent::class.java)!!

    override suspend fun processNB(executionRequest: ExecutionServiceInput) {
        log.info("Triggering K8s Profile Upload component logic.")

        val inputParameterNames = arrayOf(
            INPUT_K8S_PROFILE_NAME,
            INPUT_K8S_DEFINITION_NAME,
            INPUT_K8S_DEFINITION_VERSION,
            INPUT_K8S_PROFILE_NAMESPACE,
            INPUT_K8S_PROFILE_K8S_VERSION,
            INPUT_K8S_PROFILE_SOURCE,
            INPUT_ARTIFACT_PREFIX_NAMES
        )
        var outputPrefixStatuses = mutableMapOf<String, String>()
        var inputParamsMap = mutableMapOf<String, JsonNode?>()

        inputParameterNames.forEach {
            inputParamsMap[it] = getOptionalOperationInput(it)?.returnNullIfMissing()
        }

        log.info("Getting the template prefixes")
        val prefixList: ArrayList<String> = getTemplatePrefixList(inputParamsMap[INPUT_ARTIFACT_PREFIX_NAMES])

        log.info("Iterating over prefixes in resource assignment map.")
        for (prefix in prefixList) {
            // Prefilling prefix sucess status
            outputPrefixStatuses.put(prefix, OUTPUT_SKIPPED)
            // Resource assignment map is organized by prefixes, in each iteraton we work only
            // on one section of resource assignment map
            val prefixNode: JsonNode = operationInputs[INPUT_RESOURCE_ASSIGNMENT_MAP]?.get(prefix) ?: continue
            val assignmentMapPrefix = JacksonUtils.jsonNode(prefixNode.toPrettyString()) as ObjectNode

            // We are copying the map because for each prefix it might be completed with a different data
            var prefixInputParamsMap = inputParamsMap.toMutableMap()
            prefixInputParamsMap.forEach { (inputParamName, value) ->
                if (value == null) {
                    val mapValue = assignmentMapPrefix?.get(inputParamName)
                    log.debug("$inputParamName value was $value so we fetch $mapValue")
                    prefixInputParamsMap[inputParamName] = mapValue
                }
            }

            // For clarity we pull out the required fields
            val profileName: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_NAME]?.returnNullIfMissing()?.asText()
            val definitionName: String? = prefixInputParamsMap[INPUT_K8S_DEFINITION_NAME]?.returnNullIfMissing()?.asText()
            val definitionVersion: String? = prefixInputParamsMap[INPUT_K8S_DEFINITION_VERSION]?.returnNullIfMissing()?.asText()

            val k8sProfileUploadConfiguration = K8sProfileUploadConfiguration(bluePrintPropertiesService)

            // Creating API connector
            var api = K8sPluginApi(
                k8sProfileUploadConfiguration.getProperties().username,
                k8sProfileUploadConfiguration.getProperties().password,
                k8sProfileUploadConfiguration.getProperties().url,
                definitionName,
                definitionVersion
            )

            if ((profileName == null) || (definitionName == null) || (definitionVersion == null)) {
                log.warn("Prefix $prefix does not have required data for us to continue.")
            } else if (!api.hasDefinition()) {
                throw BluePrintProcessorException("K8s RB Definition ($definitionName/$definitionVersion) not found ")
            } else if (profileName == "") {
                log.warn("K8s rb profile name is empty! Either define profile name to use or choose default")
            } else if (api.hasProfile(profileName)) {
                log.info("Profile Already Existing - skipping upload")
            } else {
                log.info("Uploading K8s Profile..")
                outputPrefixStatuses.put(prefix, OUTPUT_ERROR)
                val profileNamespace: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_NAMESPACE]?.returnNullIfMissing()?.asText()
                val profileK8sVersion: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_K8S_VERSION]?.returnNullIfMissing()?.asText()
                var profileSource: String? = prefixInputParamsMap[INPUT_K8S_PROFILE_SOURCE]?.returnNullIfMissing()?.asText()
                if (profileNamespace == null)
                    throw BluePrintProcessorException("Profile $profileName namespace is missing")
                if (profileSource == null) {
                    profileSource = profileName
                    log.info("Profile name used instead of profile source")
                }
                val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
                val artifact: ArtifactDefinition = bluePrintContext.nodeTemplateArtifact(nodeTemplateName, profileSource)
                if (artifact.type != BluePrintConstants.MODEL_TYPE_ARTIFACT_K8S_PROFILE)
                    throw BluePrintProcessorException(
                        "Unexpected profile artifact type for profile source " +
                            "$profileSource. Expecting: $artifact.type"
                    )
                var profile = K8sProfile()
                profile.profileName = profileName
                profile.rbName = definitionName
                profile.rbVersion = definitionVersion
                profile.namespace = profileNamespace
                if (profileK8sVersion != null)
                    profile.kubernetesVersion = profileK8sVersion
                val profileFilePath: Path = prepareProfileFile(profileName, profileSource, artifact.file)
                api.createProfile(profile)
                api.uploadProfileContent(profile, profileFilePath)

                log.info("K8s Profile Upload Completed")
                outputPrefixStatuses.put(prefix, OUTPUT_UPLOADED)
            }
        }
        bluePrintRuntimeService.setNodeTemplateAttributeValue(
            nodeTemplateName,
            OUTPUT_STATUSES,
            outputPrefixStatuses.asJsonNode()
        )
    }

    override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
        bluePrintRuntimeService.getBluePrintError().addError(runtimeException.message!!)
    }

    private fun getTemplatePrefixList(node: JsonNode?): ArrayList<String> {
        var result = ArrayList<String>()
        when (node) {
            is ArrayNode -> {
                val arrayNode = node.toList()
                for (prefixNode in arrayNode)
                    result.add(prefixNode.asText())
            }
            is ObjectNode -> {
                result.add(node.asText())
            }
        }
        return result
    }

    private suspend fun prepareProfileFile(k8sRbProfileName: String, ks8ProfileSource: String, ks8ProfileLocation: String): Path {
        val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
        val bluePrintBasePath: String = bluePrintContext.rootPath
        val profileSourceFileFolderPath: Path = Paths.get(
            bluePrintBasePath.plus(File.separator).plus(ks8ProfileLocation)
        )

        if (profileSourceFileFolderPath.toFile().exists() && !profileSourceFileFolderPath.toFile().isDirectory)
            return profileSourceFileFolderPath
        else if (profileSourceFileFolderPath.toFile().exists()) {
            log.info("Profile building started from source $ks8ProfileSource")
            val properties: MutableMap<String, Any> = mutableMapOf()
            properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = false
            properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = ""
            properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = ""
            properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = ""
            properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1
            properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false
            val resolutionResult: Pair<String, JsonNode> = resourceResolutionService.resolveResources(
                bluePrintRuntimeService,
                nodeTemplateName,
                ks8ProfileSource,
                properties
            )
            val tempMainPath: File = createTempDir("k8s-profile-", "")
            val tempProfilePath: File = createTempDir("content-", "", tempMainPath)

            try {
                val manifestFiles: ArrayList<File>? = readManifestFiles(
                    profileSourceFileFolderPath.toFile(),
                    tempProfilePath
                )
                if (manifestFiles != null) {
                    templateLocation(
                        profileSourceFileFolderPath.toFile(), resolutionResult.second,
                        tempProfilePath, manifestFiles
                    )
                } else
                    throw BluePrintProcessorException("Manifest file is missing")
                // Preparation of the final profile content
                val finalProfileFilePath = Paths.get(
                    tempMainPath.toString().plus(File.separator).plus(
                        "$k8sRbProfileName.tar.gz"
                    )
                )
                if (!BluePrintArchiveUtils.compress(
                        tempProfilePath, finalProfileFilePath.toFile(),
                        ArchiveType.TarGz
                    )
                ) {
                    throw BluePrintProcessorException("Profile compression has failed")
                }
                FileUtils.deleteDirectory(tempProfilePath)

                return finalProfileFilePath
            } catch (t: Throwable) {
                FileUtils.deleteDirectory(tempMainPath)
                throw t
            }
        } else
            throw BluePrintProcessorException("Profile source $ks8ProfileLocation is missing in CBA folder")
    }

    private fun readManifestFiles(profileSource: File, destinationFolder: File): ArrayList<File>? {
        val directoryListing: Array<File>? = profileSource.listFiles()
        var result: ArrayList<File>? = null
        if (directoryListing != null) {
            for (child in directoryListing) {
                if (!child.isDirectory && child.name.toLowerCase() == "manifest.yaml") {
                    child.bufferedReader().use { inr ->
                        val manifestYaml = Yaml()
                        val manifestObject: Map<String, Any> = manifestYaml.load(inr)
                        val typeObject: MutableMap<String, Any>? = manifestObject["type"] as MutableMap<String, Any>?
                        if (typeObject != null) {
                            result = ArrayList<File>()
                            val valuesObject = typeObject["values"]
                            if (valuesObject != null) {
                                result!!.add(File(destinationFolder.toString().plus(File.separator).plus(valuesObject)))
                                result!!.add(File(destinationFolder.toString().plus(File.separator).plus(child.name)))
                            }
                            (typeObject["configresource"] as ArrayList<*>?)?.forEach { item ->
                                val fileInfo: Map<String, Any> = item as Map<String, Any>
                                val filePath = fileInfo["filepath"]
                                val chartPath = fileInfo["chartpath"]
                                if (filePath == null || chartPath == null)
                                    log.error("One configresource in manifest was skipped because of the wrong format")
                                else {
                                    result!!.add(File(destinationFolder.toString().plus(File.separator).plus(filePath)))
                                }
                            }
                        }
                    }
                    break
                }
            }
        }
        return result
    }

    private fun templateLocation(
        location: File,
        params: JsonNode,
        destinationFolder: File,
        manifestFiles: ArrayList<File>
    ) {
        val directoryListing: Array<File>? = location.listFiles()
        if (directoryListing != null) {
            for (child in directoryListing) {
                var newDestinationFolder = destinationFolder.toPath()
                if (child.isDirectory)
                    newDestinationFolder = Paths.get(destinationFolder.toString().plus(File.separator).plus(child.name))

                templateLocation(child, params, newDestinationFolder.toFile(), manifestFiles)
            }
        } else if (!location.isDirectory) {
            if (location.extension.toLowerCase() == "vtl") {
                templateFile(location, params, destinationFolder, manifestFiles)
            } else {
                val finalFilePath = Paths.get(
                    destinationFolder.path.plus(File.separator)
                        .plus(location.name)
                ).toFile()
                if (isFileInTheManifestFiles(finalFilePath, manifestFiles)) {
                    if (!destinationFolder.exists())
                        Files.createDirectories(destinationFolder.toPath())
                    FileUtils.copyFile(location, finalFilePath)
                }
            }
        }
    }

    private fun isFileInTheManifestFiles(file: File, manifestFiles: ArrayList<File>): Boolean {
        manifestFiles.forEach { fileFromManifest ->
            if (fileFromManifest.toString().toLowerCase() == file.toString().toLowerCase())
                return true
        }
        return false
    }

    private fun templateFile(
        templatedFile: File,
        params: JsonNode,
        destinationFolder: File,
        manifestFiles: ArrayList<File>
    ) {
        val finalFile = File(
            destinationFolder.path.plus(File.separator)
                .plus(templatedFile.nameWithoutExtension)
        )
        if (!isFileInTheManifestFiles(finalFile, manifestFiles))
            return
        val fileContent = templatedFile.bufferedReader().readText()
        val finalFileContent = BluePrintVelocityTemplateService.generateContent(
            fileContent,
            params, true
        )
        if (!destinationFolder.exists())
            Files.createDirectories(destinationFolder.toPath())
        finalFile.bufferedWriter().use { out -> out.write(finalFileContent) }
    }
}