5G Core Network Service CBA package upload
[ccsdk/cds.git] / components / model-catalog / blueprint-model / service-blueprint / 5GC_Simulator_CNF_CDS / Scripts / kotlin / KotlinDayOneConfig.kt
1 /*
2 * Copyright © 2019 TechMahindra
3 * Author: Malinconico Aniello Paolo, Vamshi Namilikonda, Thamlur Raju
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.onap.ccsdk.cds.blueprintsprocessor.services.execution.scripts
18
19 import com.fasterxml.jackson.annotation.JsonIgnore
20 import com.fasterxml.jackson.annotation.JsonProperty
21 import com.fasterxml.jackson.databind.ObjectMapper
22 import com.fasterxml.jackson.databind.node.ObjectNode
23 import java.io.File
24 import java.nio.file.Path
25 import java.nio.file.Paths
26 import org.apache.commons.io.FilenameUtils
27 import org.apache.commons.io.IOUtils
28 import org.apache.http.client.ClientProtocolException
29 import org.apache.http.client.entity.EntityBuilder
30 import org.apache.http.client.methods.HttpPost
31 import org.apache.http.client.methods.HttpUriRequest
32 import org.apache.http.entity.ContentType
33 import org.apache.http.message.BasicHeader
34 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
35 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties
36 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties
37 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BasicAuthRestClientService
38 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
39 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.RestLoggerService
40 import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction
41 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
42 import org.onap.ccsdk.cds.controllerblueprints.core.utils.ArchiveType
43 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils
44 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
45 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.storedContentFromResolvedArtifactNB
46 import org.slf4j.LoggerFactory
47 import org.springframework.http.HttpHeaders
48 import org.springframework.http.HttpMethod
49 import org.springframework.http.MediaType
50 import org.springframework.web.client.RestTemplate
51 import org.yaml.snakeyaml.Yaml
52 import java.util.ArrayList
53 import java.io.IOException
54 import java.util.Base64
55 import java.nio.charset.Charset
56 import java.nio.file.Files
57 import com.google.gson.Gson
58 import com.google.gson.reflect.TypeToken
59
60 open class DayOneConfig : AbstractScriptComponentFunction() {
61
62     private val log = LoggerFactory.getLogger(DayOneConfig::class.java)!!
63
64     override fun getName(): String {
65         return "DayOneConfig"
66     }
67
68     override suspend fun processNB(executionRequest: ExecutionServiceInput) {
69         log.info("DAY-1 Script excution Started")
70
71         val prefix = "baseconfig"
72
73         val baseK8sApiUrl = getDynamicProperties("api-access").get("url").asText()
74         val k8sApiUsername = getDynamicProperties("api-access").get("username").asText()
75         val k8sApiPassword = getDynamicProperties("api-access").get("password").asText()
76
77         log.info("Multi-cloud params $baseK8sApiUrl")
78
79         val aaiApiUrl = getDynamicProperties("aai-access").get("url").asText()
80         val aaiApiUsername = getDynamicProperties("aai-access").get("username").asText()
81         val aaiApiPassword = getDynamicProperties("aai-access").get("password").asText()
82
83         log.info("AAI params $aaiApiUrl")
84
85         val resolution_key = getDynamicProperties("resolution-key").asText()
86
87         val payload = storedContentFromResolvedArtifactNB(resolution_key, prefix)
88
89         val payloadObject = JacksonUtils.jsonNode(payload) as ObjectNode
90
91         val serviceInstanceID: String = getResolvedParameter(payloadObject, "service-instance-id")
92         val vnfID: String = getResolvedParameter(payloadObject, "vnf-id")
93
94         log.info("Get serviceInstanceID $serviceInstanceID")
95         log.info("Get vnfID $vnfID")
96
97         val vnfUrl = aaiApiUrl + "/aai/v19/network/generic-vnfs/generic-vnf/" + vnfID + "/vf-modules";
98
99         val mapOfHeaders = hashMapOf<String, String>()
100         mapOfHeaders.put("Accept", "application/json")
101         mapOfHeaders.put("Content-Type", "application/json")
102         mapOfHeaders.put("x-FromAppId", "SO")
103         mapOfHeaders.put("X-TransactionId", "get_aai_subscr")
104         val basicAuthRestClientProperties: BasicAuthRestClientProperties = BasicAuthRestClientProperties()
105         basicAuthRestClientProperties.username = aaiApiUsername
106         basicAuthRestClientProperties.password = aaiApiPassword
107         basicAuthRestClientProperties.url = vnfUrl
108         basicAuthRestClientProperties.additionalHeaders =mapOfHeaders
109         val basicAuthRestClientService: BasicAuthRestClientService= BasicAuthRestClientService(basicAuthRestClientProperties)
110         try {
111             val resultOfGet: BlueprintWebClientService.WebClientResponse<String> = basicAuthRestClientService.exchangeResource(HttpMethod.GET.name, "", "")
112
113             val aaiBody = resultOfGet.body
114             val aaiPayloadObject = JacksonUtils.jsonNode(aaiBody) as ObjectNode
115
116             for (item in aaiPayloadObject.get("vf-module")) {
117
118                 log.info("item payload Deatils : $item")
119
120                 val isItBaseVfModule = item.get("is-base-vf-module").asText()
121
122                 if(isItBaseVfModule.toBoolean())
123                     continue
124
125                 val vfModuleID: String = item.get("vf-module-id").asText()
126
127                 log.info("AAI Vf-module ID is : $vfModuleID")
128
129                 val vfModuleInvariantID: String = item.get("model-invariant-id").asText()
130
131                 log.info("AAI Vf-module Invariant ID is : $vfModuleInvariantID")
132
133                 val vfModuleUUID: String = item.get("model-version-id").asText()
134
135                 log.info("AAI Vf-module UUID is : $vfModuleUUID")
136
137                 val vfModuleInstance: String = item.get("heat-stack-id").asText()
138
139                 log.info("AAI Vf-module Heat Stack ID : $vfModuleInstance")
140
141                 var delimiter = "/"
142
143                 val Instance = vfModuleInstance.split(delimiter)
144                 val instanceName = Instance[0]
145                 val instanceID = Instance[1]
146                 log.info("instance name is : $instanceName")
147                 log.info("K8S instance ID is : $instanceID")
148
149                 val instanceNameNameArray: List<String> = instanceName.split("..")
150                 val typOfVfmodule = instanceNameNameArray[1]
151                 log.info("Type of vf-module: $typOfVfmodule")
152
153                 val k8sRbProfileName: String = "profile_" + vfModuleID
154
155                 val k8sConfigTemplateName: String = "template_" + vfModuleID
156
157                 val api = K8sConfigTemplateApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleInvariantID, vfModuleUUID, k8sConfigTemplateName)
158
159                 // Check if definition exists
160                 if (!api.hasDefinition()) {
161                     throw BluePrintProcessorException("K8s Config Template ($vfModuleInvariantID/$vfModuleUUID) -  $k8sConfigTemplateName not found ")
162                 }
163
164                 log.info("Config Template name: $k8sConfigTemplateName")
165
166                 if (k8sRbProfileName.equals("")) {
167                     throw BluePrintProcessorException("K8s rb profile name is empty! Either define profile name to use or choose default")
168                 }
169
170                 var configTemplate = K8sConfigTemplate()
171                 configTemplate.templateName = k8sConfigTemplateName
172                 configTemplate.description = " "
173                 configTemplate.ChartName = typOfVfmodule
174                 log.info("Chart name: ${configTemplate.ChartName}")
175
176                 val instanceAPI = K8sInstanceApi(k8sApiUsername, k8sApiPassword, baseK8sApiUrl, vfModuleInvariantID, vfModuleUUID)
177                 val configMapName: String = instanceAPI.getInsnceDetails(instanceID, typOfVfmodule)
178
179                 log.info("configmap retrieved " +typOfVfmodule+ "vfmodule ->"+ configMapName)
180                 modifyTemplate(configMapName, typOfVfmodule)
181
182                 val configTemplateFile: Path = prepareConfigTemplateJson(k8sConfigTemplateName, typOfVfmodule)
183
184                 if (!api.hasConfigTemplate(configTemplate)) {
185                     log.info("K8s Config Template Upload Started")
186                     api.createConfigTemplate(configTemplate)
187                     api.uploadConfigTemplateContent(configTemplate, configTemplateFile)
188                     log.info("K8s Config Template Upload Completed")
189                 }
190             }
191             log.info("DAY-1 Script excution completed")
192         }
193         catch (e: Exception) {
194             log.info("Caught exception trying to get the vnf Details!!")
195            // throw BluePrintProcessorException("${e.message}")
196         }
197     }
198
199     fun prepareConfigTemplateJson(configTemplateName: String, typOfVfmodule: String): Path {
200         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
201         val bluePrintBasePath: String = bluePrintContext.rootPath
202
203         var profileFilePath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus("Templates").plus(File.separator).plus("k8s-profiles").plus(File.separator).plus(typOfVfmodule +"-config-template.tar.gz"))
204         log.info("Reading K8s Config Template file: $profileFilePath")
205
206         val profileFile = profileFilePath.toFile()
207
208         if (!profileFile.exists())
209             throw BluePrintProcessorException("K8s Profile template file $profileFilePath does not exists")
210
211         return profileFilePath
212     }
213
214     fun getResolvedParameter(payload: ObjectNode, keyName: String): String {
215         for (node in payload.get("resource-accumulator-resolved-data").elements()) {
216             if (node.get("param-name").asText().equals(keyName)) {
217                 return node.get("param-value").asText()
218             }
219         }
220         return ""
221     }
222     override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) {
223         log.info("Executing Recovery")
224         bluePrintRuntimeService.getBluePrintError().addError("${runtimeException.message}")
225     }
226
227     fun modifyTemplate(configmapName: String, typOfVfmodule: String): String {
228
229         log.info("Executing modifyTemplate ->")
230
231         val bluePrintContext = bluePrintRuntimeService.bluePrintContext()
232         val bluePrintBasePath: String = bluePrintContext.rootPath
233
234         val destPath: String = "/tmp/config-template-"+typOfVfmodule
235
236         var templateFilePath: Path = Paths.get(bluePrintBasePath.plus(File.separator).plus("Templates").plus(File.separator).plus("k8s-profiles").plus(File.separator).plus(typOfVfmodule +"-config-template.tar.gz"))
237
238         log.info("Reading config template file: ${templateFilePath}")
239         val templateFile = templateFilePath.toFile()
240
241         if (!templateFile.exists())
242             throw BluePrintProcessorException("K8s Profile template file ${templateFilePath} does not exists")
243
244         log.info("Decompressing config template to ${destPath}")
245
246         val decompressedProfile: File = BluePrintArchiveUtils.deCompress(templateFilePath.toFile(),
247                 "${destPath}", ArchiveType.TarGz)
248
249         log.info("${templateFilePath.toString()} decompression completed")
250
251         //Here we update override.yaml file
252
253         val manifestFileName = destPath.plus(File.separator).plus(typOfVfmodule).plus(File.separator).plus("templates").plus(File.separator).plus("configmap.yaml")
254         log.info("Modification of configmap.yaml file at ${manifestFileName.toString()}")
255         var finalManifest = ""
256         File(manifestFileName).bufferedReader().use { inr ->
257             val manifestYaml = Yaml()
258             val manifestObject: Map<String, Any> = manifestYaml.load(inr)
259
260             for((k,v) in manifestObject) {
261                 log.info("manifestObject: ${k}, ${v}" )
262             }
263
264             log.info("Uploaded YAML object")
265
266             val metadata: MutableMap<String, Any> = manifestObject.get("metadata") as MutableMap<String, Any>
267             log.info("Uploaded config YAML object")
268
269             for((k,v) in metadata) {
270                 metadata.put(k, configmapName)
271             }
272
273             finalManifest = manifestYaml.dump(manifestObject)
274         }
275
276         File(manifestFileName).bufferedWriter().use { out -> out.write(finalManifest) }
277
278         log.info(finalManifest)
279
280         log.info("Reading config template file: ${templateFilePath}")
281
282         if (!templateFile.exists())
283             throw BluePrintProcessorException("config template file ${templateFilePath} does not exists")
284
285         val tempMainPath: File = createTempDir("config-template-", "")
286         val tempConfigTemplatePath: File = createTempDir("conftemplate-", "", tempMainPath)
287         log.info("Decompressing profile to ${tempConfigTemplatePath.toString()}")
288
289         val decompressedProfile2: File = BluePrintArchiveUtils.deCompress(templateFilePath.toFile(),
290                 "${tempConfigTemplatePath.toString()}", ArchiveType.TarGz)
291
292         log.info("${templateFilePath.toString()} decompression completed")
293
294         //Here we update configmap.yaml file
295
296         log.info("Modification of configmap.yaml file ")
297         val manifestFileName2 = destPath.toString().plus(File.separator).plus(typOfVfmodule).plus(File.separator).plus("templates").plus(File.separator).plus("configmap.yaml")
298         val destOverrideFile = tempConfigTemplatePath.toString().plus(File.separator).plus(typOfVfmodule).plus(File.separator).plus("templates").plus(File.separator).plus("configmap.yaml")
299         log.info("destination override file ${destOverrideFile}")
300
301         File(manifestFileName2).copyTo(File(destOverrideFile), true)
302
303         if (!BluePrintArchiveUtils.compress(decompressedProfile2, templateFilePath.toFile(),
304                         ArchiveType.TarGz)) {
305             throw BluePrintProcessorException("Profile compression has failed")
306         }
307
308         log.info("${templateFilePath.toString()} compression completed")
309
310         return ""
311     }
312
313     inner class K8sInstanceApi(
314             val username: String,
315             val password: String,
316             val baseUrl: String,
317             val definition: String,
318             val definitionVersion: String
319     ) {
320         private val service: UploadConfigTemplateRestClientService // BasicAuthRestClientService
321
322         init {
323             var mapOfHeaders = hashMapOf<String, String>()
324             mapOfHeaders.put("Accept", "application/json")
325             mapOfHeaders.put("Content-Type", "application/json")
326             mapOfHeaders.put("cache-control", " no-cache")
327             mapOfHeaders.put("Accept", "application/json")
328             var basicAuthRestClientProperties: BasicAuthRestClientProperties = BasicAuthRestClientProperties()
329             basicAuthRestClientProperties.username = username
330             basicAuthRestClientProperties.password = password
331             basicAuthRestClientProperties.url = "$baseUrl/v1/instance"
332             basicAuthRestClientProperties.additionalHeaders = mapOfHeaders
333
334             this.service = UploadConfigTemplateRestClientService(basicAuthRestClientProperties)
335         }
336
337         fun getInsnceDetails(instanceId: String, vfModuleType: String): String {
338             log.info("Executing K8sInstanceApi.getInsnceDetails")
339             try {
340                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "/${instanceId}", "")
341                 print(result)
342                 if (result.status >= 200 && result.status < 300) {
343                     log.info("K8s instance details retrieved, processing it for configmap details")
344                     log.info("response body -> "+result.body.toString())
345                     val cmName: String = processInstanceResponse(result.body, vfModuleType)
346                     return cmName
347                 } else
348                     return ""
349             } catch (e: Exception) {
350                 log.info("Caught exception trying to get k8s instance details")
351                 throw BluePrintProcessorException("${e.message}")
352             }
353         }
354
355         fun processInstanceResponse(response: String, vfModuleType: String): String {
356
357             log.info("K8s instance details retrieved, processing it for configmap details")
358
359             val gson = Gson()
360
361             val startInd = response.indexOf('[')
362             val endInd = response.indexOf(']')
363
364             val subStr = response.substring(startInd, endInd+1)
365
366             val resourceType = object : TypeToken<Array<K8sResources>>() {}.type
367
368             var resources: Array<K8sResources> = gson.fromJson(subStr, resourceType)
369
370             for (resource in resources){
371
372                 if(resource.GVK?.Kind == "ConfigMap" && resource.Name?.contains(vfModuleType)){
373
374                     return resource.Name
375
376                 }
377
378             }
379             return ""
380
381         }
382
383     }
384
385     inner class K8sConfigTemplateApi(
386             val username: String,
387             val password: String,
388             val baseUrl: String,
389             val definition: String,
390             val definitionVersion: String,
391             val configTemplateName: String
392     ) {
393         private val service: UploadConfigTemplateRestClientService // BasicAuthRestClientService
394
395         init {
396             var mapOfHeaders = hashMapOf<String, String>()
397             mapOfHeaders.put("Accept", "application/json")
398             mapOfHeaders.put("Content-Type", "application/json")
399             mapOfHeaders.put("cache-control", " no-cache")
400             mapOfHeaders.put("Accept", "application/json")
401             var basicAuthRestClientProperties: BasicAuthRestClientProperties = BasicAuthRestClientProperties()
402             basicAuthRestClientProperties.username = username
403             basicAuthRestClientProperties.password = password
404             basicAuthRestClientProperties.url = "$baseUrl/v1/rb/definition/$definition/$definitionVersion"
405             basicAuthRestClientProperties.additionalHeaders = mapOfHeaders
406
407             this.service = UploadConfigTemplateRestClientService(basicAuthRestClientProperties)
408         }
409
410         fun hasDefinition(): Boolean {
411             try {
412                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "", "")
413                 print(result)
414                 if (result.status >= 200 && result.status < 300)
415                     return true
416                 else
417                     return false
418             } catch (e: Exception) {
419                 log.info("Caught exception trying to get k8s config trmplate  definition")
420                 throw BluePrintProcessorException("${e.message}")
421             }
422         }
423
424         fun hasConfigTemplate(profile: K8sConfigTemplate): Boolean {
425             try {
426                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(HttpMethod.GET.name, "/config-template/${profile.templateName}", "")
427                 print(result)
428                 if (result.status >= 200 && result.status < 300) {
429                     log.info("ConfigTemplate already exists")
430                     return true
431                 } else
432                     return false
433             } catch (e: Exception) {
434                 log.info("Caught exception trying to get k8s config trmplate  definition")
435                 throw BluePrintProcessorException("${e.message}")
436             }
437         }
438
439         fun createConfigTemplate(profile: K8sConfigTemplate) {
440             val objectMapper = ObjectMapper()
441             val profileJsonString: String = objectMapper.writeValueAsString(profile)
442             try {
443                 val result: BlueprintWebClientService.WebClientResponse<String> = service.exchangeResource(
444                         HttpMethod.POST.name,
445                         "/config-template",
446                         profileJsonString
447                 )
448
449                 if (result.status >= 200 && result.status < 300) {
450                     log.info("Config template json info uploaded correctly")
451                 } else if (result.status < 200 || result.status >= 300) {
452                     log.info("Config template already exists")
453                 }
454             } catch (e: Exception) {
455                 log.info("Caught exception trying to create k8s config template ${profile.templateName}  - updated")
456             //    throw BluePrintProcessorException("${e.message}")
457             }
458         }
459
460         fun uploadConfigTemplateContent(profile: K8sConfigTemplate, filePath: Path) {
461             try {
462                 val result: BlueprintWebClientService.WebClientResponse<String> = service.uploadBinaryFile(
463                         "/config-template/${profile.templateName}/content",
464                         filePath
465                 )
466                 if (result.status < 200 || result.status >= 300) {
467                     throw Exception(result.body)
468                 }
469             } catch (e: Exception) {
470                 log.info("Caught exception trying to upload k8s config template ${profile.templateName}")
471                 throw BluePrintProcessorException("${e.message}")
472             }
473         }
474     }
475 }
476
477 class UploadConfigTemplateRestClientService(
478         private val restClientProperties:
479         BasicAuthRestClientProperties
480 ) : BlueprintWebClientService {
481
482     override fun defaultHeaders(): Map<String, String> {
483
484         val encodedCredentials = setBasicAuth(
485                 restClientProperties.username,
486                 restClientProperties.password
487         )
488         return mapOf(
489                 HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE,
490                 HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE,
491                 HttpHeaders.AUTHORIZATION to "Basic $encodedCredentials"
492         )
493     }
494
495     override fun host(uri: String): String {
496         return restClientProperties.url + uri
497     }
498
499     override fun convertToBasicHeaders(headers: Map<String, String>):
500             Array<BasicHeader> {
501         val customHeaders: MutableMap<String, String> = headers.toMutableMap()
502         // inject additionalHeaders
503         customHeaders.putAll(verifyAdditionalHeaders(restClientProperties))
504
505         if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
506             val encodedCredentials = setBasicAuth(
507                     restClientProperties.username,
508                     restClientProperties.password
509             )
510             customHeaders[HttpHeaders.AUTHORIZATION] =
511                     "Basic $encodedCredentials"
512         }
513         return super.convertToBasicHeaders(customHeaders)
514     }
515
516     private fun setBasicAuth(username: String, password: String): String {
517         val credentialsString = "$username:$password"
518         return Base64.getEncoder().encodeToString(
519                 credentialsString.toByteArray(Charset.defaultCharset())
520         )
521     }
522
523     @Throws(IOException::class, ClientProtocolException::class)
524     private fun performHttpCall(httpUriRequest: HttpUriRequest): BlueprintWebClientService.WebClientResponse<String> {
525         val httpResponse = httpClient().execute(httpUriRequest)
526         val statusCode = httpResponse.statusLine.statusCode
527         httpResponse.entity.content.use {
528             val body = IOUtils.toString(it, Charset.defaultCharset())
529             return BlueprintWebClientService.WebClientResponse(statusCode, body)
530         }
531     }
532
533     fun uploadBinaryFile(path: String, filePath: Path): BlueprintWebClientService.WebClientResponse<String> {
534         val convertedHeaders: Array<BasicHeader> = convertToBasicHeaders(defaultHeaders())
535         val httpPost = HttpPost(host(path))
536         val entity = EntityBuilder.create().setBinary(Files.readAllBytes(filePath)).build()
537         httpPost.setEntity(entity)
538         RestLoggerService.httpInvoking(convertedHeaders)
539         httpPost.setHeaders(convertedHeaders)
540         return performHttpCall(httpPost)
541     }
542 }
543
544 class K8sConfigTemplate {
545     @get:JsonProperty("template-name")
546     var templateName: String? = null
547     @get:JsonProperty("description")
548     var description: String? = null
549     @get:JsonProperty("ChartName")
550     var ChartName: String? = null
551
552     override fun equals(other: Any?): Boolean {
553         if (this === other) return true
554         if (javaClass != other?.javaClass) return false
555         return true
556     }
557
558     override fun hashCode(): Int {
559         return javaClass.hashCode()
560     }
561 }
562
563 class K8sResources {
564
565     var GVK: GVK? = null
566     lateinit var Name: String
567
568 }
569
570 class GVK {
571
572     var Group: String? = null
573     var Version: String? = null
574     var Kind: String? = null
575
576 }
577
578 fun main(args: Array<String>) {
579
580     val kotlin = DayOneConfig()
581
582     kotlin.modifyTemplate("modified", "upf")
583
584 }