From 1838366d51a172b0240a5e23c5d691b544a7a6be Mon Sep 17 00:00:00 2001 From: Dhrumin Desai Date: Mon, 9 Aug 2021 16:52:04 -0400 Subject: [PATCH] Helm-generator - seedcode delivery for helm chart generation tool from component spec Issue-ID: DCAEGEN2-2694 Change-Id: I3e317e312f90b061c0cdff155b8673967aea414b Signed-off-by: Dhrumin Desai --- mod2/helm-generator/.gitignore | 33 + mod2/helm-generator/Changelog.md | 11 + mod2/helm-generator/README.md | 20 + mod2/helm-generator/helmchartgenerator-cli/pom.xml | 38 + .../HelmChartGeneratorApplication.java | 97 ++ .../src/main/resources/Usage.txt | 32 + .../blueprint/addons/templates/certificates.yaml | 21 + .../src/test/input/blueprint/base/Chart.yaml | 39 + .../input/blueprint/base/templates/configmap.yaml | 19 + .../input/blueprint/base/templates/deployment.yaml | 18 + .../input/blueprint/base/templates/secret.yaml | 19 + .../input/blueprint/base/templates/service.yaml | 19 + .../src/test/input/blueprint/base/values.yaml | 96 ++ .../src/test/input/specs/invalidSpecNoHelm.json | 419 +++++++ .../test/input/specs/invalidSpecNoServices.json | 432 ++++++++ .../src/test/input/specs/invalidSpecSchema.json | 382 +++++++ .../input/specs/schemas/component-spec-schema.json | 1158 +++++++++++++++++++ .../src/test/input/specs/ves.json | 455 ++++++++ .../src/test/input/specs/vesWithDb.json | 452 ++++++++ .../specs/vescollector-componentspec-v3-helm.json | 475 ++++++++ .../HelmChartGeneratorApplicationTests.java | 36 + .../helm-generator/helmchartgenerator-core/pom.xml | 16 + .../platform/helmchartgenerator/Utils.java | 105 ++ .../chartbuilder/ChartBuilder.java | 68 ++ .../chartbuilder/ChartGenerator.java | 73 ++ .../chartbuilder/ComponentSpecParser.java | 331 ++++++ .../chartbuilder/HelmClient.java | 31 + .../chartbuilder/HelmClientImpl.java | 119 ++ .../chartbuilder/KeyValueMerger.java | 94 ++ .../helmchartgenerator/config/YamlConfig.java | 45 + .../distribution/ChartDistributor.java | 29 + .../distribution/ChartMuseumDistributor.java | 79 ++ .../models/chartinfo/ChartInfo.java | 35 + .../models/chartinfo/Metadata.java | 32 + .../models/componentspec/base/Auxilary.java | 76 ++ .../models/componentspec/base/ComponentSpec.java | 54 + .../models/componentspec/common/Artifacts.java | 41 + .../models/componentspec/common/Calls.java | 43 + .../models/componentspec/common/ConfigVolume.java | 34 + .../models/componentspec/common/Constraints.java | 61 + .../models/componentspec/common/Container.java | 39 + .../models/componentspec/common/EntrySchema.java | 47 + .../models/componentspec/common/HealthCheck.java | 54 + .../models/componentspec/common/Helm.java | 35 + .../models/componentspec/common/Host.java | 39 + .../models/componentspec/common/Parameters.java | 68 ++ .../models/componentspec/common/Policy.java | 47 + .../models/componentspec/common/PolicyInfo.java | 38 + .../models/componentspec/common/PolicySchema.java | 51 + .../models/componentspec/common/Provides.java | 44 + .../models/componentspec/common/Publishes.java | 50 + .../models/componentspec/common/Reconfigs.java | 47 + .../componentspec/common/RequestResponse.java | 39 + .../models/componentspec/common/Self.java | 47 + .../models/componentspec/common/Service.java | 43 + .../models/componentspec/common/Streams.java | 41 + .../models/componentspec/common/Subscribes.java | 49 + .../models/componentspec/common/TlsInfo.java | 43 + .../models/componentspec/common/Volumes.java | 49 + .../ChartTemplateStructureValidator.java | 60 + .../validation/ComponentSpecValidator.java | 29 + .../validation/ComponentSpecValidatorImpl.java | 108 ++ .../src/main/resources/application.properties | 5 + .../src/main/resources/component-spec-schema.json | 1162 ++++++++++++++++++++ .../blueprint/addons/templates/certificates.yaml | 21 + .../src/test/input/blueprint/base/Chart.yaml | 39 + .../input/blueprint/base/charts/common-8.0.0.tgz | Bin 0 -> 91901 bytes .../base/charts/dcaegen2-services-common-8.0.0.tgz | Bin 0 -> 28389 bytes .../input/blueprint/base/charts/postgres-8.0.0.tgz | Bin 0 -> 29791 bytes .../blueprint/base/charts/readinessCheck-8.0.0.tgz | Bin 0 -> 24803 bytes .../base/charts/repositoryGenerator-8.0.0.tgz | Bin 0 -> 2755 bytes .../input/blueprint/base/templates/configmap.yaml | 19 + .../input/blueprint/base/templates/deployment.yaml | 18 + .../input/blueprint/base/templates/secret.yaml | 19 + .../input/blueprint/base/templates/service.yaml | 19 + .../src/test/input/blueprint/base/values.yaml | 96 ++ .../src/test/input/specs/invalidSpecNoHelm.json | 419 +++++++ .../test/input/specs/invalidSpecNoServices.json | 432 ++++++++ .../src/test/input/specs/invalidSpecSchema.json | 382 +++++++ .../input/specs/schemas/component-spec-schema.json | 1158 +++++++++++++++++++ .../src/test/input/specs/ves.json | 455 ++++++++ .../src/test/input/specs/vesWithDb.json | 452 ++++++++ .../specs/vescollector-componentspec-v3-helm.json | 475 ++++++++ .../ChartTemplateStructureValidatorTest.java | 40 + .../ComponentSpecParserTest.java | 186 ++++ .../ComponentSpecValidatorTest.java | 68 ++ mod2/helm-generator/lombok.config | 1 + mod2/helm-generator/pom.xml | 129 +++ 88 files changed, 12299 insertions(+) create mode 100644 mod2/helm-generator/.gitignore create mode 100644 mod2/helm-generator/Changelog.md create mode 100644 mod2/helm-generator/README.md create mode 100644 mod2/helm-generator/helmchartgenerator-cli/pom.xml create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/HelmChartGeneratorApplication.java create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/main/resources/Usage.txt create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/addons/templates/certificates.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/Chart.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/configmap.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/deployment.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/secret.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/service.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/values.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/invalidSpecNoHelm.json create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/invalidSpecNoServices.json create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/invalidSpecSchema.json create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/schemas/component-spec-schema.json create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/ves.json create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/vesWithDb.json create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/vescollector-componentspec-v3-helm.json create mode 100644 mod2/helm-generator/helmchartgenerator-cli/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/HelmChartGeneratorApplicationTests.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/pom.xml create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/Utils.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ChartBuilder.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ChartGenerator.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ComponentSpecParser.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/HelmClient.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/HelmClientImpl.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/KeyValueMerger.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/config/YamlConfig.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/distribution/ChartDistributor.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/distribution/ChartMuseumDistributor.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/chartinfo/ChartInfo.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/chartinfo/Metadata.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/base/Auxilary.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/base/ComponentSpec.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Artifacts.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Calls.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/ConfigVolume.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Constraints.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Container.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/EntrySchema.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/HealthCheck.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Helm.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Host.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Parameters.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Policy.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/PolicyInfo.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/PolicySchema.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Provides.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Publishes.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Reconfigs.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/RequestResponse.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Self.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Service.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Streams.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Subscribes.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/TlsInfo.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Volumes.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ChartTemplateStructureValidator.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ComponentSpecValidator.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ComponentSpecValidatorImpl.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/resources/application.properties create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/main/resources/component-spec-schema.json create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/addons/templates/certificates.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/Chart.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/common-8.0.0.tgz create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/dcaegen2-services-common-8.0.0.tgz create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/postgres-8.0.0.tgz create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/readinessCheck-8.0.0.tgz create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/repositoryGenerator-8.0.0.tgz create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/configmap.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/deployment.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/secret.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/service.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/values.yaml create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/invalidSpecNoHelm.json create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/invalidSpecNoServices.json create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/invalidSpecSchema.json create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/schemas/component-spec-schema.json create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/ves.json create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/vesWithDb.json create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/vescollector-componentspec-v3-helm.json create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ChartTemplateStructureValidatorTest.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ComponentSpecParserTest.java create mode 100644 mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ComponentSpecValidatorTest.java create mode 100644 mod2/helm-generator/lombok.config create mode 100644 mod2/helm-generator/pom.xml diff --git a/mod2/helm-generator/.gitignore b/mod2/helm-generator/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/mod2/helm-generator/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/mod2/helm-generator/Changelog.md b/mod2/helm-generator/Changelog.md new file mode 100644 index 0000000..d38f0b4 --- /dev/null +++ b/mod2/helm-generator/Changelog.md @@ -0,0 +1,11 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + +## [1.0.0] + +* Helm chart generator seed code introduction \ No newline at end of file diff --git a/mod2/helm-generator/README.md b/mod2/helm-generator/README.md new file mode 100644 index 0000000..6304ebb --- /dev/null +++ b/mod2/helm-generator/README.md @@ -0,0 +1,20 @@ +## Instructions for running helm chart generator +version: 1.0.0-SNAPSHOT + +1. Must have helm installed. + +2. Run `mvn clean package` to build and package the code. + +3. Run main class `HelmChartGeneratorApplication` with parameters added to configuration properties, set in the following order: + 1. Spec file location + 2. Chart directory location + 3. Output directory location + 4. Component spec schema (Optional) + 5. --distribute flag (Optional) + 6. --help (to see usage) + + Arguments expected as: ` --distribute` + + Test files currently included in project: + - Spec file: `helm-chart-generator\src\test\input\specs\ves.json` + - Charts Directory: `helm-chart-generator\src\test\input\blueprint` \ No newline at end of file diff --git a/mod2/helm-generator/helmchartgenerator-cli/pom.xml b/mod2/helm-generator/helmchartgenerator-cli/pom.xml new file mode 100644 index 0000000..3c6de24 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.onap.dcaegen2.platform + helmchartgenerator + 1.0.0-SNAPSHOT + + helmchartgenerator-cli + 1.0.0-SNAPSHOT + helmchartgenerator-cli + + UTF-8 + + + + org.onap.dcaegen2.platform + helmchartgenerator-core + 1.0.0-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/HelmChartGeneratorApplication.java b/mod2/helm-generator/helmchartgenerator-cli/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/HelmChartGeneratorApplication.java new file mode 100644 index 0000000..617947f --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/HelmChartGeneratorApplication.java @@ -0,0 +1,97 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator; + +import lombok.extern.slf4j.Slf4j; +import org.onap.dcaegen2.platform.helmchartgenerator.chartbuilder.ChartBuilder; +import org.onap.dcaegen2.platform.helmchartgenerator.distribution.ChartDistributor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.io.ClassPathResource; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Main class to run the application. + * @author Dhrumin Desai + */ +@SpringBootApplication +@Slf4j +public class HelmChartGeneratorApplication implements CommandLineRunner { + + @Autowired + private final ChartBuilder builder; + + @Autowired + private final ChartDistributor distributor; + + public HelmChartGeneratorApplication(ChartBuilder builder, ChartDistributor distributor) { + this.builder = builder; + this.distributor = distributor; + } + + public static void main(String[] args) { + SpringApplication.run(HelmChartGeneratorApplication.class, args); + } + + @Override + public void run(String... args) throws Exception { + List argList = new ArrayList<>(Arrays.asList(args)); + boolean isDistribute = false; + if(argList.contains("--help") || argList.size() < 3){ + printUsage(); + return; + } + if(argList.contains("--distribute")){ + isDistribute = true; + argList.remove("--distribute"); + } + + log.info("STARTED HELM GENERATION:"); + final File chartPackage = builder.build(argList.get(0), argList.get(1), argList.get(2), + getSpecSchemaLocation(argList)); + if(isDistribute) { + log.info("Distributing.."); + distributor.distribute(chartPackage); + } + } + + private String getSpecSchemaLocation(List argList) { + String specSchemaLocation; + try { + specSchemaLocation = argList.get(3); + } + catch (Exception e) { + specSchemaLocation = ""; + } + return specSchemaLocation; + } + + private void printUsage() throws IOException { + InputStream inputStream = new ClassPathResource("Usage.txt").getInputStream(); + log.info(new String(inputStream.readAllBytes())); + } +} diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/main/resources/Usage.txt b/mod2/helm-generator/helmchartgenerator-cli/src/main/resources/Usage.txt new file mode 100644 index 0000000..7aaec03 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/main/resources/Usage.txt @@ -0,0 +1,32 @@ + +Helm Chart Generator: + +- generate a helm chart from the base helm template and parsed component spec file. +- distribute a helm chart to Chartmuseum + +Environment variables: + +| Name | Description | +|------------------------------------|-----------------------------------------------------------------------------------| +| $CHARTMUSEUM_BASEURL | set a Chartmuseum base url for chart distribution. | +| $CHARTMUSEUM_AUTH_BASIC_USERNAME | set a Chartmuseum username for the basic auth. | +| $CHARTMUSEUM_AUTH_BASIC_PASSWORD | set a Chartmuseum password for the basic auth. | + +Requirements: + +- Helm Chart Generator uses 'helm' command installed on the host machine, so 'helm' command must be installed. +- For the distribution, $CHARTMUSEUM_BASEURL, $CHARTMUSEUM_AUTH_BASIC_USERNAME and $CHARTMUSEUM_AUTH_BASIC_PASSWORD + must be set. + +Usage: + helmchartgenerator-.jar (with JAR) + OR + HelmChartGeneratorApplication.java (with the main class) + + - Arguments must be passed in the numeric order mentioned below. +Arguments: + 1. Spec file location (Required) + 2. Chart directory location (helm template location) (Required) + 3. Output directory location (Required) + 4. Component spec schema (Optional) (Note: Default componentSpec schema will be used if not passed.) + 5. --distribute flag (Optional) diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/addons/templates/certificates.yaml b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/addons/templates/certificates.yaml new file mode 100644 index 0000000..a871343 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/addons/templates/certificates.yaml @@ -0,0 +1,21 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} + +{{ if and .Values.certDirectory .Values.global.cmpv2Enabled .Values.global.CMPv2CertManagerIntegration }} +{{ include "certManagerCertificate.certificate" . }} +{{ end }} diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/Chart.yaml b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/Chart.yaml new file mode 100644 index 0000000..239978f --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/Chart.yaml @@ -0,0 +1,39 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + +apiVersion: v2 +appVersion: "Honolulu" +description: TBD +name: TBD +version: TBD + +dependencies: + - name: common + version: ~8.x-0 + repository: '@local' + - name: repositoryGenerator + version: ~8.x-0 + repository: '@local' + - name: readinessCheck + version: ~8.x-0 + repository: '@local' + - name: dcaegen2-services-common + version: ~8.x-0 + repository: '@local' + - name: postgres + version: ~8.x-0 + repository: '@local' + condition: postgres.enabled diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/configmap.yaml b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/configmap.yaml new file mode 100644 index 0000000..eaa7f34 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/configmap.yaml @@ -0,0 +1,19 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} + +{{ include "dcaegen2-services-common.configMap" . }} diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/deployment.yaml b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/deployment.yaml new file mode 100644 index 0000000..6d554d3 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/deployment.yaml @@ -0,0 +1,18 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} +{{ include "dcaegen2-services-common.microserviceDeployment" . }} \ No newline at end of file diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/secret.yaml b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/secret.yaml new file mode 100644 index 0000000..2162630 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/secret.yaml @@ -0,0 +1,19 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} + +{{ include "common.secretFast" . }} diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/service.yaml b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/service.yaml new file mode 100644 index 0000000..115398a --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/templates/service.yaml @@ -0,0 +1,19 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} + +{{ include "common.service" . }} diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/values.yaml b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/values.yaml new file mode 100644 index 0000000..e83afd3 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/blueprint/base/values.yaml @@ -0,0 +1,96 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + +################################################################# +# Global configuration defaults. +################################################################# +global: + nodePortPrefix: 302 + nodePortPrefixExt: 304 + +################################################################# +# Filebeat configuration defaults. +################################################################# +filebeatConfig: + logstashServiceName: log-ls + logstashPort: 5044 + +################################################################# +# initContainer images. +################################################################# +tlsImage: onap/org.onap.dcaegen2.deployments.tls-init-container:2.1.0 +consulLoaderImage: onap/org.onap.dcaegen2.deployments.consul-loader-container:1.1.0 + +################################################################# +# Application configuration defaults. +################################################################# +# application image +image: TBD #DONE +pullPolicy: Always + +#policy sync image +dcaePolicySyncImage: onap/org.onap.dcaegen2.deployments.dcae-services-policy-sync:1.0.1 + +#postgres enable/disable +postgres: + enabled: false + +# log directory where logging sidecar should look for log files +# if absent, no sidecar will be deployed +#logDirectory: TBD #/opt/app/VESCollector/logs #DONE + +# directory where TLS certs should be stored +# if absent, no certs will be retrieved and stored +#certDirectory: TBD #/opt/app/dcae-certificate #DONE + +# TLS role -- set to true if microservice acts as server +# If true, an init container will retrieve a server cert +# and key from AAF and mount them in certDirectory. +#tlsServer: TBD #DONE + +# dependencies +readinessCheck: + wait_for: + - dcae-config-binding-service + - aaf-cm + +# probe configuration #NEED DISCUSSION +readiness: + initialDelaySeconds: TBD + periodSeconds: TBD + path: TBD + scheme: TBD + port: TBD + +# Resource Limit flavor -By Default using small +flavor: small +# Segregation for Different environment (Small and Large) +resources: + small: + limits: + cpu: 2 + memory: 2Gi + requests: + cpu: 1 + memory: 1Gi + large: + limits: + cpu: 4 + memory: 4Gi + requests: + cpu: 2 + memory: 2Gi + unlimited: {} diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/invalidSpecNoHelm.json b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/invalidSpecNoHelm.json new file mode 100644 index 0000000..f5e27f6 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/invalidSpecNoHelm.json @@ -0,0 +1,419 @@ +{ + "self": { + "version": "1.8.0", + "name": "dcae-ves-collector", + "description": "Collector for receiving VES events through restful interface", + "component_type": "docker" + }, + "streams": { + "subscribes": [], + "publishes": [ + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-fault" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-measurement" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-syslog" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-heartbeat" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-other" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-mobileflow" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-statechange" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-thresholdCrossingAlert" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-voicequality" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-sipsignaling" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-pnfRegistration" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-notification" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-perf3gpp" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-fault-supervision" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-provisioning" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-heartbeat" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-performance-assurance" + } + ] + }, + "services": { + "calls": [], + "provides": [ + { + "route": "/eventListener/v1", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v2", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v3", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v4", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v5", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "5.28.4" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v7", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "7.30.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + } + ] + }, + "parameters": [ + { + "name": "streams_publishes", + "value": { + "ves-fault": { + "dmaap_info": { + "topic_url": "http://message-router:3904/events/unauthenticated.SEC_FAULT_OUTPUT" + }, + "type": "message_router" + } + }, + "description": "standard http port collector will open for listening;", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + } + , + { + "name": "collector.service.port", + "value": 8080, + "description": "standard http port collector will open for listening;", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.service.secure.port", + "value": 8443, + "description": "secure http port collector will open for listening ", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": true + }, + { + "name": "collector.keystore.file.location", + "value": "/opt/app/dcae-certificate/cert.jks", + "description": "fs location of keystore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.keystore.passwordfile", + "value": "/opt/app/dcae-certificate/jks.pass", + "description": "location of keystore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.file.location", + "value": "/opt/app/dcae-certificate/trust.jks", + "description": "fs location of truststore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.passwordfile", + "value": "/opt/app/dcae-certificate/trust.pass", + "description": "location of truststore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.dmaap.streamid", + "value": "fault=ves-fault|syslog=ves-syslog|heartbeat=ves-heartbeat|measurementsForVfScaling=ves-measurement|mobileFlow=ves-mobileflow|other=ves-other|stateChange=ves-statechange|thresholdCrossingAlert=ves-thresholdCrossingAlert|voiceQuality=ves-voicequality|sipSignaling=ves-sipsignaling|notification=ves-notification|pnfRegistration=ves-pnfRegistration|3GPP-FaultSupervision=ves-3gpp-fault-supervision|3GPP-Heartbeat=ves-3gpp-heartbeat|3GPP-Provisioning=ves-3gpp-provisioning|3GPP-PerformanceAssurance=ves-3gpp-performance-assurance", + "description": "domain-to-streamid mapping used by VESCollector to distributes events based on domain. Both primary and secondary config_key are included for resilency (multiple streamid can be included commma separated). The streamids MUST match to topic config_keys. For single site without resiliency deployment - configkeys with -secondary suffix can be removed", + "sourced_at_deployment": true, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "auth.method", + "value": "noAuth", + "description": "Property to manage application mode, possible configurations: noAuth - default option - no security (http) , certOnly - auth by certificate (https), basicAuth - auth by basic auth username and password (https),certBasicAuth - auth by certificate and basic auth username / password (https),", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "header.authlist", + "value": "sample1,$2a$10$pgjaxDzSuc6XVFEeqvxQ5u90DKJnM/u7TJTcinAlFJVaavXMWf/Zi|userid1,$2a$10$61gNubgJJl9lh3nvQvY9X.x4e5ETWJJ7ao7ZhJEvmfJigov26Z6uq|userid2,$2a$10$G52y/3uhuhWAMy.bx9Se8uzWinmbJa.dlm1LW6bYPdPkkywLDPLiy", + "description": "List of id and base 64 encoded password.For each onboarding VNF - unique userid and password should be assigned and communicated to VNF owner. Password value should be base64 encoded in config here", + "policy_editable": false, + "sourced_at_deployment": true, + "designer_editable": true + }, + { + "name": "collector.schema.checkflag", + "value": 1, + "description": "Schema check validation flag. When enabled, collector will validate input VES events against VES Schema defined on collector.schema.file ", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.schema.file", + "value": "{\"v1\":\"./etc/CommonEventFormat_27.2.json\",\"v2\":\"./etc/CommonEventFormat_27.2.json\",\"v3\":\"./etc/CommonEventFormat_27.2.json\",\"v4\":\"./etc/CommonEventFormat_27.2.json\",\"v5\":\"./etc/CommonEventFormat_28.4.1.json\",\"v7\":\"./etc/CommonEventFormat_30.2.1_ONAP.json\"}", + "description": "VES schema file name per version used for validation", + "designer_editable": true, + "sourced_at_deployment": false, + "policy_editable": false + }, + { + "name": "event.transform.flag", + "value": 1, + "description": "flag to enable tranformation rules defined under eventTransform.json; this is applicable when event tranformation rules preset should be activated for transforming ’) the value declared.", + "type": "number" + }, + "greater_or_equal": { + "description": "Constrains a property or parameter to a value greater than or equal to (‘>=’) the value declared.", + "type": "number" + }, + "less_than": { + "description": "Constrains a property or parameter to a value less than (‘<’) the value declared.", + "type": "number" + }, + "less_or_equal": { + "description": "Constrains a property or parameter to a value less than or equal to (‘<=’) the value declared.", + "type": "number" + }, + "valid_values": { + "description": "Constrains a property or parameter to a value that is in the list of declared values.", + "type": "array" + }, + "length": { + "description": "Constrains the property or parameter to a value of a given length.", + "type": "number" + }, + "min_length": { + "description": "Constrains the property or parameter to a value to a minimum length.", + "type": "number" + }, + "max_length": { + "description": "Constrains the property or parameter to a value to a maximum length.", + "type": "number" + } + } + }, + "stream_message_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "message router", + "message_router" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "stream_kafka": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "kafka" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_http": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "http", + "https" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_message_router": { + "$ref": "#/definitions/stream_message_router" + }, + "publisher_data_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "data router", + "data_router" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_kafka": { + "$ref": "#/definitions/stream_kafka" + }, + "subscriber_http": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "route": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "http", + "https" + ] + } + }, + "required": [ + "format", + "version", + "route", + "type" + ] + }, + "subscriber_message_router": { + "$ref": "#/definitions/stream_message_router" + }, + "subscriber_data_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "route": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "data router", + "data_router" + ] + }, + "config_key": { + "description": "Data router subscribers require config info to setup their endpoints to handle requests. For example, needs username and password", + "type": "string" + } + }, + "required": [ + "format", + "version", + "route", + "type", + "config_key" + ] + }, + "subscriber_kafka": { + "$ref": "#/definitions/stream_kafka" + }, + "provider": { + "oneOf": [ + { + "$ref": "#/definitions/docker-provider" + }, + { + "$ref": "#/definitions/cdap-provider" + } + ] + }, + "cdap-provider": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "service_name": { + "type": "string" + }, + "service_endpoint": { + "type": "string" + }, + "verb": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "DELETE" + ] + } + }, + "required": [ + "request", + "response", + "service_name", + "service_endpoint", + "verb" + ] + }, + "docker-provider": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "route": { + "type": "string" + }, + "verb": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "DELETE" + ] + } + }, + "required": [ + "request", + "response", + "route" + ] + }, + "caller": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "config_key": { + "type": "string" + } + }, + "required": [ + "request", + "response", + "config_key" + ] + }, + "formatPair": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + } + } + }, + "name": { + "type": "string" + }, + "version": { + "type": "string", + "pattern": "^(\\d+\\.)(\\d+\\.)(\\*|\\d+)$" + }, + "artifact": { + "type": "object", + "description": "Component artifact object", + "properties": { + "uri": { + "type": "string", + "description": "Uri to artifact" + }, + "type": { + "type": "string", + "enum": [ + "jar", + "docker image" + ] + } + }, + "required": [ + "uri", + "type" + ] + }, + "auxilary_cdap": { + "title": "cdap component specification schema", + "type": "object", + "properties": { + "streamname": { + "type": "string" + }, + "artifact_name": { + "type": "string" + }, + "artifact_version": { + "type": "string", + "pattern": "^(\\d+\\.)(\\d+\\.)(\\*|\\d+)$" + }, + "namespace": { + "type": "string", + "description": "optional" + }, + "programs": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/cdap_program" + } + } + }, + "required": [ + "streamname", + "programs", + "artifact_name", + "artifact_version" + ] + }, + "cdap_program_type": { + "type": "string", + "enum": [ + "flows", + "mapreduce", + "schedules", + "spark", + "workflows", + "workers", + "services" + ] + }, + "cdap_program": { + "type": "object", + "properties": { + "program_type": { + "$ref": "#/definitions/cdap_program_type" + }, + "program_id": { + "type": "string" + } + }, + "required": [ + "program_type", + "program_id" + ] + }, + "auxilary_docker": { + "title": "Docker component specification schema", + "type": "object", + "properties": { + "helm": { + "type": "object", + "properties": { + "applicationEnv": { + "type": "object" + }, + "service": { + "description": "Mapping for kubernetes services", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "NodePort", + "Cluster" + ] + }, + "name": { + "type": "string" + }, + "ports": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": [ + "type", + "name", + "ports" + ] + } + }, + "required": [ + "service" + ] + }, + "healthcheck": { + "description": "Define the health check that Consul should perfom for this component", + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/docker_healthcheck_http" + }, + { + "$ref": "#/definitions/docker_healthcheck_script" + } + ] + }, + "ports": { + "description": "Port mapping to be used for Docker containers. Each entry is of the format :.", + "type": "array", + "items": { + "type": "string" + } + }, + "log_info": { + "description": "Component specific details for logging", + "type": "object", + "properties": { + "log_directory": { + "description": "The path in the container where the component writes its logs. If the component is following the EELF requirements, this would be the directory where the four EELF files are being written. (Other logs can be placed in the directory--if their names in '.log', they'll also be sent into ELK.)", + "type": "string" + }, + "alternate_fb_path": { + "description": "By default, the log volume is mounted at /var/log/onap/ in the sidecar container's file system. 'alternate_fb_path' allows overriding the default. Will affect how the log data can be found in the ELK system.", + "type": "string" + } + }, + "additionalProperties": false + }, + "tls_info": { + "description": "Component information to use tls certificates", + "type": "object", + "properties": { + "cert_directory": { + "description": "The path in the container where the component certificates will be placed by the init container", + "type": "string" + }, + "use_tls": { + "description": "Boolean flag to determine if the application is using tls certificates", + "type": "boolean" + }, + "use_external_tls": { + "description": "Boolean flag to determine if the application is using tls certificates for external communication", + "type": "boolean" + } + }, + "required": [ + "cert_directory", + "use_tls" + ], + "additionalProperties": false + }, + "databases": { + "description": "The databases the application is connecting to using the pgaas", + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "postgres" + ] + } + }, + "policy": { + "properties": { + "trigger_type": { + "description": "Only value of docker is supported at this time.", + "type": "string", + "enum": [ + "docker" + ] + }, + "script_path": { + "description": "Script command that will be executed for policy reconfiguration", + "type": "string" + } + }, + "required": [ + "trigger_type", + "script_path" + ], + "additionalProperties": false + }, + "volumes": { + "description": "Volume mapping to be used for Docker containers. Each entry is of the format below", + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/host_path_volume" + }, + { + "$ref": "#/definitions/config_map_volume" + } + ] + } + } + }, + "required": [ + "healthcheck" + ], + "additionalProperties": false + }, + "host_path_volume": { + "type": "object", + "properties": { + "host": { + "type": "object", + "path": { + "type": "string" + } + }, + "container": { + "type": "object", + "bind": { + "type": "string" + }, + "mode": { + "type": "string" + } + } + }, + "required": [ + "host", + "container" + ] + }, + "config_map_volume": { + "type": "object", + "properties": { + "config_volume": { + "type": "object", + "name": { + "type": "string" + } + }, + "container": { + "type": "object", + "bind": { + "type": "string" + }, + "mode": { + "type": "string" + } + } + }, + "required": [ + "config_volume", + "container" + ] + }, + "docker_healthcheck_http": { + "properties": { + "type": { + "description": "Consul health check type", + "type": "string", + "enum": [ + "http", + "https", + "HTTP", + "HTTPS" + ] + }, + "interval": { + "description": "Interval duration in seconds i.e. 10s", + "default": "15s", + "type": "string" + }, + "timeout": { + "description": "Timeout in seconds i.e. 10s", + "default": "1s", + "type": "string" + }, + "endpoint": { + "description": "Relative endpoint used by Consul to check health by making periodic HTTP GET calls", + "type": "string" + }, + "port": { + "description": "Port mapping for readiness section", + "type": "integer" + }, + "initialDelaySeconds": { + "description": "Initial delay in seconds for readiness section", + "type": "integer" + } + }, + "required": [ + "type", + "endpoint" + ] + }, + "docker_healthcheck_script": { + "properties": { + "type": { + "description": "Consul health check type", + "type": "string", + "enum": [ + "script", + "docker" + ] + }, + "interval": { + "description": "Interval duration in seconds i.e. 10s", + "default": "15s", + "type": "string" + }, + "timeout": { + "description": "Timeout in seconds i.e. 10s", + "default": "1s", + "type": "string" + }, + "script": { + "description": "Script command that will be executed by Consul to check health", + "type": "string" + } + }, + "required": [ + "type", + "script" + ] + } + } +} \ No newline at end of file diff --git a/mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/ves.json b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/ves.json new file mode 100644 index 0000000..3829a1a --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-cli/src/test/input/specs/ves.json @@ -0,0 +1,455 @@ +{ + "self": { + "version": "1.8.0", + "name": "dcae-ves-collector", + "description": "Collector for receiving VES events through restful interface", + "component_type": "docker" + }, + "streams": { + "subscribes": [], + "publishes": [ + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-fault" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-measurement" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-syslog" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-heartbeat" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-other" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-mobileflow" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-statechange" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-thresholdCrossingAlert" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-voicequality" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-sipsignaling" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-pnfRegistration" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-notification" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-perf3gpp" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-fault-supervision" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-provisioning" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-heartbeat" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-performance-assurance" + } + ] + }, + "services": { + "calls": [], + "provides": [ + { + "route": "/eventListener/v1", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v2", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v3", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v4", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v5", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "5.28.4" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v7", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "7.30.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + } + ] + }, + "parameters": [ + { + "name": "streams_publishes", + "value": { + "ves-fault": { + "dmaap_info": { + "topic_url": "http://message-router:3904/events/unauthenticated.SEC_FAULT_OUTPUT" + }, + "type": "message_router" + } + }, + "description": "standard http port collector will open for listening;", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + } + , + { + "name": "collector.service.port", + "value": 8080, + "description": "standard http port collector will open for listening;", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.service.secure.port", + "value": 8443, + "description": "secure http port collector will open for listening ", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": true + }, + { + "name": "collector.keystore.file.location", + "value": "/opt/app/dcae-certificate/cert.jks", + "description": "fs location of keystore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.keystore.passwordfile", + "value": "/opt/app/dcae-certificate/jks.pass", + "description": "location of keystore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.file.location", + "value": "/opt/app/dcae-certificate/trust.jks", + "description": "fs location of truststore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.passwordfile", + "value": "/opt/app/dcae-certificate/trust.pass", + "description": "location of truststore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.dmaap.streamid", + "value": "fault=ves-fault|syslog=ves-syslog|heartbeat=ves-heartbeat|measurementsForVfScaling=ves-measurement|mobileFlow=ves-mobileflow|other=ves-other|stateChange=ves-statechange|thresholdCrossingAlert=ves-thresholdCrossingAlert|voiceQuality=ves-voicequality|sipSignaling=ves-sipsignaling|notification=ves-notification|pnfRegistration=ves-pnfRegistration|3GPP-FaultSupervision=ves-3gpp-fault-supervision|3GPP-Heartbeat=ves-3gpp-heartbeat|3GPP-Provisioning=ves-3gpp-provisioning|3GPP-PerformanceAssurance=ves-3gpp-performance-assurance", + "description": "domain-to-streamid mapping used by VESCollector to distributes events based on domain. Both primary and secondary config_key are included for resilency (multiple streamid can be included commma separated). The streamids MUST match to topic config_keys. For single site without resiliency deployment - configkeys with -secondary suffix can be removed", + "sourced_at_deployment": true, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "auth.method", + "value": "noAuth", + "description": "Property to manage application mode, possible configurations: noAuth - default option - no security (http) , certOnly - auth by certificate (https), basicAuth - auth by basic auth username and password (https),certBasicAuth - auth by certificate and basic auth username / password (https),", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "header.authlist", + "value": "sample1,$2a$10$pgjaxDzSuc6XVFEeqvxQ5u90DKJnM/u7TJTcinAlFJVaavXMWf/Zi|userid1,$2a$10$61gNubgJJl9lh3nvQvY9X.x4e5ETWJJ7ao7ZhJEvmfJigov26Z6uq|userid2,$2a$10$G52y/3uhuhWAMy.bx9Se8uzWinmbJa.dlm1LW6bYPdPkkywLDPLiy", + "description": "List of id and base 64 encoded password.For each onboarding VNF - unique userid and password should be assigned and communicated to VNF owner. Password value should be base64 encoded in config here", + "policy_editable": false, + "sourced_at_deployment": true, + "designer_editable": true + }, + { + "name": "collector.schema.checkflag", + "value": 1, + "description": "Schema check validation flag. When enabled, collector will validate input VES events against VES Schema defined on collector.schema.file ", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.schema.file", + "value": "{\"v1\":\"./etc/CommonEventFormat_27.2.json\",\"v2\":\"./etc/CommonEventFormat_27.2.json\",\"v3\":\"./etc/CommonEventFormat_27.2.json\",\"v4\":\"./etc/CommonEventFormat_27.2.json\",\"v5\":\"./etc/CommonEventFormat_28.4.1.json\",\"v7\":\"./etc/CommonEventFormat_30.2.1_ONAP.json\"}", + "description": "VES schema file name per version used for validation", + "designer_editable": true, + "sourced_at_deployment": false, + "policy_editable": false + }, + { + "name": "event.transform.flag", + "value": 1, + "description": "flag to enable tranformation rules defined under eventTransform.json; this is applicable when event tranformation rules preset should be activated for transforming + + 4.0.0 + + org.onap.dcaegen2.platform + helmchartgenerator + 1.0.0-SNAPSHOT + + helmchartgenerator-core + 1.0.0-SNAPSHOT + helmchartgenerator-core + + UTF-8 + + diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/Utils.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/Utils.java new file mode 100644 index 0000000..83c67b1 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/Utils.java @@ -0,0 +1,105 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +/** + * An utility class for various file related tasks. + * @author Dhrumin Desai + */ +@Slf4j +public class Utils { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private Utils() {} + + public static T deserializeJsonFileToModel(String filePath, Class modelClass) { + return deserializeJsonFileToModel(new File(filePath), modelClass); + } + + /** + * maps json file to a model class + * @param file Json file which holds the data + * @param modelClass target model class for mapping + * @return mapped model instance + */ + public static T deserializeJsonFileToModel(File file, Class modelClass) { + try { + return MAPPER.readValue(file, modelClass); + } catch (IOException e) { + log.error(e.getMessage()); + throw new RuntimeException("Error occurred while converting file to the given model class"); + } + } + + /** + * copies dir/file to a temp location on OS + * @param srcLocation + * @return + */ + public static File cloneFileToTempLocation(String srcLocation) { + File cloneLocation = null; + try { + Path tempRootDir = Files.createTempDirectory("chart"); + cloneLocation = new File(tempRootDir.toString()); + log.info("cloning dir/file at : " + tempRootDir); + FileUtils.copyDirectory(new File(srcLocation), cloneLocation); + } catch (IOException e) { + log.error(e.getMessage()); + throw new RuntimeException("Error occured while cloning file to temp location."); + } + return cloneLocation; + } + + /** + * deletes dir / file from temp location of OS + * @param dir dir to be deleted + */ + public static void deleteTempFileLocation(File dir) { + try { + FileUtils.deleteDirectory(dir); + } catch (IOException e) { + log.warn("Could not delete dir/file: " + dir.getAbsolutePath()); + } + } + + /** + * puts value into a map if only exists + * @param map a map + * @param key a key + * @param value a value + */ + public static void putIfNotNull(Map map, String key, Object value){ + if(value != null){ + map.put(key, value); + } + } + +} + diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ChartBuilder.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ChartBuilder.java new file mode 100644 index 0000000..ac73544 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ChartBuilder.java @@ -0,0 +1,68 @@ + +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.chartbuilder; + +import lombok.extern.slf4j.Slf4j; +import org.onap.dcaegen2.platform.helmchartgenerator.models.chartinfo.ChartInfo; +import org.onap.dcaegen2.platform.helmchartgenerator.validation.ChartTemplateStructureValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.File; + +/** + * ChartBuilder is a main class responsible to generate a helm chart. + * @author Dhrumin Desai + */ +@Component +@Slf4j +public class ChartBuilder { + + @Autowired + private ComponentSpecParser specParser; + + @Autowired + private ChartGenerator chartGenerator; + + /** + * constructor of ChartBuilder + * @param specParser implementation of ComponentSpecParser + * @param chartGenerator implementation of ChartGenerator + */ + public ChartBuilder(ComponentSpecParser specParser, ChartGenerator chartGenerator) { + this.specParser = specParser; + this.chartGenerator = chartGenerator; + } + + /** + * this method validates the inputs and generate a helm chart. + * @param specFileLocation location of the application specification json file + * @param chartTemplateLocation location of the base helm chart template + * @param outputLocation location to store the helm chart + * @param specSchemaLocation location of the specification json schema file to validate the application spec + * @return generated helm chart tgz file + * @throws Exception + */ + public File build(String specFileLocation, String chartTemplateLocation, String outputLocation, String specSchemaLocation ) throws Exception { + ChartTemplateStructureValidator.validateChartTemplateStructure(chartTemplateLocation); + ChartInfo chartInfo = specParser.extractChartInfo(specFileLocation, chartTemplateLocation, specSchemaLocation); + return chartGenerator.generate(chartTemplateLocation, chartInfo, outputLocation); + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ChartGenerator.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ChartGenerator.java new file mode 100644 index 0000000..b9980d7 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ChartGenerator.java @@ -0,0 +1,73 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.chartbuilder; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.onap.dcaegen2.platform.helmchartgenerator.models.chartinfo.ChartInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.File; + +import static org.onap.dcaegen2.platform.helmchartgenerator.Utils.cloneFileToTempLocation; +import static org.onap.dcaegen2.platform.helmchartgenerator.Utils.deleteTempFileLocation; + +/** + * ChartGenerator interacts with HelmClient and generates a packaged helm chart + * @author Dhrumin Desai + */ +@Component +@Slf4j +public class ChartGenerator { + + @Setter + @Autowired + private HelmClient helmClient; + + @Setter + @Autowired + private KeyValueMerger merger; + + /** + * Constructor for ChartGenerator + * @param helmClient HelmClient implementation + * @param merger KeyValueMerger implementation + */ + public ChartGenerator(HelmClient helmClient, KeyValueMerger merger) { + this.helmClient = helmClient; + this.merger = merger; + } + + /** + * Merges the key-values from the helm base template and parsed spec file and generates a new packaged helm chart + * @param chartBlueprintLocation location of the base helm chart template + * @param chartInfo chartInfo object with key-values parsed from the specfile. + * @param outputLocation location to store the helm chart + * @return generated helm chart tgz file + */ + public File generate(String chartBlueprintLocation, ChartInfo chartInfo, String outputLocation) { + File newChartDir = cloneFileToTempLocation(chartBlueprintLocation + "/base"); + merger.mergeValuesToChart(chartInfo, newChartDir); + helmClient.lint(newChartDir); + final File chartLocation = helmClient.packageChart(newChartDir, outputLocation); + deleteTempFileLocation(newChartDir); + return chartLocation; + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ComponentSpecParser.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ComponentSpecParser.java new file mode 100644 index 0000000..942e5ae --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/ComponentSpecParser.java @@ -0,0 +1,331 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.chartbuilder; + +import org.jetbrains.annotations.NotNull; +import org.onap.dcaegen2.platform.helmchartgenerator.Utils; +import org.onap.dcaegen2.platform.helmchartgenerator.models.chartinfo.ChartInfo; +import org.onap.dcaegen2.platform.helmchartgenerator.models.chartinfo.Metadata; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.base.ComponentSpec; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Artifacts; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.HealthCheck; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Parameters; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Policy; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.PolicyInfo; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Self; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Service; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.TlsInfo; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Volumes; +import org.onap.dcaegen2.platform.helmchartgenerator.validation.ComponentSpecValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * ComponentSpecParser reads a componentspec file and collects useful data for helm chart generation. + * @author Dhrumin Desai + */ +@Component +public class ComponentSpecParser { + + @Autowired + private ComponentSpecValidator specValidator; + + /** + * Constructor for ComponentSpecParser + * @param specValidator ComponentSpecValidator implementation + */ + public ComponentSpecParser(ComponentSpecValidator specValidator) { + this.specValidator = specValidator; + } + + /** + * + * @param specFileLocation location of the application specification json file + * @param chartTemplateLocation location of the base helm chart template + * @param specSchemaLocation location of the specification json schema file to validate the application spec + * @return ChartInfo object populated with key-values + * @throws Exception + */ + public ChartInfo extractChartInfo(String specFileLocation, String chartTemplateLocation, String specSchemaLocation) throws Exception { + specValidator.validateSpecFile(specFileLocation, specSchemaLocation); + ComponentSpec cs = Utils.deserializeJsonFileToModel(specFileLocation, ComponentSpec.class); + ChartInfo chartInfo = new ChartInfo(); + chartInfo.setMetadata(extractMetadata(cs.getSelf())); + chartInfo.setValues(extractValues(cs, chartTemplateLocation)); + return chartInfo; + } + + private Map extractValues(ComponentSpec cs, String chartTemplateLocation) { + Map outerValues = new LinkedHashMap<>(); + if(cs.getAuxilary() != null && cs.getAuxilary().getTlsInfo() != null){ + Utils.putIfNotNull(outerValues,"certDirectory", cs.getAuxilary().getTlsInfo().getCertDirectory()); + Utils.putIfNotNull(outerValues, "tlsServer", cs.getAuxilary().getTlsInfo().getUseTls()); + } + if(cs.getAuxilary() != null && cs.getAuxilary().getLogInfo() != null) { + Utils.putIfNotNull(outerValues,"logDirectory", cs.getAuxilary().getLogInfo().get("log_directory")); + } + if(imageUriExistsForFirstArtifact(cs)){ + Utils.putIfNotNull(outerValues,"image", cs.getArtifacts()[0].getUri()); + } + populateApplicationConfigSection(outerValues, cs); + populateReadinessSection(outerValues, cs); + populateApplicationEnvSection(outerValues, cs); + populateServiceSection(outerValues, cs); + populateCertificatesSection(outerValues, cs, chartTemplateLocation); + populatePoliciesSection(outerValues, cs); + populateExternalVolumesSection(outerValues, cs); + populatePostgresSection(outerValues, cs); + populateSecretsSection(outerValues, cs); + return outerValues; + } + + private boolean imageUriExistsForFirstArtifact(ComponentSpec cs) { + final Artifacts[] artifacts = cs.getArtifacts(); + return artifacts != null && artifacts.length > 0 && artifacts[0].getUri() != null; + } + + private void populateApplicationConfigSection(Map outerValues, ComponentSpec cs) { + Map applicationConfig = new LinkedHashMap<>(); + Parameters[] parameters = cs.getParameters(); + for(Parameters param : parameters){ + applicationConfig.put(param.getName(), param.getValue()); + } + Utils.putIfNotNull(outerValues,"applicationConfig", applicationConfig); + } + + private void populateReadinessSection(Map outerValues, ComponentSpec cs) { + + if (!healthCheckExists(cs)) return; + + Map readiness = new LinkedHashMap<>(); + final HealthCheck healthcheck = cs.getAuxilary().getHealthcheck(); + Utils.putIfNotNull(readiness, "initialDelaySeconds", healthcheck.getInitialDelaySeconds()); + + if(healthcheck.getInterval() != null) { + readiness.put("periodSeconds", getSeconds(healthcheck.getInterval(), "interval")); + } + if(healthcheck.getTimeout() != null) { + readiness.put("timeoutSeconds", getSeconds(healthcheck.getTimeout(), "timeout")); + } + + readiness.put("path", healthcheck.getEndpoint()); + readiness.put("scheme", healthcheck.getType()); + readiness.put("port", healthcheck.getPort()); + + outerValues.put("readiness", readiness); + } + + private int getSeconds(String value, String field) { + int seconds = 0; + try { + seconds = Integer.parseInt(value.replaceAll("[^\\d.]", "")); + } + catch (NumberFormatException e){ + throw new RuntimeException(String.format("%s with %s is not given in a correct format", field, value)); + } + return seconds; + } + + private boolean healthCheckExists(ComponentSpec cs) { + return cs.getAuxilary() != null && + cs.getAuxilary().getHealthcheck() != null; + } + + private void populateApplicationEnvSection(Map outerValues, ComponentSpec cs){ + if(applicationEnvExists(cs)) { + Object applicationEnv = cs.getAuxilary().getHelm().getApplicationEnv(); + Utils.putIfNotNull(outerValues,"applicationEnv", applicationEnv); + } + } + + private boolean applicationEnvExists(ComponentSpec cs) { + return cs.getAuxilary() != null && + cs.getAuxilary().getHelm() != null && + cs.getAuxilary().getHelm().getApplicationEnv() != null; + } + + private void populateServiceSection(Map outerValues, ComponentSpec cs) { + if (!serviceExists(cs)) return; + + Map service = new LinkedHashMap<>(); + final Service serviceFromSpec = cs.getAuxilary().getHelm().getService(); + + if(serviceFromSpec.getPorts() != null){ + List ports = mapServicePorts(serviceFromSpec.getPorts()); + service.put("ports", ports); + Utils.putIfNotNull(service, "type", serviceFromSpec.getType()); + } + Utils.putIfNotNull(service,"name", serviceFromSpec.getName()); + Utils.putIfNotNull(service,"has_internal_only_ports", serviceFromSpec.getHasInternalOnlyPorts()); + outerValues.put("service", service); + } + + private boolean serviceExists(ComponentSpec cs) { + return cs.getAuxilary() != null && + cs.getAuxilary().getHelm() != null && + cs.getAuxilary().getHelm().getService() != null; + } + + private List mapServicePorts(Object[] ports) { + List portsList = new ArrayList<>(); + Collections.addAll(portsList, ports); + return portsList; + } + + private void populatePoliciesSection(Map outerValues, ComponentSpec cs) { + Map policies = new LinkedHashMap<>(); + final PolicyInfo policyInfo = cs.getPolicyInfo(); + if(policyInfo != null && policyInfo.getPolicy() != null) { + List policyList = new ArrayList<>(); + for (Policy policyItem : policyInfo.getPolicy()) { + policyList.add('"' + policyItem.getPolicyID() + '"'); + } + policies.put("policyRelease", "onap"); + policies.put("duration", 300); + policies.put("policyID", "'" + policyList.toString() + "'\n"); + outerValues.put("policies", policies); + } + } + + private void populateCertificatesSection(Map outerValues, ComponentSpec cs, String chartTemplateLocation) { + Map certificate = new LinkedHashMap<>(); + Map keystore = new LinkedHashMap<>(); + Map passwordsSecretRef = new LinkedHashMap<>(); + TlsInfo tlsInfo = cs.getAuxilary().getTlsInfo(); + String componentName = getComponentNameWithOmitFirstWord(cs); + if(externalTlsExists(tlsInfo)) { + String mountPath = tlsInfo.getCertDirectory(); + if(tlsInfo.getUseExternalTls() != null && tlsInfo.getUseExternalTls()) { + checkCertificateYamlExists(chartTemplateLocation); + mountPath += "external"; + } + passwordsSecretRef.put("name", componentName + "-cmpv2-keystore-password"); + passwordsSecretRef.put("key", "password"); + passwordsSecretRef.put("create", true); + keystore.put("outputType", List.of("jks")); + keystore.put("passwordSecretRef", passwordsSecretRef); + certificate.put("mountPath", mountPath); + Utils.putIfNotNull(certificate,"commonName", cs.getSelf().getName()); + Utils.putIfNotNull(certificate,"dnsNames", List.of(cs.getSelf().getName())); + certificate.put("keystore", keystore); + outerValues.put("certificates", List.of(certificate)); + } + } + + private String getComponentNameWithOmitFirstWord(ComponentSpec cs) { + return cs.getSelf().getName().substring(cs.getSelf().getName().indexOf("-") + 1); + } + + private boolean externalTlsExists(TlsInfo tlsInfo) { + return tlsInfo != null && tlsInfo.getUseExternalTls() != null && tlsInfo.getUseExternalTls().equals(true); + } + + private void checkCertificateYamlExists(String chartTemplateLocation) { + Path certificateYaml = Paths.get(chartTemplateLocation, "addons/templates/certificates.yaml"); + if(!Files.exists(certificateYaml)) { + throw new RuntimeException("certificates.yaml not found under templates directory in addons"); + } + } + + private void populateExternalVolumesSection(Map outerValues, ComponentSpec cs) { + if(cs.getAuxilary().getVolumes() != null) { + List externalVolumes = new ArrayList<>(); + Volumes[] volumes = cs.getAuxilary().getVolumes(); + for (Volumes volume : volumes) { + if(volume.getHost() == null) { + Map tempVolume = new LinkedHashMap<>(); + tempVolume.put("name", volume.getConfigVolume().getName()); + tempVolume.put("type", "configMap"); + tempVolume.put("mountPath", volume.getContainer().getBind()); + tempVolume.put("optional", true); + externalVolumes.add(tempVolume); + } + } + if(!externalVolumes.isEmpty()) { + outerValues.put("externalVolumes", externalVolumes); + } + } + } + + private void populatePostgresSection(Map outerValues, ComponentSpec cs) { + if(cs.getAuxilary().getDatabases() != null) { + String componentFullName = cs.getSelf().getName(); + String component = getComponentNameWithOmitFirstWord(cs); + Map postgres = new LinkedHashMap<>(); + Map service = new LinkedHashMap<>(); + Map container = new LinkedHashMap<>(); + Map name = new LinkedHashMap<>(); + Map persistence = new LinkedHashMap<>(); + Map config = new LinkedHashMap<>(); + service.put("name", componentFullName + "-postgres"); + service.put("name2", componentFullName + "-pg-primary"); + service.put("name3", componentFullName + "-pg-replica"); + name.put("primary", componentFullName + "-pg-primary"); + name.put("replica", componentFullName + "-pg-replica"); + container.put("name", name); + persistence.put("mountSubPath", componentFullName + "/data"); + persistence.put("mountInitPath", componentFullName); + config.put("pgUserName", component); + config.put("pgDatabase", component); + config.put("pgUserExternalSecret", "{{ include \"common.release\" . }}-" + component + "-pg-user-creds"); + + postgres.put("enabled", true); + postgres.put("nameOverride", componentFullName + "-postgres"); + postgres.put("service", service); + postgres.put("container", container); + postgres.put("persistence", persistence); + postgres.put("config", config); + outerValues.put("postgres", postgres); + } + } + + private void populateSecretsSection(Map outerValues, ComponentSpec cs) { + if(cs.getAuxilary().getDatabases() != null) { + String component = getComponentNameWithOmitFirstWord(cs); + List secrets = new ArrayList<>(); + Map secret = new LinkedHashMap<>(); + secret.put("uid", "pg-user-creds"); + secret.put("name", "{{ include \"common.release\" . }}-" + component + "-pg-user-creds"); + secret.put("type", "basicAuth"); + secret.put("externalSecret", "{{ ternary \"\" (tpl (default \"\" .Values.postgres.config.pgUserExternalSecret) .) (hasSuffix \"" + component + "-pg-user-creds\" .Values.postgres.config.pgUserExternalSecret) }}"); + secret.put("login", "{{ .Values.postgres.config.pgUserName }}"); + secret.put("password", "{{ .Values.postgres.config.pgUserPassword }}"); + secret.put("passwordPolicy", "generate"); + secrets.add(secret); + outerValues.put("secrets", secrets); + } + } + + private Metadata extractMetadata(Self self) { + Metadata metadata = new Metadata(); + metadata.setName(self.getName()); + metadata.setDescription(self.getDescription()); + metadata.setVersion(self.getVersion()); + return metadata; + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/HelmClient.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/HelmClient.java new file mode 100644 index 0000000..bcfe731 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/HelmClient.java @@ -0,0 +1,31 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.chartbuilder; + +import java.io.File; + +/** + * An Interface for HelmClient to perform helm operations + * @author Dhrumin Desai + */ +public interface HelmClient { + void lint(File chartLocation); + + File packageChart(File chartLocation, String outputLocation); +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/HelmClientImpl.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/HelmClientImpl.java new file mode 100644 index 0000000..098ddda --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/HelmClientImpl.java @@ -0,0 +1,119 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.chartbuilder; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * HelmClient implementation which uses helm command installed in the runtime environment. + * @author Dhrumin Desai + */ +@Component +@Slf4j +public class HelmClientImpl implements HelmClient { + + /** + * performs helm lint operation + * @param chartLocation helm chart location + */ + @Override + public void lint(File chartLocation) { + ProcessBuilder builder = new ProcessBuilder(); + builder.command("helm", "lint", chartLocation.getAbsolutePath()); + runProcess(builder, "lint"); + } + + /** + * performs helm package operation + * @param chartLocation helm chart location + * @param outputLocation location to store the generated helm package + * @return generated helm tgz file + */ + @Override + public File packageChart(File chartLocation, String outputLocation) { + ProcessBuilder builder = new ProcessBuilder(); + builder.directory(new File(System.getProperty("user.dir"))); + builder.command("helm", "package", "-d", outputLocation, chartLocation.getAbsolutePath()); + return runProcess(builder, "package"); + } + + private File runProcess(ProcessBuilder builder, String command) { + Process process = null; + String chartPath = ""; + try { + process = builder.start(); + if(command.equals("lint")) { + printLintingProcessOutput(process); + } + else { + chartPath = printPackagingProcessOutput(process); + } + assertExitCode(process); + } catch (IOException e) { + log.error(e.getMessage(), e); + throw new RuntimeException("Error occurred while running helm command."); + } catch (InterruptedException e) { + log.error(e.getMessage(), e); + Thread.currentThread().interrupt(); + throw new RuntimeException("execution interrupted"); + } + return new File(chartPath); + } + + private void printLintingProcessOutput(Process process) throws IOException { + final InputStream inputStream = process.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + reader.lines().forEach(log::info); + inputStream.close(); + } + + private String printPackagingProcessOutput(Process process) throws IOException { + String helmChartPath = ""; + final InputStream inputStream = process.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = reader.readLine()) != null){ + if (line.contains("Successfully packaged chart and saved it to: ")){ + helmChartPath = line.split("Successfully packaged chart and saved it to: ")[1]; + } + log.info(line); + } + inputStream.close(); + if(helmChartPath.isEmpty()){ + throw new RuntimeException("Could not generate the chart."); + } + return helmChartPath; + } + + private void assertExitCode(Process process) throws InterruptedException { + int exitCode = 0; + exitCode = process.waitFor(); + process.destroy(); + if (exitCode != 0){ + throw new RuntimeException("Error occurred while running helm command."); + } + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/KeyValueMerger.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/KeyValueMerger.java new file mode 100644 index 0000000..8a28871 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/chartbuilder/KeyValueMerger.java @@ -0,0 +1,94 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.chartbuilder; + +import lombok.extern.slf4j.Slf4j; +import org.onap.dcaegen2.platform.helmchartgenerator.models.chartinfo.ChartInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.nio.file.Paths; +import java.util.Map; + +/** + * KeyValueMerger merges helm base templates key-values with key-values parsed from component specification file + * @author Dhrumin Desai + */ +@Component +@Slf4j +public class KeyValueMerger { + + @Autowired + private Yaml yaml; + + public KeyValueMerger(Yaml yaml) { + this.yaml = yaml; + } + + /** + * merges helm base templates key-values with key-values parsed from component specification file + * @param chartInfo populated ChartInfo object which holds key-values parsed from component spec file + * @param chartDir location of the base helm chart template + */ + public void mergeValuesToChart(ChartInfo chartInfo, File chartDir) { + mergeChartYamlFile(chartInfo, chartDir); + mergeValuesYamlFile(chartInfo, chartDir); + } + + private void mergeChartYamlFile(ChartInfo chartInfo, File chartDir) { + String chartYamlFilePath = Paths.get(chartDir.getAbsolutePath(), "Chart.yaml").toString(); + checkIfFIleExists(chartYamlFilePath, "Chart.yaml is not found in the given chart template."); + + Map chartYamlKV; + try { + chartYamlKV = yaml.load(new FileInputStream(chartYamlFilePath)); + chartYamlKV.put("name", chartInfo.getMetadata().getName()); + chartYamlKV.put("version", chartInfo.getMetadata().getVersion()); + chartYamlKV.put("description", chartInfo.getMetadata().getDescription()); + yaml.dump(chartYamlKV, new PrintWriter(chartYamlFilePath)); + } catch (FileNotFoundException e) { + log.error(e.getMessage()); + } + } + + private void mergeValuesYamlFile(ChartInfo chartInfo, File chartDir) { + String valuesYamlFilePath = Paths.get(chartDir.getAbsolutePath(), "values.yaml").toString(); + checkIfFIleExists(valuesYamlFilePath, "values.yaml is not found in the given chart template."); + Map valuesYamlKv; + try { + valuesYamlKv = yaml.load(new FileInputStream(valuesYamlFilePath)); + valuesYamlKv.putAll(chartInfo.getValues()); + yaml.dump(valuesYamlKv, new PrintWriter(valuesYamlFilePath)); + } catch (FileNotFoundException e) { + log.error(e.getMessage()); + } + } + + private void checkIfFIleExists(String filePath, String message) { + File valuesYaml = new File(filePath); + if (!valuesYaml.exists()) { + throw new RuntimeException(message); + } + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/config/YamlConfig.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/config/YamlConfig.java new file mode 100644 index 0000000..47a7fbd --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/config/YamlConfig.java @@ -0,0 +1,45 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +/** + * Factory for Yaml object + * @author Dhrumin Desai + */ +@Configuration +public class YamlConfig { + + /** + * creates Yaml instance + * @return constructed Yaml object + */ + @Bean + public Yaml getYamlInstance(){ + DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setPrettyFlow(true); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + return new Yaml(options); + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/distribution/ChartDistributor.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/distribution/ChartDistributor.java new file mode 100644 index 0000000..7bcfefa --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/distribution/ChartDistributor.java @@ -0,0 +1,29 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.distribution; + +import java.io.File; + +/** + * An interface for the helm chart distribution + * @author Dhrumin Desai + */ +public interface ChartDistributor { + void distribute(File chartFile); +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/distribution/ChartMuseumDistributor.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/distribution/ChartMuseumDistributor.java new file mode 100644 index 0000000..ed861b0 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/distribution/ChartMuseumDistributor.java @@ -0,0 +1,79 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.distribution; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.Credentials; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +/** + * Distributes helm chart to Chart Museum through REST + * @author Dhrumin Desai + */ +@Component +@Slf4j +public class ChartMuseumDistributor implements ChartDistributor { + + @Value("${chartmuseum.baseurl}") + private String chartMuseumUrl; + + @Value("${chartmuseum.auth.basic.username}") + private String username; + + @Value("${chartmuseum.auth.basic.password}") + private String password; + + /** + * distributes chart to Chart Museum + * @param chartFile packaged helm chart tgz file + */ + @Override + public void distribute(File chartFile) { + OkHttpClient client = new OkHttpClient().newBuilder().build(); + Request request = createRequestBody(chartFile); + try { + Response response = client.newCall(request).execute(); + log.info(Objects.requireNonNull(response.body()).string()); + } catch (IOException e) { + throw new RuntimeException(e.getMessage()); + } + } + + private Request createRequestBody(File chartFile) { + String credential = Credentials.basic(username, password); + MediaType mediaType = MediaType.parse("application/octet-stream"); + RequestBody body = RequestBody.create(chartFile, mediaType); + return new Request.Builder() + .url(chartMuseumUrl) + .method("POST", body) + .addHeader("Content-Type", "application/octet-stream") + .addHeader("Authorization", credential) + .build(); + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/chartinfo/ChartInfo.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/chartinfo/ChartInfo.java new file mode 100644 index 0000000..45b15a6 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/chartinfo/ChartInfo.java @@ -0,0 +1,35 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.chartinfo; + +import lombok.Data; + +import java.util.Map; + +/** + * POJO class to hold key-values parsed from the spec file. + * @author Dhrumin Desai + */ +@Data +public class ChartInfo { + + private Metadata metadata; + + private Map values; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/chartinfo/Metadata.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/chartinfo/Metadata.java new file mode 100644 index 0000000..00408b2 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/chartinfo/Metadata.java @@ -0,0 +1,32 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.chartinfo; + +import lombok.Data; + +/** + * POJO class to hold metadata of a helm chart + * @author Dhrumin Desai + */ +@Data +public class Metadata { + private String name; + private String version; + private String description; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/base/Auxilary.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/base/Auxilary.java new file mode 100644 index 0000000..2b8504a --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/base/Auxilary.java @@ -0,0 +1,76 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.base; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.HealthCheck; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Helm; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Policy; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Reconfigs; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.TlsInfo; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Volumes; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Auxilary of + * Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Auxilary { + + private Helm helm; + + private HealthCheck healthcheck; + + private HealthCheck livehealthcheck; + + private Volumes[] volumes; + + private List ports; + + @JsonProperty("log_info") + private Map logInfo; + + @JsonProperty("tls_info") + private TlsInfo tlsInfo; + + private Policy policy; + + private Reconfigs reconfigs; + + private List> env; + + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private Map databases; + + @JsonProperty( value = "hpa_config", access = JsonProperty.Access.WRITE_ONLY) + private Object hpaConfig; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/base/ComponentSpec.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/base/ComponentSpec.java new file mode 100644 index 0000000..80fa9cc --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/base/ComponentSpec.java @@ -0,0 +1,54 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.base; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Artifacts; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Parameters; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.PolicyInfo; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Self; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common.Streams; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents base + * ComponentSpec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ComponentSpec { + + private Self self; + + private Streams streams; + + private Parameters[] parameters; + + private Auxilary auxilary; + + private Artifacts[] artifacts; + + @JsonProperty("policy_info") + private PolicyInfo policyInfo; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Artifacts.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Artifacts.java new file mode 100644 index 0000000..742102c --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Artifacts.java @@ -0,0 +1,41 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Artifacts + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Artifacts { + + private String type; + + private String uri; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Calls.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Calls.java new file mode 100644 index 0000000..3e1eb2b --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Calls.java @@ -0,0 +1,43 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Calls used + * in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Calls { + + @JsonProperty("config_key") + private String configKey; + + private RequestResponse request; + + private RequestResponse response; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/ConfigVolume.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/ConfigVolume.java new file mode 100644 index 0000000..2e095f7 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/ConfigVolume.java @@ -0,0 +1,34 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * A model class which represents Config Volume + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@EqualsAndHashCode +public class ConfigVolume { + + private String name; + +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Constraints.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Constraints.java new file mode 100644 index 0000000..b6b89a0 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Constraints.java @@ -0,0 +1,61 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Constraints + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Constraints { + + private Object equal; + + @JsonProperty("greater_than") + private int greaterThan; + + @JsonProperty("greater_or_equal") + private int greaterOrEqual; + + @JsonProperty("less_than") + private int lessThan; + + @JsonProperty("less_or_equal") + private int lessOrEqual; + + @JsonProperty("valid_values") + private Object[] validValues; + + private int length; + + @JsonProperty("min_length") + private int minLength; + + @JsonProperty("max_length") + private int maxLength; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Container.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Container.java new file mode 100644 index 0000000..7f7b1ce --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Container.java @@ -0,0 +1,39 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Container + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@EqualsAndHashCode +public class Container { + + private String bind; + + private String mode; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/EntrySchema.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/EntrySchema.java new file mode 100644 index 0000000..7a1f97d --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/EntrySchema.java @@ -0,0 +1,47 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Entry Schema + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class EntrySchema { + + private String name; + + private String description; + + private String type; + + private String value; + + private EntrySchema[] entry_schema; + + private boolean required; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/HealthCheck.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/HealthCheck.java new file mode 100644 index 0000000..7aa87fc --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/HealthCheck.java @@ -0,0 +1,54 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents HealthCheck + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class HealthCheck { + + private String interval; + + private String timeout; + + private String script; + + @JsonProperty(required = true) + private String type; + + @JsonProperty(required = true) + private String endpoint; + + private Integer initialDelaySeconds; + + private Integer port; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Helm.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Helm.java new file mode 100644 index 0000000..b210e34 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Helm.java @@ -0,0 +1,35 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * A model class which represents helm field in Component Spec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@EqualsAndHashCode +public class Helm { + private Object applicationEnv; + + private Service service; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Host.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Host.java new file mode 100644 index 0000000..d987479 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Host.java @@ -0,0 +1,39 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Host used in + * Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@EqualsAndHashCode +public class Host { + + private String path; + + private String mode; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Parameters.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Parameters.java new file mode 100644 index 0000000..633fa27 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Parameters.java @@ -0,0 +1,68 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Parameters + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Parameters { + + private String name; + + private Object value; + + private String description; + + @JsonProperty("sourced_at_deployment") + private boolean sourcedAtDeployment; + + @JsonProperty("designer_editable") + private boolean designerEditable; + + @JsonProperty("policy_editable") + private boolean policyEditable; + + private boolean required; + + private String type; + + @JsonProperty("policy_group") + private String policyGroup; + + @JsonProperty("policy_schema") + private PolicySchema[] policySchemas; + + @JsonProperty("entry_schema") + private EntrySchema[] entrySchemas; + + private Constraints[] constraints; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Policy.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Policy.java new file mode 100644 index 0000000..b5aa2e5 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Policy.java @@ -0,0 +1,47 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Policy used + * in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Policy { + + @JsonProperty("node_label") + private String nodeLabel; + + @JsonProperty("policy_id") + private String policyID; + + @JsonProperty("policy_model_id") + private String policyModelID; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/PolicyInfo.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/PolicyInfo.java new file mode 100644 index 0000000..444cbe0 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/PolicyInfo.java @@ -0,0 +1,38 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * A model class which represents policyInfo field in Component Spec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class PolicyInfo { + + private Policy[] policy; + +} + diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/PolicySchema.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/PolicySchema.java new file mode 100644 index 0000000..b584af5 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/PolicySchema.java @@ -0,0 +1,51 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Policy + * Schema used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class PolicySchema { + + private String name; + + private String description; + + private String type; + + private String value; + + @JsonProperty("entry_schema") + private EntrySchema[] entrySchemas; + + private boolean required; + + private Constraints[] constraints; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Provides.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Provides.java new file mode 100644 index 0000000..01c5186 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Provides.java @@ -0,0 +1,44 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Provides + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Provides { + + private String route; + + private RequestResponse request; + + private RequestResponse response; + + // Used in ONAP + private String verb; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Publishes.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Publishes.java new file mode 100644 index 0000000..fa3bddc --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Publishes.java @@ -0,0 +1,50 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Publishes + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Publishes { + + @JsonProperty("config_key") + private String configKey; + + private String format; + + private String type; + + private String version; + + // Used in ONAP + private String route; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Reconfigs.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Reconfigs.java new file mode 100644 index 0000000..0aa6b77 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Reconfigs.java @@ -0,0 +1,47 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Reconfigs + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Reconfigs { + + private String dti; + + @JsonProperty("app_reconfig") + private String appReconfig; + + private String policy; + + private String streams; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/RequestResponse.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/RequestResponse.java new file mode 100644 index 0000000..c4e494b --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/RequestResponse.java @@ -0,0 +1,39 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Request + * Response used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RequestResponse { + + private String format; + + private String version; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Self.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Self.java new file mode 100644 index 0000000..b45b391 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Self.java @@ -0,0 +1,47 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Self used in + * Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Self { + + @JsonProperty("component_type") + private String componentType; + + private String description; + + private String name; + + private String version; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Service.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Service.java new file mode 100644 index 0000000..1677896 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Service.java @@ -0,0 +1,43 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * A model class which represents service field in Component Spec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties +@EqualsAndHashCode +public class Service { + private String type; + + private String name; + + @JsonProperty("has_internal_only_ports") + private Boolean hasInternalOnlyPorts; + + private Object[] ports; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Streams.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Streams.java new file mode 100644 index 0000000..852e3cd --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Streams.java @@ -0,0 +1,41 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Streams used + * in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Streams { + + private Publishes[] publishes; + + private Subscribes[] subscribes; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Subscribes.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Subscribes.java new file mode 100644 index 0000000..5e45d91 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Subscribes.java @@ -0,0 +1,49 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Subscribes + * used in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Subscribes { + + @JsonProperty("config_key") + private String configKey; + + private String format; + + private String route; + + private String type; + + private String version; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/TlsInfo.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/TlsInfo.java new file mode 100644 index 0000000..a57b738 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/TlsInfo.java @@ -0,0 +1,43 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * A model class which represents tlsInfo field in Component Spec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties +@EqualsAndHashCode +public class TlsInfo { + @JsonProperty("cert_directory") + private String certDirectory; + + @JsonProperty("use_tls") + private Boolean useTls; + + @JsonProperty("use_external_tls") + private Boolean useExternalTls; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Volumes.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Volumes.java new file mode 100644 index 0000000..cf4539b --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/models/componentspec/common/Volumes.java @@ -0,0 +1,49 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author : Ravi Mantena + * @date 10/16/2020 Application: DCAE/ONAP - Blueprint Generator Common Module: Used by both ONAP + * and DCAE Blueprint Applications Component Spec Model: A model class which represents Volumes used + * in Componentspec + */ +@Data +@JsonInclude(value = JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode +public class Volumes { + + private Container container; + + private Host host; + + @JsonProperty("config_volume") + private ConfigVolume configVolume; + + private String type; + + private String name; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ChartTemplateStructureValidator.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ChartTemplateStructureValidator.java new file mode 100644 index 0000000..796ee91 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ChartTemplateStructureValidator.java @@ -0,0 +1,60 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.validation; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * A class to validate structure of the base helm directory + */ +public class ChartTemplateStructureValidator { + + /** + * validates base helm chart directory and throws error if the structure is not proper. + * @param chartTemplateLocation base helm chart dir location + */ + public static void validateChartTemplateStructure(String chartTemplateLocation) { + checkBaseDirectory(chartTemplateLocation); + } + + private static void checkBaseDirectory(String chartTemplateLocation) { + Path base = Paths.get(chartTemplateLocation, "base"); + Path charts = Paths.get(chartTemplateLocation, "base/charts"); + Path templates = Paths.get(chartTemplateLocation, "base/templates"); + Path chart = Paths.get(chartTemplateLocation, "base/Chart.yaml"); + Path values = Paths.get(chartTemplateLocation, "base/values.yaml"); + if(!Files.exists(base)){ + throw new RuntimeException("base directory not found in chart template location"); + } + if(!Files.exists(charts)){ + throw new RuntimeException("charts directory not found in base directory"); + } + if(!Files.exists(templates)){ + throw new RuntimeException("templates directory not found in base directory"); + } + if(!Files.exists(chart)){ + throw new RuntimeException("chart.yaml not found in base directory"); + } + if(!Files.exists(values)){ + throw new RuntimeException("values.yaml not found in base directory"); + } + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ComponentSpecValidator.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ComponentSpecValidator.java new file mode 100644 index 0000000..dfda935 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ComponentSpecValidator.java @@ -0,0 +1,29 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.validation; + +import java.io.IOException; + +/** + * An interface for Component specification validator + * @author Dhrumin Desai + */ +public interface ComponentSpecValidator { + void validateSpecFile(String specFileLocation, String specSchemaLocation) throws IOException; +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ComponentSpecValidatorImpl.java b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ComponentSpecValidatorImpl.java new file mode 100644 index 0000000..a74cab1 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/java/org/onap/dcaegen2/platform/helmchartgenerator/validation/ComponentSpecValidatorImpl.java @@ -0,0 +1,108 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator.validation; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.everit.json.schema.Schema; +import org.everit.json.schema.loader.SchemaLoader; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.onap.dcaegen2.platform.helmchartgenerator.Utils; +import org.onap.dcaegen2.platform.helmchartgenerator.models.componentspec.base.ComponentSpec; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +/** + * A class for Component specification validation. + * @author Dhrumin Desai + */ +@Service +@Slf4j +public class ComponentSpecValidatorImpl implements ComponentSpecValidator { + + /** + * Validates the spec json file against schema and prints errors if found + * @param specFileLocation specification json file location + * @param specSchemaLocation json schema file location + * @throws IOException + */ + @Override + public void validateSpecFile(String specFileLocation, String specSchemaLocation) throws IOException { + File schemaFile = getSchemaFile(specSchemaLocation); + ComponentSpec cs = Utils.deserializeJsonFileToModel(specFileLocation, ComponentSpec.class); + validateSpecSchema(new File(specFileLocation), schemaFile); + validateHelmRequirements(cs); + } + + private File getSchemaFile(String specSchemaLocation) throws IOException { + if(specSchemaLocation.isEmpty()){ + return defaultSchemaFile(); + } + else{ + return new File(specSchemaLocation); + } + } + + private File defaultSchemaFile() throws IOException { + File schemaFile = File.createTempFile("schema", ".json"); + InputStream inputStream = new ClassPathResource("component-spec-schema.json").getInputStream(); + FileUtils.copyInputStreamToFile(inputStream, schemaFile); + return schemaFile; + } + + private void validateSpecSchema(File specFile, File schemaFile) { + try { + JSONTokener schemaData = new JSONTokener(new FileInputStream(schemaFile)); + JSONObject jsonSchema = new JSONObject(schemaData); + + JSONTokener jsonDataFile = new JSONTokener(new FileInputStream(specFile)); + JSONObject jsonObject = new JSONObject(jsonDataFile); + + Schema schemaValidator = SchemaLoader.load(jsonSchema); + schemaValidator.validate(jsonObject); + } catch (FileNotFoundException e) { + log.error(e.getMessage()); + throw new RuntimeException("Specfile or Schemafile not found."); + } + } + + private void validateHelmRequirements(ComponentSpec cs) { + checkHealthCheckSection(cs); + checkHelmSection(cs); + } + + private void checkHealthCheckSection(ComponentSpec cs) { + if(cs.getAuxilary().getHealthcheck().getPort() == null) { + throw new RuntimeException("port in healthcheck section is a required field but was not found"); + } + } + + private void checkHelmSection(ComponentSpec cs) { + if(cs.getAuxilary().getHelm() == null){ + throw new RuntimeException("helm section in component spec is required but was not found"); + } + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/resources/application.properties b/mod2/helm-generator/helmchartgenerator-core/src/main/resources/application.properties new file mode 100644 index 0000000..df2005a --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.main.web-application-type=NONE + +chartmuseum.baseurl=http://localhost:8081/api/charts +chartmuseum.auth.basic.username=TBD +chartmuseum.auth.basic.password=TBD \ No newline at end of file diff --git a/mod2/helm-generator/helmchartgenerator-core/src/main/resources/component-spec-schema.json b/mod2/helm-generator/helmchartgenerator-core/src/main/resources/component-spec-schema.json new file mode 100644 index 0000000..ddea3f0 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/main/resources/component-spec-schema.json @@ -0,0 +1,1162 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Component specification schema", + "type": "object", + "properties": { + "self": { + "type": "object", + "properties": { + "version": { + "$ref": "#/definitions/version" + }, + "description": { + "type": "string" + }, + "component_type": { + "type": "string", + "enum": [ + "docker", + "cdap" + ] + }, + "name": { + "$ref": "#/definitions/name" + } + }, + "required": [ + "version", + "name", + "description", + "component_type" + ] + }, + "streams": { + "type": "object", + "properties": { + "publishes": { + "type": "array", + "uniqueItems": true, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/publisher_http" + }, + { + "$ref": "#/definitions/publisher_message_router" + }, + { + "$ref": "#/definitions/publisher_data_router" + }, + { + "$ref": "#/definitions/publisher_kafka" + } + ] + } + }, + "subscribes": { + "type": "array", + "uniqueItems": true, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/subscriber_http" + }, + { + "$ref": "#/definitions/subscriber_message_router" + }, + { + "$ref": "#/definitions/subscriber_data_router" + }, + { + "$ref": "#/definitions/subscriber_kafka" + } + ] + } + } + }, + "required": [ + "publishes", + "subscribes" + ] + }, + "services": { + "type": "object", + "properties": { + "calls": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/caller" + } + }, + "provides": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/provider" + } + } + }, + "required": [ + "calls", + "provides" + ] + }, + "parameters": { + "anyOf": [ + { + "$ref": "#/definitions/docker-parameters" + }, + { + "$ref": "#/definitions/cdap-parameters" + } + ] + }, + "auxilary": { + "oneOf": [ + { + "$ref": "#/definitions/auxilary_cdap" + }, + { + "$ref": "#/definitions/auxilary_docker" + } + ] + }, + "artifacts": { + "type": "array", + "description": "List of component artifacts", + "items": { + "$ref": "#/definitions/artifact" + } + }, + "policy_info": { + "type": "object", + "properties": { + "policy": { + "type": "array", + "items": { + "type": "object", + "properties": { + "node_label": { + "type": "string" + }, + "policy_id": { + "type": "string" + }, + "policy_model_id": { + "type": "string" + } + }, + "required": [ + "node_label", + "policy_model_id" + ] + } + } + }, + "additionalProperties": false + } + }, + "required": [ + "self", + "streams", + "services", + "parameters", + "auxilary", + "artifacts" + ], + "additionalProperties": false, + "definitions": { + "cdap-parameters": { + "description": "There are three seperate ways to pass parameters to CDAP: app config, app preferences, program preferences. These are all treated as optional.", + "type": "object", + "properties": { + "program_preferences": { + "description": "A list of {program_id, program_type, program_preference} objects where program_preference is an object passed into program_id of type program_type", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/program_preference" + } + }, + "app_preferences": { + "description": "Parameters Passed down to the CDAP preference API", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/parameter" + } + }, + "app_config": { + "description": "Parameters Passed down to the CDAP App Config", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/parameter" + } + } + } + }, + "program_preference": { + "type": "object", + "properties": { + "program_type": { + "$ref": "#/definitions/program_type" + }, + "program_id": { + "type": "string" + }, + "program_pref": { + "description": "Parameters that the CDAP developer wants pushed to this program's preferences API. Optional", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/parameter" + } + } + }, + "required": [ + "program_type", + "program_id", + "program_pref" + ] + }, + "program_type": { + "type": "string", + "enum": [ + "flows", + "mapreduce", + "schedules", + "spark", + "workflows", + "workers", + "services" + ] + }, + "docker-parameters": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/parameter" + } + }, + "parameter": { + "oneOf": [ + { + "$ref": "#/definitions/parameter-list" + }, + { + "$ref": "#/definitions/parameter-other" + } + ] + }, + "parameter-list": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "description": "Default value for the parameter" + }, + "description": { + "description": "Description for the parameter.", + "type": "string" + }, + "type": { + "description": "Only valid type is list, the entry_schema is required - which contains the type of the list element. All properties set for the parameter apply to all elements in the list at this time", + "type": "string", + "enum": [ + "list" + ] + }, + "required": { + "description": "An optional key that declares a parameter as required (true) or not (false). Default is true.", + "type": "boolean", + "default": true + }, + "constraints": { + "description": "The optional list of sequenced constraint clauses for the parameter.", + "type": "array", + "items": { + "$ref": "#/definitions/parameter-constraints" + } + }, + "entry_schema": { + "description": "The optional property used to declare the name of the Datatype definition for entries of certain types. entry_schema must be defined when the type is list. This is the only type it is currently supported for.", + "type": "object", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/list-parameter" + } + }, + "designer_editable": { + "description": "A required property that declares a parameter as editable by designer in SDC Tool (true) or not (false).", + "type": "boolean" + }, + "sourced_at_deployment": { + "description": "A required property that declares that a parameter is assigned at deployment time (true) or not (false).", + "type": "boolean" + }, + "policy_editable": { + "description": "A required property that declares a parameter as editable by DevOps in Policy UI (true) or not (false).", + "type": "boolean" + }, + "policy_group": { + "description": "An optional property used to group policy_editable parameters into groups. Each group will become it's own policy model. Any parameters without this property will be grouped together to form their own policy model", + "type": "string" + }, + "policy_schema": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/policy_schema_parameter" + } + } + }, + "required": [ + "name", + "value", + "description", + "designer_editable", + "policy_editable", + "sourced_at_deployment", + "entry_schema" + ], + "additionalProperties": false, + "dependencies": { + "policy_schema": [ + "policy_editable" + ] + } + }, + "parameter-other": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "description": "Default value for the parameter" + }, + "description": { + "description": "Description for the parameter.", + "type": "string" + }, + "type": { + "description": "The required data type for the parameter.", + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "datetime" + ] + }, + "required": { + "description": "An optional key that declares a parameter as required (true) or not (false). Default is true.", + "type": "boolean", + "default": true + }, + "constraints": { + "description": "The optional list of sequenced constraint clauses for the parameter.", + "type": "array", + "items": { + "$ref": "#/definitions/parameter-constraints" + } + }, + "designer_editable": { + "description": "A required property that declares a parameter as editable by designer in SDC Tool (true) or not (false).", + "type": "boolean" + }, + "sourced_at_deployment": { + "description": "A required property that declares that a parameter is assigned at deployment time (true) or not (false).", + "type": "boolean" + }, + "policy_editable": { + "description": "A required property that declares a parameter as editable in Policy UI (true) or not (false).", + "type": "boolean" + }, + "policy_group": { + "description": "An optional property used to group policy_editable parameters into groups. Each group will become it's own policy model. Any parameters without this property will be grouped together to form their own policy model", + "type": "string" + }, + "policy_schema": { + "description": "An optional property used to define policy_editable parameters as lists or maps", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/policy_schema_parameter" + } + } + }, + "required": [ + "name", + "value", + "description", + "designer_editable", + "sourced_at_deployment", + "policy_editable" + ], + "additionalProperties": false, + "dependencies": { + "policy_schema": [ + "policy_editable" + ] + } + }, + "list-parameter": { + "type": "object", + "properties": { + "type": { + "description": "The required data type for each parameter in the list.", + "type": "string", + "enum": [ + "string", + "number" + ] + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "policy_schema_parameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "description": "Default value for the parameter" + }, + "description": { + "description": "Description for the parameter.", + "type": "string" + }, + "type": { + "description": "The required data type for the parameter.", + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "datetime", + "list", + "map" + ] + }, + "required": { + "description": "An optional key that declares a parameter as required (true) or not (false). Default is true.", + "type": "boolean", + "default": true + }, + "constraints": { + "description": "The optional list of sequenced constraint clauses for the parameter.", + "type": "array", + "items": { + "$ref": "#/definitions/parameter-constraints" + } + }, + "entry_schema": { + "description": "The optional key that is used to declare the name of the Datatype definition for entries of certain types. entry_schema must be defined when the type is either list or map. If the type is list and the entry type is a simple type (string, number, boolean, datetime), follow with a simple string to describe the entry type. If the type is list and the entry type is a map, follow with an array to describe the keys for the entry map. If the type is list and the entry type is also list, this is not currently supported here. If the type is map, then follow with an array to describe the keys for this map. ", + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/policy_schema_parameter" + } + } + }, + "required": [ + "name", + "type" + ], + "additionalProperties": false + }, + "parameter-constraints": { + "type": "object", + "additionalProperties": false, + "properties": { + "equal": { + "description": "Constrains a property or parameter to a value equal to (‘=’) the value declared." + }, + "greater_than": { + "description": "Constrains a property or parameter to a value greater than (‘>’) the value declared.", + "type": "number" + }, + "greater_or_equal": { + "description": "Constrains a property or parameter to a value greater than or equal to (‘>=’) the value declared.", + "type": "number" + }, + "less_than": { + "description": "Constrains a property or parameter to a value less than (‘<’) the value declared.", + "type": "number" + }, + "less_or_equal": { + "description": "Constrains a property or parameter to a value less than or equal to (‘<=’) the value declared.", + "type": "number" + }, + "valid_values": { + "description": "Constrains a property or parameter to a value that is in the list of declared values.", + "type": "array" + }, + "length": { + "description": "Constrains the property or parameter to a value of a given length.", + "type": "number" + }, + "min_length": { + "description": "Constrains the property or parameter to a value to a minimum length.", + "type": "number" + }, + "max_length": { + "description": "Constrains the property or parameter to a value to a maximum length.", + "type": "number" + } + } + }, + "stream_message_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "message router", + "message_router" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "stream_kafka": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "kafka" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_http": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "http", + "https" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_message_router": { + "$ref": "#/definitions/stream_message_router" + }, + "publisher_data_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "data router", + "data_router" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_kafka": { + "$ref": "#/definitions/stream_kafka" + }, + "subscriber_http": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "route": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "http", + "https" + ] + } + }, + "required": [ + "format", + "version", + "route", + "type" + ] + }, + "subscriber_message_router": { + "$ref": "#/definitions/stream_message_router" + }, + "subscriber_data_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "route": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "data router", + "data_router" + ] + }, + "config_key": { + "description": "Data router subscribers require config info to setup their endpoints to handle requests. For example, needs username and password", + "type": "string" + } + }, + "required": [ + "format", + "version", + "route", + "type", + "config_key" + ] + }, + "subscriber_kafka": { + "$ref": "#/definitions/stream_kafka" + }, + "provider": { + "oneOf": [ + { + "$ref": "#/definitions/docker-provider" + }, + { + "$ref": "#/definitions/cdap-provider" + } + ] + }, + "cdap-provider": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "service_name": { + "type": "string" + }, + "service_endpoint": { + "type": "string" + }, + "verb": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "DELETE" + ] + } + }, + "required": [ + "request", + "response", + "service_name", + "service_endpoint", + "verb" + ] + }, + "docker-provider": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "route": { + "type": "string" + }, + "verb": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "DELETE" + ] + } + }, + "required": [ + "request", + "response", + "route" + ] + }, + "caller": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "config_key": { + "type": "string" + } + }, + "required": [ + "request", + "response", + "config_key" + ] + }, + "formatPair": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + } + } + }, + "name": { + "type": "string" + }, + "version": { + "type": "string", + "pattern": "^(\\d+\\.)(\\d+\\.)(\\*|\\d+)$" + }, + "artifact": { + "type": "object", + "description": "Component artifact object", + "properties": { + "uri": { + "type": "string", + "description": "Uri to artifact" + }, + "type": { + "type": "string", + "enum": [ + "jar", + "docker image" + ] + } + }, + "required": [ + "uri", + "type" + ] + }, + "auxilary_cdap": { + "title": "cdap component specification schema", + "type": "object", + "properties": { + "streamname": { + "type": "string" + }, + "artifact_name": { + "type": "string" + }, + "artifact_version": { + "type": "string", + "pattern": "^(\\d+\\.)(\\d+\\.)(\\*|\\d+)$" + }, + "namespace": { + "type": "string", + "description": "optional" + }, + "programs": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/cdap_program" + } + } + }, + "required": [ + "streamname", + "programs", + "artifact_name", + "artifact_version" + ] + }, + "cdap_program_type": { + "type": "string", + "enum": [ + "flows", + "mapreduce", + "schedules", + "spark", + "workflows", + "workers", + "services" + ] + }, + "cdap_program": { + "type": "object", + "properties": { + "program_type": { + "$ref": "#/definitions/cdap_program_type" + }, + "program_id": { + "type": "string" + } + }, + "required": [ + "program_type", + "program_id" + ] + }, + "auxilary_docker": { + "title": "Docker component specification schema", + "type": "object", + "properties": { + "helm": { + "type": "object", + "properties": { + "applicationEnv": { + "type": "object" + }, + "service": { + "description": "Mapping for kubernetes services", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "NodePort", + "Cluster" + ] + }, + "name": { + "type": "string" + }, + "ports": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": [ + "type", + "name", + "ports" + ] + } + }, + "required": [ + "service" + ] + }, + "healthcheck": { + "description": "Define the health check that Consul should perfom for this component", + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/docker_healthcheck_http" + }, + { + "$ref": "#/definitions/docker_healthcheck_script" + } + ] + }, + "ports": { + "description": "Port mapping to be used for Docker containers. Each entry is of the format :.", + "type": "array", + "items": { + "type": "string" + } + }, + "log_info": { + "description": "Component specific details for logging", + "type": "object", + "properties": { + "log_directory": { + "description": "The path in the container where the component writes its logs. If the component is following the EELF requirements, this would be the directory where the four EELF files are being written. (Other logs can be placed in the directory--if their names in '.log', they'll also be sent into ELK.)", + "type": "string" + }, + "alternate_fb_path": { + "description": "By default, the log volume is mounted at /var/log/onap/ in the sidecar container's file system. 'alternate_fb_path' allows overriding the default. Will affect how the log data can be found in the ELK system.", + "type": "string" + } + }, + "additionalProperties": false + }, + "tls_info": { + "description": "Component information to use tls certificates", + "type": "object", + "properties": { + "cert_directory": { + "description": "The path in the container where the component certificates will be placed by the init container", + "type": "string" + }, + "use_tls": { + "description": "Boolean flag to determine if the application is using tls certificates", + "type": "boolean" + }, + "use_external_tls": { + "description": "Boolean flag to determine if the application is using tls certificates for external communication", + "type": "boolean" + } + }, + "required": [ + "cert_directory", + "use_tls" + ], + "additionalProperties": false + }, + "databases": { + "description": "The databases the application is connecting to using the pgaas", + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "postgres" + ] + } + }, + "policy": { + "properties": { + "trigger_type": { + "description": "Only value of docker is supported at this time.", + "type": "string", + "enum": [ + "docker" + ] + }, + "script_path": { + "description": "Script command that will be executed for policy reconfiguration", + "type": "string" + } + }, + "required": [ + "trigger_type", + "script_path" + ], + "additionalProperties": false + }, + "volumes": { + "description": "Volume mapping to be used for Docker containers. Each entry is of the format below", + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/host_path_volume" + }, + { + "$ref": "#/definitions/config_map_volume" + } + ] + } + } + }, + "required": [ + "healthcheck" + ], + "additionalProperties": false + }, + "host_path_volume": { + "type": "object", + "properties": { + "host": { + "type": "object", + "path": { + "type": "string" + } + }, + "container": { + "type": "object", + "bind": { + "type": "string" + }, + "mode": { + "type": "string" + } + } + }, + "required": [ + "host", + "container" + ] + }, + "config_map_volume": { + "type": "object", + "properties": { + "config_volume": { + "type": "object", + "name": { + "type": "string" + } + }, + "container": { + "type": "object", + "bind": { + "type": "string" + }, + "mode": { + "type": "string" + } + } + }, + "required": [ + "config_volume", + "container" + ] + }, + "docker_healthcheck_http": { + "properties": { + "type": { + "description": "Consul health check type", + "type": "string", + "enum": [ + "http", + "https", + "HTTP", + "HTTPS" + ] + }, + "interval": { + "description": "Interval duration in seconds i.e. 10s", + "default": "15s", + "type": "string" + }, + "timeout": { + "description": "Timeout in seconds i.e. 10s", + "default": "1s", + "type": "string" + }, + "endpoint": { + "description": "Relative endpoint used by Consul to check health by making periodic HTTP GET calls", + "type": "string" + }, + "port": { + "description": "Port mapping for readiness section", + "type": "integer" + }, + "initialDelaySeconds": { + "description": "Initial delay in seconds for readiness section", + "type": "integer" + } + }, + "required": [ + "type", + "endpoint" + ] + }, + "docker_healthcheck_script": { + "properties": { + "type": { + "description": "Consul health check type", + "type": "string", + "enum": [ + "script", + "docker" + ] + }, + "interval": { + "description": "Interval duration in seconds i.e. 10s", + "default": "15s", + "type": "string" + }, + "timeout": { + "description": "Timeout in seconds i.e. 10s", + "default": "1s", + "type": "string" + }, + "script": { + "description": "Script command that will be executed by Consul to check health", + "type": "string" + }, + "initialDelaySeconds": { + "description": "Initial delay in seconds for readiness section", + "type": "integer" + } + }, + "required": [ + "type", + "script" + ] + } + } +} \ No newline at end of file diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/addons/templates/certificates.yaml b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/addons/templates/certificates.yaml new file mode 100644 index 0000000..a871343 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/addons/templates/certificates.yaml @@ -0,0 +1,21 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} + +{{ if and .Values.certDirectory .Values.global.cmpv2Enabled .Values.global.CMPv2CertManagerIntegration }} +{{ include "certManagerCertificate.certificate" . }} +{{ end }} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/Chart.yaml b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/Chart.yaml new file mode 100644 index 0000000..239978f --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/Chart.yaml @@ -0,0 +1,39 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + +apiVersion: v2 +appVersion: "Honolulu" +description: TBD +name: TBD +version: TBD + +dependencies: + - name: common + version: ~8.x-0 + repository: '@local' + - name: repositoryGenerator + version: ~8.x-0 + repository: '@local' + - name: readinessCheck + version: ~8.x-0 + repository: '@local' + - name: dcaegen2-services-common + version: ~8.x-0 + repository: '@local' + - name: postgres + version: ~8.x-0 + repository: '@local' + condition: postgres.enabled diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/common-8.0.0.tgz b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/common-8.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..910eedd0c7b8b351ab5bbd5edc64f6c3a5ccf905 GIT binary patch literal 91901 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�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 iGL_6tPQ3I-XJ3!kDc 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 M|9dZ|`~b!R08L5basU7T literal 0 HcmV?d00001 diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/postgres-8.0.0.tgz b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/postgres-8.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..1618016f86cefaf32ad770b233aec5fb3298af3a GIT binary patch literal 29791 zcmV)(K#RX0iwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PMZ}b{jX6IJ*D-bdKHu)A5eQtk|L`$@Vat`K=;?*jLZMJ7R234&#V93w z=V**m;m`0S`j>}(I-O4E#r{70+v#-5|8{nFU;fMP{%&Wld+>61xAQNZ-M!9U=U-6g z!7-_MGA?lXFP(e0RqovH9Us<16%zg>*;*EJXx&DsLUN zP`A_Fb<6vk8(+yfZ)recPC}H$AxV)K6Lgs1U`))HZKQ<|>iQkDB@1}^lh^u2f|#)k zP4EoGOrVStB*v7ZA&m$k9|DpHL}L`NNfJ>U2L#2bqiw|5P~75_NH8JgBBg^&6sTs9q`Xjo z#R$j9JM5#=zK6a$?4S0xC6J#^uf9Kje}#TJyu3U-yE;ATqw`C2bbfYxdUbk!)<@^B z(c#(8=!es@<84H!7?Tu{4@oL}z*0mdJxK_}>l0EyVaOCpJRyN(2o&LXl;IIUBQ_;z zOydzsNIIdM1E=6Plwcw{p+YlHrCW}Ku)DG5m|D2^IBJHVUd zD;Ho+<55J=uNf0WE;9Krn7{fWCGkj%&!;3!X=vD2wvc2|bipDT%zEfBy2UfD+Qm(M z+}uzSE5}e`*5hUfC8xft7m~bV0WnGG+V-n$6p9Cxpxm;5^dQG7$v~PFerJxhS zGGX5yC@RDxAe`fLCKZj4a;1UjmNZJYV-h1ygw#OLASL)FVKf$8L7ML}JCOFP;6wTxxctk=2T*CShU`dq)9YvTu+-OV+XFIWDbcv2TN*{O*nnlRW@Pe# zayc}B2J}ajy(EErV%5SDphI==*= zGdfR5+!r{wY00lRkn`&uld|)Y;P9uE3UVF?zy_TDgY?ku8!CTHSQZN<<90$8+>lg` zOgkQO`?a49Q~|pqH_{M3pT<-azsu3)B%M;t=xVt=iZU)pdU|1nn-Wq)t)(`LPW3wwTNsZo&{ss_&XZYn`}UQIalS5JY?t= z$Ax}4)v{RiRRC^77Den9rn}q*#=zK~hY^rvhL6%B!nPMQ! zP>>Wo&&mAU7#dDK;7Jn6`S2Aw(u*yzD8Z(jaG^*GJ5jEBI_#N^zU*Ea6G6C7nVhS9 zM-sI?%4kS-0yar9LE5(I9WaT|m`<_@nve-gXD}EcnUaVl(0R&H7ad)^M*&OquR6+r4pCe-?j>Y)CCP>^JDDeX+m3k3l*i+9whIf_8# zN|ic)(N!Xxj>w$$y5)-dZ{{~uF5gwoSv*1~ax0Xw zmN%nxtQ}%TNX!17Jn8pfqLH}U;gX7PDWDGhcEh1`O!T;#Yw_HHvGAxSCRhaH zw=P-lOjxt-8ptlAfL2v(yLf-~%Qp=`7XVud$mRL@)gnlRGIijB5bY?_Xq(1KCeWY% z3@gLM>(jRC1*#gtLaiiALM%wzbiTau z%VoRGXFL!N-RhR?odJz^+;w~ILLH3RZH&4vdfgXjCt;yvpPk|gG@h^!z3g;qzppd~ zgN)Ax?8DO!K_hFgu-!064(4$=))2

+1BY<@n%^q%~sT>1iv~ogz_f@73W*sX2Wv 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 KoFc*iC<6c%5nDb0 literal 0 HcmV?d00001 diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/readinessCheck-8.0.0.tgz b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/charts/readinessCheck-8.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2060b85462ef462419b315c94e0a31614693249a GIT binary patch literal 24803 zcmV)dK&QVSiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PMZ(cHFj-D7t@pExiMV@gA!)YDz81AL%)na}-I=)`=}^Bs)3# zPUb3T65Wl6O)vy0xySZ>pZgm3$?ihog8;vpEwvs_I6(k2G0k7*%3c> z{(^Q^h(qI(^9-kd*|~FD<<9*{9-PphNy;gUhv<6nBqTgYX_CS3_hgntI3pX%DM?XCl8DV`B+j;Z#(^=7^2$f(T~{Y57dEEE#TZ-`w2z7*O_EI@yk7VZQz5>rE~cGmTo)| zgG0DoaL*~W3#A@=j(yEfN&aVQ$>-ab?iW8xZoMKbkpC}Uz8I9`|I3%p_t*0OA)Zg4 zw!anAxw;CxOL06QYr(%3{L2acZ@0gE5h7f27D7Lgn51GCVFU_Fa4Po^7$qzeg?^z? zgd)bS1hqpl#(9(p;`%>Jg;eZ3F2^htW#3OpN)S#7%5IndGs782_!`qlj1a6nPzrI; zeS%m_(B=))(r^U*f6sY_BFZztKmm&dOOcfKh7y1-3Tq2OeT4D2AF%*u#C{XZ>|dOu z*EApn3JJR*={S#?kick$lZGbc9%?oh|H?N7ma0e{US-T`am&iM;V5D^9N~yFXR8Oh zIyD)oY<3}3zn2Wg~uq7Wcj3yqjYqABeerF?S9c023UlW9p7qb|R=|>WJNMnTK5M|SxqmaeV zpj#6H>;-a2FL{WLQA|ik!eaQwc^n8{e_r8z(kY#9yNPJCG7KY~ zFN%q3_2Xr+hyt?xEkf_|6e)9x7`7>iW+-HY3;yPLlCU&GAt8xa*AZvvh5&ENRX4^w zo1-kH!4)d{iqKP@kAf*qvm=SzIAyc{%~>pX+@(6tM~AAQOKRx8r!n@5&h{i}RYL{m zStQU!3%)G=vgv2>=Hj|Xb*%*2KzY4!D=$Wxll4_Ta%kC_rpX&xW&53o6HXjtrAjZlK(Kgtbk z6Qbg)svrgd$04-UN830_wgoljelmx(DxF9XDfj|ZaEsyX>9Q$AcF&m&vVlJ8_sz?` zUIY6Aw=9WlXo>}NYa|Y>U#e}f#ty$%{8=>cI~&!27KIyulK5J6T5{&;!Q11DvxCE9 zNk%X|zE0W9XbxjaqVSxIhs9dsnyZWVQ1lFE(;=FX42L+wz~(%`0pOSt&hj)Mdik*o z44_ygdE?;8qJEI%X2=T*Lwg-rs^ApcT(PKGU3j^MO)aZwJq#chO8dXk{$Fi+K(p~9 z7F?}jkGbglXMg{BY5)KH`SZbg|NjtAND>lNRq|5mtTK65xa7ef;61-pCU?klp7U*f&-=;e5@^WxR3z2~Es zuf7`%2H(B>Zv6b^emHuCcV6v$xBKe(i0tf-gK$6CfAtDKnW#N!I7Hs=&hDVU^Qyn| z;{9NExbxj`ci`{t40Z;4ulhUt!<`-Pzq0|cZT_$76ll@;&;H(SW&XcdpZ`9{Q?dLF zUZI0o$O68FejrhV4snb_yfy=_&4A0E|2RvFt@z31Luq|l(EmTL{Ez(|ttS;L#k2CV z^UMZSvwAEKw+6_wf0R?BlKcmCk}FED${T2*|Ihx*lKg-1VtxMeFi)HR&mnMuWgrEc zwbkf*Buy=>6`)ycyy3_gA~DbsIm^>4GXFR2j-XsHzCkz)DR_EBNbbA1UaK^|8ADSXhY>*m zNi!}h;xG&p%2}F|&tn{Md#bgT$5bA^-@=PNoWPdDiX=(EHAJndtV9sw? z8ZN9S#=owj_6g8JSB}p~Y=tAqSfp*9;f##)NIN9J0MM9bhh;aq8Ycs;DaO1hi2 zl`syde*#|+9dKg1T&SRvW>1d2N);J^vJ}G=)3)HY&3ZRM&2sf@roZ-@c@g9B!Gy%w zgRo*nJa!Oy9pqgd?W8o2@<53R-zZJSi%9|(^>7$E;?ly)pe5F8S!R1g<88N^&(%`R z^O3Aq^IcACbP z_fPeN|MF${YUjJ>P4HQIX~b2z8OCN^s$Issu79=IwDgpvphdTnDvaA9NM*kbhAdHq za&&z5=H1_m!KzaVq`K;p_*%$jsXo5Oyl?@QvO#+R=QQ++hAn;DyCQQBd7wXfJ@gqV zrQ(HQ#1d9#2NxH=ygNU7Fx0fNS%=*>QsuwFF?iW0c_s}MEJmj+Bzy~zYZ7NUj-t8S z8+U_JGfGB@f4=MQSqQontApokirU}~_Wa!nTvbnl{pN2ZikR8!`v@II6ha+j_=Nnw z@h0CwxwL|ulL_S+NohQh8{>n6*XWI8%w~?YHjr&>YZbHB8_&~7Klqw1rJr%DbbKrq zsKiR6?!zjR;B4xZbl8RZ>wu<5OI>h8A~=#$oDfrmS9STCO)j`nCJ(Z#dt=y z*3JOyL!cEL0-cjAPsKKrrgRRO?^Ad*>lf z9sqeVf(m&*9GrXN|NocQ>&XSN*{oUL22@H?GA3zC!lPWMz!$-kgn2~c$;l*U=JoMM z66D}sCv6b27KsC5)Xo{sg6Xjw6G1i0JXj8#ar1?tG*rC z48s2RX1%&$^MA-&Z(vx_JF%&Q^NJN5+ zrSC~PE6#bo?tyF{xw{OS885Mvc*v8J9Fv!IBWb7p2lctj#S8?E^QQ}K=X$cI4wN<_O=qLe&9(FA$bBE4SE zIujf0)J!)z1`S5XPn+KNDrJ$5y~au6U*#i`#v~)$r)=BQ8X}0xE@;>;sO8oYERVMI z=r^52KuN311)Pi}EMk-SPeO3H$W0l~irgQ|tqm*WY-godC1+d9!&01mi?yWRz`zf} zzONi+>dw0>;M_9b)^P38Jllz5+gWsV-1$&TqHJp=E21UUSGgcsQ)=OQ_}H7{e}Jc} z{YN@DovCAy_0DF?w0cZ?e0E*`EqUld5EXMurv5>odtGn+*!Sc?>XTtx|U7P zFwaP8{Q8umuFf?cl9XN(6=A5$a38X=!BUCXSU}VVKoXi3P}*bHN+)Q}DHUNvw^AAs zG$X+j$CS?yg_CnpPbK8*myhB~t)O86Mr(rQZh<#)6sagl7pe>Hod#t=mF+?XJGTLY z0|QM(hhc*2C~r{~+}f6|U>`g-;AJoITICa3-GXXG6Iyp@r|P0+>T5N(vLyJ0aLFIY z%LvcTbq>RdHeO^m>{-oie~T`}^uiGeu$psn&?3-|;)#!r-ko~y!5t3${oRLibawvk z=)<9y-st@J&GEs-F|wLg*7NXj?Ds$sfO)(AeB1=g;G3WE;S(22Bb)F}U6&KEwa?P%D zrBbiCrKOu#1iP+&#NtVpAu6Si#V95>g=igNE|*J}5#%D_l%!m62RMr0IApUeL^D`+ zVU-CX(_sn978>VKG)I5rIHF@pLdE%za1@q5WXEshG&Ia7Rvodz<(#Af8Ojlfd7kQc zL~>2iIm+mapqnX)(T#LV&10np>7=886|Bml%_PDB*(!7)`BL{#tPlbR2@CR@>M|+e zd6aR5iAwaEF@&#~Kw3i(tKfuibTbutn9$Hbc|j;-Vy5TGB*mefUbm7QRU*t{BA{o} zl#ot&QrnWG_K`1oHAHz#|HuiVA&E0F9I4RsO1gtd>jXriikt~fGaBR(RlQ zj^>j3qNgf;W5!aUn_#fK(s&YC1XdtL?*NR#j%%(TN`;ktq#0K z&XPJ5%8J(0Qg2I~FtAV%5Gy)`>kHJA)Vi}XPuKl_IKwzO6Y@>y|3X#iZUR^k|NCmj#!ltl!*h9NJy90E}(vW^cuUQ_4AoG0G>9Mx(D|!^f zJK8d8Al}uqQH9vi(DA5rL`xZ09tX!2*m6n`?`+no;J7LqPXTcSmYx!h?M9!aM`q=+ zr)+crwO6lYMoHudqo2&N5X6Pr+3!oS=yoW&53=q<))C}Rl?IND?} zdP_JL#64$uM$#UTb4*AmzaHTX>rYCx$DFf(f)f_}(3 zI%IK{vM3UK5Qhn)LfICqsx4^-Kc#FRD5&r?p@Cq`x93~(mIP)(nd1;mQXFR%GXU>^ zCqADF8i_JIAMqfiBa(7o!hcO9vmc#TdjyHIMYn_&FKOuOXj;&6K+o_KThVYLc8Apg zor#@?khE-!j%GDv30ZJOQW@B0_0WV20J2cg8V=FUj0P!FQH$2bYf7R7H2hJ}4ryDi(xTC^48jl=)44D zv2qLGFc6zM66=OuQIkAiJHZpex34L=5r6p@wtN3m{CyIBKiJvZAH012V!v1HB+Xcc zG$lb6&5?17<0uq^LSt=jkc>)WQ*<+%rYxUK5zVyy78b`MZJpjmzdJoR)B3cB0x=?h zTCcV_Z4;|YFHzNp7v`Dx2DnQymz*Pqb|wMXXs6=6%0{8R$b_&sg#dw39$j4~vrF+t zAB(daF)2_OU{c)oA_|z$*d2dIzsEdD4iTt-kCy6|=+7I)v$9mgf}Ne3W-pBVscZLQ*>;Ig&BS6lMDNp_EW}5C zPEvYJBQhajfldhkiRd*+=O51B43VBS0%|{*^=CLqNIHDAzrVMw3+k?_^+WX*M8#Gc zt!JqeC#7zKq?eeV9iP8`cmDR^^ziud?eW3Ihx6mN$EWWvELR^IxWixkKzPLROybeX zgIWAhMdA#Rm7fG5DKTAlq$+tij{D;j#{uPmbJvWle$FC0ABm{%Co@A9yMmNHj_)LR zkmaNDD zVf*IPn#7u6$xwLPwXDTN)~^jpS0oO^ZfnCt{t3lI>ji!F&AQS<6xu>hB@%GChZRVt zx`|5BbE-6WTqz=2_*ex54>-2`ZbvVz6z@o&=FVHueCX4c`yfU+q|b@T+uz;UST@kl z@~Q*JxpI?tCU0q;bfU5RZfPk^v71gA_g4YPU8V z2g>zr%55xr*-j ze%32sk&kev-_iNyn|D86emH+~={-ID@Yeg@d%8JENrL(eJ@XL9;9d2@VmdG_IlHzyZ=J3eo0MVWw>Y$;@MGx-ws&P1_L$gpagy}Q z-|A}C*V*WpX=k@4)6VYm1(|m0((~-@m}EQa0FnE6y83_6c#;x+i{u|2@_!6=EBW7d zU#$H<9^|oY1$vBCj=w46*|I5}Mwk;EhHz2sra5T$p6U0`P=ZrD+k$K(;3Q}rL?<}S zv|GdYk1*asv^cHzJ>+XBJ-5v~jq2LV)2OaFIREX?TFo{^6X=?zS&kz?AYZj)P_nEx zJ{WEcNhG2R>@}q^5K6-mW1uqvD6};&b%co?VW9MiENJEKBx|{?dX=~aWl^&5sRxe} z+xl$Bi;i$`MND!{`6x=9YW=`f4V*G(ER`6;Ak@Xi7Pe0PA<@>*>Zdw;T@5d^as!TI zrW`MP&Lht;ew7D3sM^QsiwICoG4e-SNiQYx=v6iEhSr)25u|UACp7-J@dW-cz}y;~ zNBsnIu1;}>$jj24c#A7(Jc=5?+je|vOD|%a(Q6%JaL-M#_!bSpp_$jL`?US&&PT00 z)XF>-5F>sv_IEssT8o-=n)}-3wWwc>BBL@L{4UUI?c*Bqr4GD086U}$eAkCL$kP-$ zC*-EDepx#H(*&m~Gk~xDv_4*Akhi}0*9pyu2dvd?{v~fYh{G_Q(;A7XNPQrQQjZ6 zv4_q$Av^9>zX?a0CfkseagxYYU0#Ul1j)gUspf0Hc%vPNKckdD5D)VH#Y5g@kuyQX#5I`sb&y`6 z_K=ij+B9$clrAhSPpE2ue40n*oV$?%Qp@zqBC|&5XU52q0d<;Xz9?x)<60qqC#^*^ zuvjs+k=1%AFPW8^)#Xp0Tur%9lnb@gD7#kj>ecGGZzcTC3KADACJAKT|eEQuJ)fRJifw^-hAcxU-t$tcFOjj7th!Bp9guG61xu8xBsv6 ze%v+hMVGkIMG}%i{z-_gNQvMR8l#yCq)4SFthu!n zp}(;kQ8MR5MaKdZ03()il5%LMR=XbN{T#%PG6$aH%)q_ ziajeC-KkVo)naU_NF$V|w6MxoFh6NsKvL?#x&?5$cL|Euc1M1(y3!IvY$IQBODj2} z*@(es=S5A@K2XnL^X|MUN&=3se;0og?+dKNSKwUpQ5{dxE%X$C=W#|OLEFs5uvZW7 zducRo$s;1QGq3NJdjhX0^8D%-za}w>l`v0%^!at*2T{&5lKK$~a5VH35k3o%C$u^n z*LvO0F7X-t&S;FhEpM$Ge)UgR{ZHz(Xa);499pv!v4r|%dN8| zs-#znlEyGsYB5V9Q1y(yc`bkGdR7AFilwRcY(Q|%8#8(k)Tx7qe8I9lS6Pzn+{ZE` z`8se*JPgk{Nt$)jd>dXzZj4 z@tv7iA2iqhm(JtePpMSXXb0Vo@WrMWiqtX%Kq@{yb6UH{aiup6($ljsR# zcNS)*bzoNSBHK7cY*6JXVl}_)Y%v0~<3xFwsp81^C3Oqaqx+QTm74;(TomBEb7h`k zrg~m#mv?IWxU|r_%UG^R>NSp{?$m8$T6bYpy4?~kA847eOHJ5jxGmGSvf8agLRFhs zR;^eYmG1U*w*RnYGXA;GqVvDK!CuM!v$OYNe{KJHh^P5t$F==uZU4Dj%Kxf$g-v|1 zVh1>__wZA$Z~=+46i4*GNce_LCN!SdE@ca2n!y`PaMp<6Z0R`WNVZt`Th+xdcZpwa zj9sn?|5Ev26wVNBB{da0GArllHlN~@Z0myN4}UI`Od-Vckt|;Rq#KFYgjZl&T@r(2 zd9PP+6G~4f++@Ke9Y96v)x00KWswR&Z*lW>iJKbfQOz*2WVa1&cjgVDQxSwZ!ZBi#f^EL_ zdj5gQ_EGo{R#ngvSs!~T{Zl?&^}iWTDGo>X5&!pMuvJowvCr3_1j*hkrM}1MGpO}36EhZ}PkFju) zZFEQGvhcum!-LdqP1;S#`T~Mg`yPoU(J$WUYWB5_^$7|lBtEqb&n7K_f*>;!7_T2c zr{Q~@T2+em4`$8N{W@r^oW5o{CkAWi#%XW_O`^a zkh9pzGSR)GqUK>yv4wX<$-dP_EBlu48{yz8Pi}{ytlJGoS++BlWu7UMacQO{VLxohQ4G${Ndj!257JKv%7?JLC= zmb^Y?wf+R^dGgTIgn6|`|FvSEyV?KJ@#1rbaQdD`bz=B6$A3>BaTvcr?yP z8sn(a_>z>f;oOycl7*!=b(T_cEw84M1v>Gbj4N6JDXsJf{lD^lt>|mH`o`~7J(gQ} zx7RiVC1n08+HQ%zYi^syG^@6}ptRil>RXnMaE3?FPH#oS4GA3XI_Qx0+`j)#lPq!X zcv_#w{5hYl`+vdOw>XJle7~IkXXp9e-g^K45Kq(o|0Oz@g)HD(>o~BrF<^DZ zfa8w|Tr*>7#aP|S*x>EvTq$|OR_Ds29n8S(16yuw8o8DwGOq0W7hi5zIJ7xlY;Fn- zaL%yu2-waVwsrxb`gt6n7kesd|3bN*^#L#Gv#Z;L-!jAq;)U$j3;eO(>q|tjz7h$v zF*$mpJS*;C6$$|ft+7Bf<{7~uOv!Yv?nr^V6d++C%QO2*RTd+fal~%oHswUCg2x}l zB&1pGD3pyav5EOIHkaCgY4S8I-+gP8#Rc(NvSHmo+mO|bVYAMsZfGh}os=%BwQi^a z+RC{l8kg5fWda953ywx4z(N3XHY4_RSb+JMk|>myNx?*yG7e<*rn=(x==4J0A0$MF zyqxL-#}i`mZnbFhi;I*zEWO$MLjNg`qk7BAjTK1&JoPDZr2w^rZ4_~V!hO2b`b0K{t(>uKf6_dXialxJu&B@^=TB8jM6yxgnm`-2|p znBq%C)R?K}h;z@$-m@@o&w|zWPqZCgX8#9H>bKYgF4+GKUJfefzt8vA`@aWy*89Kp z{_g?zf9Hg==(;!-0g(b)L6Sss_(NaPFLXMs+!RISQVL5RuNVReX96D= zY#~+DPt88iS>+_eO2aEzXu8V(goXFz|GocmP|p9fv-jfVTK+%8v-ba4i~Rc)`6eDr z?q1Y|bMh!RqFfsOei0iE!4*>J_q4E6=c+Dmx8)lN+Q;y(z(=7|kY~GN1d?9`dr1gF?X(RqNa-lK%D|9 z4#~%@B8$?~GF~OjzBf6B?6>fv$h$6U+Lp@3_F5UhK|{s8Z%#3{+q3cLITg%clyFv! z+eKYe?O(Qw*sZ8X$2D1<2npZ6x$p`fFX;sD@D;eZ8*5Ri!^YjD7|f^4*s;otrT=rLZ0)yCrzrfrvG8q^Hac9cbI3XRRISL%A+IRbx)F zw~~@bB~U8AaD2c&aFRMPB&CwzJatL+z(JiKQJC1T^!BN8OtCNV$L%%8>)4|zCYZ$EE|gr$eI3f z%mW;$JKI$eyMwpY|GMk{Je#Dq$^N=n{~uJ&{|Ece*YTeZ@-*xJ7kI|=c!G{25@ach z;STThW?;P;xP2a2<+!Y6N#|GqBYf^N7{Fk)&@Z`+&OdB8K>a{2PrxMu0$SeJoE zNL@k#Oox|<2`IODPeB2dF~+?NL_N$Cuy^V>)mRrO5OY#Jn0?3RM& z?juw}^T9``hDJi`e}Z;>i3V+ok8b&Ag$bB zIqZdm!~9eqbeMBlQBup87{|XKynQ1{DAi0hE;Yt|67F8VEUSv zsDi0A3G|XGr%wwoU|}qM9A)Nc5|+?I>dJH|lSFxNB_I-iGDM~%cdx&n;V2rq?aH1G z%g6*tK191aJG1hK8JV$kK1BOJQumV@nFdgU7El8hRD@G2d}uM2`z@efEr(@Whj(32 zd8}fhWWv9eX=%p74g^yCP;-N`p2dDqTHUd`34?6vR+;*<^q=Y#S@Q9$KtSwk90zka zypd-wEf-L@F#B~*iRw@D$;3_@|GN70vKNXl^;h>ljf>xHR0z-*8KSQ_;So zUwx@w)$e{-(T)9OJe+&`b&K^^ZpjQw6C7iX&Sg7EI~PruZL)c69MB|CV7(cUZ!@+Qz3b#R3GeGkx9yjP<7*q%x!(N zuR@N{&!vn`S7eTolwH$Mu0cS*O%>^c3lf>c7-BVvCp{ZeXlTN6s=Eeeflh0sXpFtEn$e!oAXch>-K*%=lS2@%N6q zlS&ED(74^kSTL4JEbz0jC;e4VSn-!&2EkyOw>*>8!_Xx*{!&FUffhfIwV+%J%C(@Z z7nH9t&mKouRs`i*Kt8m9tce*kdqA0Q%H*2Tb6X0g6n!{30$x2CgBKv>MG;7JVon+; zH_WZK@gxqV4ViMNEX^QzoRM?`lA91KO_eX2(2#T+TVk$igT57(Zi#6eO;}2^snp>^ zW8qbe-Qc;8eqhYM*?1pVRpb5EWfO4h%v<*$kWS~XHsz)nLbhNCrf>>%K5Jo}HGd5HCtF$>| zkzZxmpSrUE5`&~PtxY>&Q%=KPovIaEF5gZCmB*-BL#G}RwPNB{vb?oDNJ=Agigezb zK(`@(kroe@C%6T>*Orjii8yqRgf>eVc8ab%mE>_E_S%#8xC6B0Dxn6cIzTl8Q!^-k z>cNm}iI)+c^~-jNQYxh{MP9Irgai~vko!x<#eo2f;G|5bCNa$JJs%ytJN4e9(}TCi z==D_EDuW0ESU*%ZsJiIzhW zL^u_tVqLX37ggFg!JJ0(BAIlMrvjEPFPhiZy@GZIu(ePlaxJs5&*)-<|#R+S4gXdg26V6#~Qp}|Us1V2?OO3xAr%)XzJ7T8* zIhyOFXJ79!D;~YoVKbVEPEn<^7;vn;D&G}LNsNlfIrh8C~BSwjFr zw@pCzHFeolJHzROm~bs4itZGZ1xP3(zQj}sucBR*6hIBQ*rO=9T|_^pB;)SRLQG3B z$!hxdRL9j+yQ%3+uO;$r)$7P=HNIjp>Ha8EIY{(ujGfKVSjd`9v5OL9)) z*q*7cx>AxjE%Ydf<_cv>8Geh_OHJ4vD6$;18k9OsrRB7xhS3`3Gzt%KFs&SGSUYVF z1!_3;()nIL6MYz|CeYGRA|xM6Z2^QzXW4p zIF*5^2AsnTjZG}ZcOm~&*{=HLsS$$RS7W(WbXHs>?n)!*>7NbjT2se0nx2JmV;x(D z%C%iWsb4%R)g4)lJV#i2F7(dlIfpF1CNUK|iefvUjyGKEmfAvbwFNa7_Sl<(Yn)O% ziePi1QjME(7>uo;^{Zi%8F+&L;noNlL-8fa*ijmF*Q0!uPP)9)*G7J zY-m(NS7bg!Kasiafzv5yx#?8IWYaGOeZ%q)%1s)55?>q2%YI!PAD$n-zdSp*xcKGW z`H`~#)z5GeU%yV-OyA{Ibtr<_?Q!+pyCQSXy5`C9DSg$&%c#_xtxhSeK@bk+l|1=L z*$nz9>;FXN=VZL-mhSqUqi<$E49%xcvdN`XK?nV+27SL>knJtg4JnC^WLNaWwyZy+ zxpm9R@ij^3#<>o2gi$t6^faLyOhRmII1bG3G_cMxf0S=f***dj9r9Lj=?9tQnY>ye z#WyI#8Ah9MrYTl7-m{JuU(<-76DY4Hpt?^4bD6r3jb$aYX{k3XMSwo1@gyQj+*os2 z7wwh0^3%^&XWomx7dA6V1=Wc;%cZyg!eWDp7zjD56HHTXbwtQeC`GxP=SuX)*XnfC zSM;YDnJIfA0GEX^L@?nohWQ-AtyA2`|;!wOml*O*4Q z{Zt;gC-c>h2suZR$~!}hT)NCzJ#x?M>)WY0#MjKLp*Hg_jNz0;A(yF6AuvSMvPkm| zzTGg%ZFt2LLl$<1Q3o!x0t4-WmQ^RqUQ9 zgt^h6oh4CyHF~N#6foZ&u@w(VV9RC=QpJ=+Rm=@vIrv3ASz zB49+l^uU&aNu&^;;?(|(MkKo-Bz7;g;YBw@6G=+?>h>C?)Cia(GG-}}7v9*ePO#=C z+6zlw6Ks+?o0B%xd~rFM012l*61|w*2nEOoKRu9xNuDMQ4(ptKtz<%LiwC|Tdb74+ z?!}S3%28n#Q5m7-y+`I$P)sCw2(?v6Z_D$p&@AQDrkEFOW_EP~^`>ovuFw5Bo)vL` zjasSJL1jVdxaU@dt83qY@Izk8Smx={>f5nHvmx??HrQv`l%##x2;53!RZ}QBZho+t zl=tL%_CzNo=+8cVlD1@JI29A##Q=Kr%!VPI+s)!(f09xiplXOkhs$u_i}t^&siZ%Z z>y;#-5f7RoCvuA55xW+{EXUMk8=(zNSRsQH8!3@;P@Vxoz2B@KhaNacS)f!zG{|Ho zglIr&Cd(H3J?9yk;VXi8o~n&N4FgC>3S}*R7i=ZF*y1~z&TnlSca9oYcJk+*sX1-g z($>8WuFWbf&nt!2Q}I@$K~WV-ICWNc-esSS)R+SM{r*D1(-`+;C>h%qt+_UP>sQYb zU$#Gtq)_HD9s2(Cv!t-pla8u%w)z44GawF!4^!d;ti*CyPx2^XzRxN8&c+Jt*& z6Rzz_ur}ka&A5+e#%*;&zpDd!qy4wV9a~v}EvxS$i)g(Cw5#D$&s58fTwQcq`EW5VPcI2$(ZM2V_sr7!I?)iV^ecHE4 z0NkGcckjjS;AJ`g@9z5k*N1u5_dl&O|2|;m-@2P0ur#ne<46mcA{t4rRywZc z$j4*)5qW*DCbxY3SSKkFRbmzr1UKJH)1HrhAt++(3SkI|Mm?$Ma#akwS(-lJg3IEi}=qsKyW#?tHzaM8rD#!R``vHp~&>Cvnz z*ezlKj-vU$Zq!{U;^JfK5A}^8P3S-i%i*N$yzUJpXj9&avK*`=CD)YY@`zcbG3#~d z;qk}pPuxR9$IC&iA4?}jcu6(_iM4yYL!m89b(d&zsn6u~(?DQW1 zwWaaW%UaDEr+sw-%T^*#fpM$}0yj3IQp-q`D#goK>D-_g_c`65=@8%cj z4$?7tm$|q2=r*%R;VYW4mA)3~qRl{x8Ju$i%_4Te=2Hgec)_kx9_Mns%6Gf69#<{A9{b75!Y(!?Nt zxI^aY-Gs@+rM2h!30m4AIk}F-=z8u3MR(AB+mkXup{|Vu?dY4nTVrLE|6UY;_D`rs!XTkK1 z(x~J`#4P38Gpye>RpbPYiVnt*M|?g+hY=-lc5)`ZN~M=0p3&fHj#OD*Bn}UtFgiKg z(lUF9PFYM^^bIvH%wE16Z0GaQ1syjrVcxmE%1Dj$*euu|#sZVMuSI>ZSc z(THZ0aQ~-#L{vfRhRH0YGi7)5u5GTR$!k2L(fq7>`_LKWus_OibdllUsvhrBeQ73X zxVuw{V{>_DH>;OxN7K`&+4U27Wsa|IOnLWB8F+vtL`F4gpGCuua-|Nm?xDp;+203P zy?(FgZeVn8i>c$4^qfQ%RP)GIab1S@s)|+l_D$0tPG;96J!G>4rv!Qb_Wi)$-SKz4 zI_8rV-C>tnN{JP!o=ye*8l{OMSL522rFji`rCYxA&UCkV$gC#y)VHRvA-?m@kpcBQ zvTs@{twjNppBGDwE&&Z~d z_&s9T^fHV1C5{!xz@@ZYhynS0R#p4%Q~q7|QddPrLzfT{j_0WKe1@LsOKMH4=#*tv z_0KA-lW;-)C9BHS;a07}nQFPJ7z0(6i>k8n$=Dto853v)?SP6^fCkkP0u6(7o@7D^ zjYFgqeF08RcJ0?8Pec_Iy?+Hal{XBBKi~+*0ZDrjtcD1SAd91U2k(z%j~HuJo7z>b zr0t;A=z|&nwJH!=Mw6+7x>&hE3-zozxI%Y#=;UfhbyYG)TSAt;RExN)LLu}4iy(C0 zv^teLQJ_WwSU|h0OlWzf$vVX7W-qAX+G{;^DXZ-FrBPT*aT7y%Eu30j*&Q9gGH+BD zdXP&-k7-=KdKDW@Oj?X6Uv$WhYlbq`bJql+-+yo0KXiPO*HFzEJL(~MMg1o0hOTrE zV}75*=}>*ykt^A93L5^Sk)nec%P8t)H3>DYfx60_w)o{ixSZiD@;41xlisbp+)Aj? zY_JO4Z9z1^J8b(~7bp$rthCp^*G>4JZ3;NTT~kbn_3A#g!_=s~kx(TQfLr{)2VeDi z%+ksOe2Qywp43m!OF@}SvAzzZb7+DB0^B*5Jy2SFX;xO zslL50=LEffb0PI^a8vq)n3s^yMKYmu{6sh8St>SVM#{_xpGx)n{hJG2Kfp1<5x4H2 zh0WQBOz|~ksVrZ7W0OflWN3TE=QWG?F&H;Pglc@n0ZR?mbyju3Wf3DvWv^_fIh6g$VX7mY_&e$B1HH>I%Wr_HV?DjsRYG=$lV+P_=<-5@EF^hPySB0J1 z=Y>1e+Fk3ykDr|lt-A-U+mY7pQj2|K&F)uiDCI7*b1O9)+IwiFX77s@p|Ijs8a|rl zYg?&SY9z`XC{`uFZCi2sVq;%dX*GSZv0r5?MPJ&SoAvIlZ4E)5B{{tEKBi8Z0W@hX zY&sRKtRvN>ONWpaBC5O;bsm7*?QT$-RcE`PJZI^wbSqOwt>W!O2{r`{KK4wne>%(mjHP&TFZo|~ z_V)+X{4ayG{C|k2DgVoNYx%#H|M!;%=0|0VMk!+%8nZ}L5p6?8;F~FlWx{4OWjsR( z&ZdMnCKkvOaHr~=UZCB-9lX&;XE>Wmw{f1200DLQU^byBW7R#VXHwP*#^)J{1LDtE z9%mO4rspR14R93ceRZA7rIfmd5$4>!5O`rxJIcmKp6U?9I`{DnfvmCm<$@?=t0xl1 zvJOZiousoa^1g{qW)RHDVNGm*r7~2g5Am-r`{m1a4J}g=5zLE_!fkLDRQR-5w zY})mBi4ZyM0%=2Ej6Xhq0o#3Qyf!1)AcGDjA{sG zGfeGKl~Z6`YjJ6RnvS-bu;_N)wKN(I`Aa8dO9N9UYhAiy>NL~V;IuRzwP$@Rkd2*_ zObqoQb;e}!du{FIB(r5C@}#pk$n>~4VJ&)G$Gy5Oi?%26n5jq}cndFelBh1Sxp;o7 z8M@yp*ALjwsan@n3NnzcS7a_zd1!FIiDkchflc+1j%lsUMFi5J;W;QsQ+!>x2sb}g zXBsXhahhNg-k}&$p*msV;JxVK^uNdu4R&_k(k{hu5D?DavXBhXIl&=hq#E+_xhEq|D}`*p2)-oOV-v__vPCx z5-C}5RJBXF+6=!(o>wkoTtolx%H{jzLoSOlZgZcClr62OFVf|lvM!zlO*e~J?p3wb zv#CV8p{b@A0&U%~>Bdyw-C~znBs$RQ7t=e5}quT7#Nr4lT zTcE)?67)84^-VgPjy%3B>S$L2uW$pGxkZ#qIDqQikKU&i2poqsE5G)7-2~^R>SuL9 z(_w_^tmnY14WJSZ z`V=WQ_Qvq*04F%0nZ5>4t#c|zN=0>||D}CK3Yk&37uD7&3hGGY5{1r5AP8=UJy(ES zS4-*$VQXPN1@9%VW{$ZE5c=|^FD--04IZ+nO&JHxTx&d6u1?l!Hq&XJT#M5&yC4c0EXN8pOxT*dV zgzc>C=^Fo&XEfrsivC#`|G769l;b~lUheO#<9{CFX^#I{hx)8TeeNFWb52ANjakEG9H=e+Ke#6>bm3228axP~xt31x8e#ZQ4O5`j1rnUtNNCCX;#+c0$ za>9`(6?zZ(vIk`!yex_ICttOSDvow_l{Qta&nRPm$Fr!{P`@ot+BwNmO0LZ|2(}|C zY6)cd7R)#EdM2igihL+2-bYVqOtW+5YF$p&z7O=%sR1K}|M_ejDJE&gxL z;+iYs6r>}u>X(#T4c99W>Mimqw!s-C&F)TtZCSmvZkO8r7QM@hyKLDQO-VF^TV>;C z8RB^YsVhQ45+N!PXGKyvA;@CPvpLFA8eExU5Hus{1a93H0)n7TQ+t!mXr@#iNno$n z)5L)8cxaQ83~d&4%ozMDuN8nx#(T&+m9})S> zW(7R{(G;csrcbN>7qS4XK=Q`GlxMeVqh0@dzWegU^RoW8xBqgj|2@Rx`{+Gnh-B%N zP)Z_K(Ze!ZFTEz{5O{*4gG_2ILQL-aJLn{a?Jt-Hqm3uNZ|MTQuhp+WfsvfWT^(#R z-`*GMm+xD8C?p|~N~n*}K@`c{s6sL0B)wKaj=qmBLvl?bmXNeR$!SRXaMn7M!8wO{ znu<;CgkFl;F|(WXzn@#Oo3kA!t%T>%BwB%y!C%2bR(l0CcG?$4+ zaSWk;*Oc7AKz%D{6;jD2HuZ(3^Dk{_KbxMWO8>F}sS{so+jjToGkPs(SbcwAIR56i zWBk?)$KUK^9KSsnj!w#zc2ogJH(}Io4}zncxUw^nHX-i?N5TH)JZ2)#X#{Jor-3tk zGt4uRo?*^!SQ@qtpBO!PYl|)}2l4({%xU!N#F3LAC7C}VvDhCh#8DNW6N6)R z0e`h%`q(&<`1*CqW)jb@6UXjL$AjWX<4H<5_X8G>=>)PrF2GdasB1mU@>aspIwbKq zkLu9QU$eCHdTsnb9+r-( z(Qae?C^*{QHy!lwM=>**fUj(_m$KFdRFmJ&%qfTshE%A0Gop_ky|( zwv@-CW7qZiQE)8X-S}1V;|!-1ha+wGNg2z|Fy|jmjw+p&x2LqiDSGX689(Sc^W?sQf;t27)*579Z{EK-*j$d4#d zUsZe{ByP_b3Q0<@A&8BETedF7eY=6udGgQ>PG6(m|2f$ZbL z5@kw<%nsu6n@oal@Yb0-Rc`zgeKGD&4Dl(;qfiIDf@9aFique!dnmza)XEKl*wSKa+0#**qab zGqdG8@)6u9BguP1^z73o86{u>5{q~TbqpMijCLd796JztKt z#BAvYDG9lQ{|Iv$9OT*bE*&VT6)k;;*o4Ln9RM(=@+Qh|UA_q8AdB2i3xPpl z5KE2%7L=MQXcIDilaPeH+sZZFQV#)yTgx^D=a#}v)qZ^G7DRZKEGOF(?kbShBm{s< z^W-~auQQ>xeOQfg7^XwyL5tpoiGY&V16N)PZXcrk{XO|gVps(4@@R;>l!P=TK?e6I zN%sgnTNh7O)M>6-$LogUCJaX^&d`BRVFAX`4W9GL=pYG6Cg6~6)*DX@W2^W@<3E0b z=OqnGxv7+$3tgx*CKqCAD8ty6(9_0r@)jps$oGAx{fhrbseRot zKNL#prLJn ziPL}I_gluUY2XBywy_ic{f(sL85aUs_nM7C)Yi>`*y(jaJuQ4To=EA#Vib?)B3Wan z*}+M|x334{wM@-ZG$Y*gRKwXwYQ&LmN z$hP*5lv&tC;Dr@jYIOz`$x(!2B!%RvM5OViSWZB~u1#*`ln?}kM7*s^zqqukG`FhB zX{L8N-&VbUhB9bL#;vI9UGpfKqd#&S(J>{V4(Nty0-dP?WL$KHkSq!C#2i_r)IK!N ztX(eVY8P7MOF}f}8NneCD4nZ&?qT^Qh~=5O$xH5wW4O1U-Ne2tMx{&65Sfr)ke)21 z6B^^lGf6m2tgi_^(w|(r1NhIB=v&}%pF%)wulf@lkrdxT9*jtaEumn^MDY^1zBI8x zoSLj8Ca75*GFo@o-)S1FmMb$+1(a)GF$>9sk|s`Mxcy3$i!>WGbt zMPQ_}2$0n7SHHR4qD>oBI@1|pO!>mcNlgxxT5eJj#J0s800LF;l!6x=y6(ev>3WMT zCx}y9h8RGLq_K2ms}aevwk?;@1?;fmXPH(`q(l^|do%}$DFjKgBk`uRvgKTC>aw1? zYVF*Nf<@8Qk+kI8){&-0o0EKC7P$sWS`K%vHQBy0R4MrqqJ>sHpu#w4!z)+Twz-U5 zk=PJaxuM+$hq^v)%Tm+S2F6NQ$lIu@Tm%R7`lqt4VtnWd8!pe0#gT&|L55= zR<%Q;RCLzo7U&+`l@CEcUcc#chtS>fy+Y`V5*2!|R)6qU_}?upDr8;WWtLl>w9X_~ zAKG_(Wgk~_IOZ63*jV#)N3}#?kg|L-tvXm*X0NhCV;yFC`dMhfkhkMx5E96(4yTeQ z>kz%SGX%>79q|O=c>XZ-LT~XLjX=;y>lK*U(G89>%hrvJF$~OEoXv`iWi+FNqfK?( zD=DikDpz8pS6LI@zqx2y6jWZ<_irxrD8+`CYX-NlGiYYD(wTkR*l$xyf2dTKQ@iG% zBeYIBv<%K}SmhX35g!t^HKF02@``=~ec>#G#iZL=uf%&(kX-%f>vnp#J_80gkAF-O zk7&t*;9sO8$Ho(&#lquY`#?mVn4Yo_M1p)L7mIkLk|bNlXN6sGK14iENZK#Hs^d*q zIf~O32@EH+e4O&ioTtFM2-Emdec5@n}iMEfT)h3!G><&;Uw{kwbZ9< zy95V85;>nwWnL}#(tqji_&d!YXv{Mli>8&;uDR$8$9O`*{%Af#?`af~w4~YCOwzC6 zhYY6^lF4zgSV#6kZ~*Vyugw_l?d)nk=+_Ll4YtG`wJhTRe)V1RjN!Iztm6oDqOkby zw_sQ5y~Yt_)~`Fw#hl|fTx>^m1(LGp>B{n@Mxr=#RoVMJ zb4pt`uMBJ-N68eI$epnWrg%S(Q-Z^JpTuFpXe<<8FH3V`U?=$~qI`PFvU5@JAPiH& zIpKEZd5z+k!`cN~(Kyq+(UR!p&dyG$V8km$231(U&pwul4Zi!XS}gb@a*KVpv%gy{ zmQ7PS&PpW*FUqI|Ngq$tUZp7bV(?;j;Hdn9-E1S+%2j`pxv8W2KgDq<_I&Y(;WQMQ zq|Sm?s8MJpfVx`%H?LGxHIx4rJt@JFW^z z#I}ZJ9EEf|CMgWBQb5Y?ri~FuL+R{Tz!V=XC$fb=9H+`tWkhKJrrp_Ez}7geS5OpX zgf^xNbqCjqtSBpRv>-R*Yw6un#(32-sG%A)90Ys~q%-NEWq~p7n;Hhl`$hnTUug{p zW1TIm^FTf*&Pu&Qqx^Sj8}^ zf!a8kI>eI{4B7;)ZaQ@c0;!_eHleylY}6p!ZAx_rxTxZ+3U#7xb96)v&Z=+|+TyuRwpONPlmk|C^KlF1JjapVc|Izs~>rFps;leq!Z$wd{jBqNT=f%{pF;XngfV zYOyu^^W<7L^Ga-h*XhE)j;H(lw+v=Wwy|LT@4wvNDbN3xgT3|qe~71Y^NUHkU2`kU z?f{*#D~h+!!TW!HFABV6Ass76I&Kx;JnZT6X-Ge!*DQ}inMQtnqs;n7ndNSj*?1!F zp8#{uH^?{vQK`3reqBhcA&SYzobUN8#)%l2;b2FZcS06ik@Ro*=-etTU;0$W@I{zs zG^qKmzW4!@n=m$s$WO1hUGnM`_a}h{HKExwA06sr+;cJ!qcb0($yAk*-BFGyvWwDb zhs%RMJ|D4A(2ShyIfIn&%^)se&OL(QW;1 zIFQYY@g>i=v~+@En<>wsG1w|WYX=hRzZJBPGn{4-9`SAcVJNGMvcIQ96%(VdP2-GA zq)D^?JHEz)!SJO&kY;e9X$P6^%!p<&p3!afb~x|{U~-GtWJ2SKE=6$0x5ZQ!e+{4e z&m}CiH7>zYZ-;{&|GEE4l#3@c{#Ysn??ijA`XL!n9QOwRQ*SpkwbMKfrt=VIcw7D5 z*F}Z_i8IDu^#^EDOd zCQT8Z%iL%bG^I%#_7duF8)>qf^W3!xjg_Qlx_!N;|a`rA<^FAB%!g4y>jJH zjR-A?qlOY?SyW%DBx4#%71AYj-n*itp|UGUDgwVMMjGJQGNhqMNpv)nDvJlVPqhM} zRHQVq?XQdUzwY{fqo3?T9kX5k-`(4LS-$^q|HbZl|NkIQ)BTSx(ZMWa0pCJDkSIcj zIL0Ba-x93%0_(lNBit@o+yqz`uVKlkzmX^*skz^A(f!h@#G(rkYZ@)zi>M%pl3=?Y zevf?f!z#ekg?+kElF~REBk!;LuiUfhd-`TS&#FzwXmg7BPh@VR=K}4ycUuCce4@8YZR2`AJLV9RQO_s)|re@1P6|hz`WHrs-&)C&9 zblX9!=);`{vs$w2pLtZq~#V3R=~83YPHD~jcx_-{fuq3ncI$RjmfI> z*w)ou;Y6(g|Bw^4ruq9B%({kdJDfkd=0%h$`(pk0)!QPgW7t%%<2csWZW+s__U<&C z^)T;gNE;e!9@ci`fvo=J^pSN|$qjIMwiVo5QD>f_y> z`1W^}LnsrDI5Sy})oCb=Co)fm*&uA0Z9H5=uXjF(j^XeOGJ5)5ReGHm_)*m1?QhY$ zJVnZXQS85_B$}CXZJsCK^&b+F9I|ANI4dHGr<9`@^K6c?lm=Hwc30uY^AT)sjwFiX zl+FG(XEEBW#Kh*KLsihz+!%_#fe4;P*(y=e@!3tb7FHTvUXH(atJ_MUO6bEN|`KQUU{uzy4?2|aCc2*Lw z0QIZ)SN^LvM4x{31R$|D|J57Hw_Cq@8lwFUW|jRypl5!6QM;KI>}20%uc}t_E<;*l zq^VLkoSrWp-8yqm3CiK(f$oUU+qyBA4b+T!I6Y_>cU48wR{dBo_L3N8`jm!$rK7Lt z-j1cFhVG?}EFB~AhnV3O$J*aZtnBp7E(ZrZEfov0ZXt6pk(#@*#z>|>{(;g zt+K@=NIS4-ot?c5rURRH#i*85RTDMLItkJycGZ<6JLq2vSXR;GL6}z4e-WmJET*@2 zVPB_*t7710pton?dMkDf(@eYZaf4aE0;>x%FN0e>M^BqsdfLL%QDc zVQyr3R8em|NM&qo0PI_BZ{s%d-`4^5I}Eh{{WmFM`60*@2`a>&cEc_Ga$@>qR1w!{I#qI2sORfhm_%aWVTI849Af z=#L{Jlsh9y{9+^5U@#cGIXpD~2ZKTL|6q7Ld@(#64h}{~#|Ois7lYx^=;-(b3^p_U z@+*^yh!=yk=V~hVKXQ?j{)R$Q&OEps?gS`(K~rU(uQ5quq7X8P0Vockg$RO*5Cjb) z1#|pBOxev2BMExYfvTO`Vww|o;0|^^x8mE&TwUw`mc$v#%>{r**8k!0;h?$xk46XE z_5U2#D;V>1X4mq+{{^GLXaE<2un=GFwEKo|&TlB$htuo-zt#jl^MFpNPjtYL74qHj zUO_JcdJo_6j0J|ueYx{WkNk=H$aGxESb)Mrl+%>>5f(lBkOw9hxdYhM1Ww-N?EOu% znDGn}G6Tky4pE>YDq%`v1ibe#RX`c|JV|3pn2&HrRb=MLxoAn?kDLdeC_)(!@Oe4| zKCKc0QMu6eij+#de*f<7&Lu`1_P%`PH|*kvqtT{mNpL5(NI0QGo$W zWE`WprZJ4DD?gUkoh5I1+RNWC_yb(~T1f686X0<{{Dr*Fs0b$^r zUcuRw1K*rpon7r~CcmFu|8Vi^HT-^hd3kz%efI4YTwKEV;{5H|_1VSw6_9x7-b=(C=x2A z!3$x5W)stdsvJTsz1){OFL%P2Pe|;&+yQt6mqo~;D9x)$H_zjk2Mq5sdEjzJQtcUU zsB;WW6mb6r#Shu!vO;YeUCK2D#zZRWcl75yu5ai<&chggJdst(n-l4Ve!(iFD#|8f z&NQ%4Twt3xkg`Pw=$bZl@kVZLiT_KObB4%f8r1?FVh3w_EIhQheG_nzF6;m|vFhkAVg++-8Pn|7eTf4m7Z9? z2<`||(6_UYOhsB;`(Dza&?KytxUmmuj6@V=1gUN4PaCJ+Sf- zv^@k3upmkWWoin}Yx!C_mGRC+PUuud(<$smME;1gs(5v#dxbo9$eh0eyHhHpg55OE z{F{a-6@GhrRh2N7SZ7)1Qc7GkSiZq24VyRA8MDBsR^0arXX_vZaEX$~w-#qi^Uv32 zzJ;>emdY#3rm;P!i%@YLfLA)VIocqY#4%i)pZ-$Wm(AJDX$!}I2ZaTT4PAwyUWOX; z;73F#m=H;2f#}EZJa4N3nKD1lg4Nf+Zh|62*bS(!>>%wYr#jx*19$H!UMSO>l^xNM z5VbwhMO3(^F=fL)9V3@GYR+pzF6)^7)A+7yqNh5sl@G7%%=XBZE88T6Bdz-&llrxg zlQpv3`GhStOT5~iazoy2G5$2ZZBO%5&vuloR`zYz*bOe!4))KvP&=moG|udr=&A1f zzFuAgk=GZy{nuTKZ2NE#;fj9j9=*hui{@L)o!vY?kxLg$w%D!9g*Rq(|BOC!&T7%1 z6IOe-Af2tJeU@~d;5Exhd$4&2%*Dwq-fyR)xm+vfqEB*JUM5=|Werc)6*Qd=IMw3Y znwau45pP(!vg)^{Xn{}AmaOHB`5P9pqky)ixJ4;*!-Cc-q-%;;)KNDqVy!&8rikwa zZdkgaEWD;*c@=rX!c}Y2YYLcGus1AVwJyGT@@oocYWEu!Z+;%Irf|in z!G`6l9Y(AvVtHh-VHxX3A?vgx!>e1Bz9 z?uCV`vA_VR{rmyw6jH%&X^@}#Ejdmt4gsr2fW@6{PY8`YK>ZJPi{srr-S-F2z$Xky zEO~KITAYSb7D^*=xk1=3xABAp^UWvFsX07D3{2s8wUpkP=`*Fn{V(An6OezusP|tH z#z}duEweP`LP3C-j(Iu*$>9!*<8CT}5vgXN1odygs+-Fvvx(W>ytN`u1yBAVIfLC2 zqqeZ=%4{;u37rCsfxb6Ja0-!~xypNc&6Hh3LF#FoM)kNV#&W#Phk@m(*{w9ZKGfID zRvp@&*+8@6Qs?iE<>{K98ZF6GR4=J}rlxMl^xw-a=DJ%~;h_&pzDZJR$l;(Ke1a``mezfte!c3&=KVU0q| zs<3nl_?KSc6~tEi8&Iz4R;4osJWB@7&AJce8I|o^%(I5lkHKuK2HHjlt^n9}DY(vJ z1+=arNhefs0a9@hP_cplTm`21Y$&z1UM68~gj~CI0E(N(=OUI*hG$1rw+)NX7b`%t ztIpnJvjR+4m8cV{`4k;s4Slf)uEih=hvMlG$mTYA7SI;C++HUB&JIO|%-07nJo|yjB78$H>-==x8ydqa~P*>QISRM0NC>u#QSxKPK(mK69 zzup`l9&Nw>e2%OA<>$$&Uw&48D7O8GbNdnJM|{Njxk10Zw%7LBUfb)%e*ypi|Nn0} J;zR&M007eHV>bW* literal 0 HcmV?d00001 diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/configmap.yaml b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/configmap.yaml new file mode 100644 index 0000000..eaa7f34 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/configmap.yaml @@ -0,0 +1,19 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} + +{{ include "dcaegen2-services-common.configMap" . }} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/deployment.yaml b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/deployment.yaml new file mode 100644 index 0000000..6d554d3 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/deployment.yaml @@ -0,0 +1,18 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} +{{ include "dcaegen2-services-common.microserviceDeployment" . }} \ No newline at end of file diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/secret.yaml b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/secret.yaml new file mode 100644 index 0000000..2162630 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/secret.yaml @@ -0,0 +1,19 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} + +{{ include "common.secretFast" . }} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/service.yaml b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/service.yaml new file mode 100644 index 0000000..115398a --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/templates/service.yaml @@ -0,0 +1,19 @@ +{{/* + # ============LICENSE_START======================================================= + # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + # ================================================================================ + # 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. + # ============LICENSE_END========================================================= +*/}} + +{{ include "common.service" . }} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/values.yaml b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/values.yaml new file mode 100644 index 0000000..e83afd3 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/blueprint/base/values.yaml @@ -0,0 +1,96 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + +################################################################# +# Global configuration defaults. +################################################################# +global: + nodePortPrefix: 302 + nodePortPrefixExt: 304 + +################################################################# +# Filebeat configuration defaults. +################################################################# +filebeatConfig: + logstashServiceName: log-ls + logstashPort: 5044 + +################################################################# +# initContainer images. +################################################################# +tlsImage: onap/org.onap.dcaegen2.deployments.tls-init-container:2.1.0 +consulLoaderImage: onap/org.onap.dcaegen2.deployments.consul-loader-container:1.1.0 + +################################################################# +# Application configuration defaults. +################################################################# +# application image +image: TBD #DONE +pullPolicy: Always + +#policy sync image +dcaePolicySyncImage: onap/org.onap.dcaegen2.deployments.dcae-services-policy-sync:1.0.1 + +#postgres enable/disable +postgres: + enabled: false + +# log directory where logging sidecar should look for log files +# if absent, no sidecar will be deployed +#logDirectory: TBD #/opt/app/VESCollector/logs #DONE + +# directory where TLS certs should be stored +# if absent, no certs will be retrieved and stored +#certDirectory: TBD #/opt/app/dcae-certificate #DONE + +# TLS role -- set to true if microservice acts as server +# If true, an init container will retrieve a server cert +# and key from AAF and mount them in certDirectory. +#tlsServer: TBD #DONE + +# dependencies +readinessCheck: + wait_for: + - dcae-config-binding-service + - aaf-cm + +# probe configuration #NEED DISCUSSION +readiness: + initialDelaySeconds: TBD + periodSeconds: TBD + path: TBD + scheme: TBD + port: TBD + +# Resource Limit flavor -By Default using small +flavor: small +# Segregation for Different environment (Small and Large) +resources: + small: + limits: + cpu: 2 + memory: 2Gi + requests: + cpu: 1 + memory: 1Gi + large: + limits: + cpu: 4 + memory: 4Gi + requests: + cpu: 2 + memory: 2Gi + unlimited: {} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/invalidSpecNoHelm.json b/mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/invalidSpecNoHelm.json new file mode 100644 index 0000000..f5e27f6 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/invalidSpecNoHelm.json @@ -0,0 +1,419 @@ +{ + "self": { + "version": "1.8.0", + "name": "dcae-ves-collector", + "description": "Collector for receiving VES events through restful interface", + "component_type": "docker" + }, + "streams": { + "subscribes": [], + "publishes": [ + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-fault" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-measurement" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-syslog" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-heartbeat" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-other" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-mobileflow" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-statechange" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-thresholdCrossingAlert" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-voicequality" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-sipsignaling" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-pnfRegistration" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-notification" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-perf3gpp" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-fault-supervision" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-provisioning" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-heartbeat" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-performance-assurance" + } + ] + }, + "services": { + "calls": [], + "provides": [ + { + "route": "/eventListener/v1", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v2", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v3", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v4", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v5", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "5.28.4" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v7", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "7.30.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + } + ] + }, + "parameters": [ + { + "name": "streams_publishes", + "value": { + "ves-fault": { + "dmaap_info": { + "topic_url": "http://message-router:3904/events/unauthenticated.SEC_FAULT_OUTPUT" + }, + "type": "message_router" + } + }, + "description": "standard http port collector will open for listening;", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + } + , + { + "name": "collector.service.port", + "value": 8080, + "description": "standard http port collector will open for listening;", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.service.secure.port", + "value": 8443, + "description": "secure http port collector will open for listening ", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": true + }, + { + "name": "collector.keystore.file.location", + "value": "/opt/app/dcae-certificate/cert.jks", + "description": "fs location of keystore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.keystore.passwordfile", + "value": "/opt/app/dcae-certificate/jks.pass", + "description": "location of keystore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.file.location", + "value": "/opt/app/dcae-certificate/trust.jks", + "description": "fs location of truststore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.passwordfile", + "value": "/opt/app/dcae-certificate/trust.pass", + "description": "location of truststore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.dmaap.streamid", + "value": "fault=ves-fault|syslog=ves-syslog|heartbeat=ves-heartbeat|measurementsForVfScaling=ves-measurement|mobileFlow=ves-mobileflow|other=ves-other|stateChange=ves-statechange|thresholdCrossingAlert=ves-thresholdCrossingAlert|voiceQuality=ves-voicequality|sipSignaling=ves-sipsignaling|notification=ves-notification|pnfRegistration=ves-pnfRegistration|3GPP-FaultSupervision=ves-3gpp-fault-supervision|3GPP-Heartbeat=ves-3gpp-heartbeat|3GPP-Provisioning=ves-3gpp-provisioning|3GPP-PerformanceAssurance=ves-3gpp-performance-assurance", + "description": "domain-to-streamid mapping used by VESCollector to distributes events based on domain. Both primary and secondary config_key are included for resilency (multiple streamid can be included commma separated). The streamids MUST match to topic config_keys. For single site without resiliency deployment - configkeys with -secondary suffix can be removed", + "sourced_at_deployment": true, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "auth.method", + "value": "noAuth", + "description": "Property to manage application mode, possible configurations: noAuth - default option - no security (http) , certOnly - auth by certificate (https), basicAuth - auth by basic auth username and password (https),certBasicAuth - auth by certificate and basic auth username / password (https),", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "header.authlist", + "value": "sample1,$2a$10$pgjaxDzSuc6XVFEeqvxQ5u90DKJnM/u7TJTcinAlFJVaavXMWf/Zi|userid1,$2a$10$61gNubgJJl9lh3nvQvY9X.x4e5ETWJJ7ao7ZhJEvmfJigov26Z6uq|userid2,$2a$10$G52y/3uhuhWAMy.bx9Se8uzWinmbJa.dlm1LW6bYPdPkkywLDPLiy", + "description": "List of id and base 64 encoded password.For each onboarding VNF - unique userid and password should be assigned and communicated to VNF owner. Password value should be base64 encoded in config here", + "policy_editable": false, + "sourced_at_deployment": true, + "designer_editable": true + }, + { + "name": "collector.schema.checkflag", + "value": 1, + "description": "Schema check validation flag. When enabled, collector will validate input VES events against VES Schema defined on collector.schema.file ", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.schema.file", + "value": "{\"v1\":\"./etc/CommonEventFormat_27.2.json\",\"v2\":\"./etc/CommonEventFormat_27.2.json\",\"v3\":\"./etc/CommonEventFormat_27.2.json\",\"v4\":\"./etc/CommonEventFormat_27.2.json\",\"v5\":\"./etc/CommonEventFormat_28.4.1.json\",\"v7\":\"./etc/CommonEventFormat_30.2.1_ONAP.json\"}", + "description": "VES schema file name per version used for validation", + "designer_editable": true, + "sourced_at_deployment": false, + "policy_editable": false + }, + { + "name": "event.transform.flag", + "value": 1, + "description": "flag to enable tranformation rules defined under eventTransform.json; this is applicable when event tranformation rules preset should be activated for transforming ’) the value declared.", + "type": "number" + }, + "greater_or_equal": { + "description": "Constrains a property or parameter to a value greater than or equal to (‘>=’) the value declared.", + "type": "number" + }, + "less_than": { + "description": "Constrains a property or parameter to a value less than (‘<’) the value declared.", + "type": "number" + }, + "less_or_equal": { + "description": "Constrains a property or parameter to a value less than or equal to (‘<=’) the value declared.", + "type": "number" + }, + "valid_values": { + "description": "Constrains a property or parameter to a value that is in the list of declared values.", + "type": "array" + }, + "length": { + "description": "Constrains the property or parameter to a value of a given length.", + "type": "number" + }, + "min_length": { + "description": "Constrains the property or parameter to a value to a minimum length.", + "type": "number" + }, + "max_length": { + "description": "Constrains the property or parameter to a value to a maximum length.", + "type": "number" + } + } + }, + "stream_message_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "message router", + "message_router" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "stream_kafka": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "kafka" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_http": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "http", + "https" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_message_router": { + "$ref": "#/definitions/stream_message_router" + }, + "publisher_data_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "config_key": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "data router", + "data_router" + ] + } + }, + "required": [ + "format", + "version", + "config_key", + "type" + ] + }, + "publisher_kafka": { + "$ref": "#/definitions/stream_kafka" + }, + "subscriber_http": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "route": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "http", + "https" + ] + } + }, + "required": [ + "format", + "version", + "route", + "type" + ] + }, + "subscriber_message_router": { + "$ref": "#/definitions/stream_message_router" + }, + "subscriber_data_router": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + }, + "route": { + "type": "string" + }, + "type": { + "description": "Type of stream to be used", + "type": "string", + "enum": [ + "data router", + "data_router" + ] + }, + "config_key": { + "description": "Data router subscribers require config info to setup their endpoints to handle requests. For example, needs username and password", + "type": "string" + } + }, + "required": [ + "format", + "version", + "route", + "type", + "config_key" + ] + }, + "subscriber_kafka": { + "$ref": "#/definitions/stream_kafka" + }, + "provider": { + "oneOf": [ + { + "$ref": "#/definitions/docker-provider" + }, + { + "$ref": "#/definitions/cdap-provider" + } + ] + }, + "cdap-provider": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "service_name": { + "type": "string" + }, + "service_endpoint": { + "type": "string" + }, + "verb": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "DELETE" + ] + } + }, + "required": [ + "request", + "response", + "service_name", + "service_endpoint", + "verb" + ] + }, + "docker-provider": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "route": { + "type": "string" + }, + "verb": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "DELETE" + ] + } + }, + "required": [ + "request", + "response", + "route" + ] + }, + "caller": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/formatPair" + }, + "response": { + "$ref": "#/definitions/formatPair" + }, + "config_key": { + "type": "string" + } + }, + "required": [ + "request", + "response", + "config_key" + ] + }, + "formatPair": { + "type": "object", + "properties": { + "format": { + "$ref": "#/definitions/name" + }, + "version": { + "$ref": "#/definitions/version" + } + } + }, + "name": { + "type": "string" + }, + "version": { + "type": "string", + "pattern": "^(\\d+\\.)(\\d+\\.)(\\*|\\d+)$" + }, + "artifact": { + "type": "object", + "description": "Component artifact object", + "properties": { + "uri": { + "type": "string", + "description": "Uri to artifact" + }, + "type": { + "type": "string", + "enum": [ + "jar", + "docker image" + ] + } + }, + "required": [ + "uri", + "type" + ] + }, + "auxilary_cdap": { + "title": "cdap component specification schema", + "type": "object", + "properties": { + "streamname": { + "type": "string" + }, + "artifact_name": { + "type": "string" + }, + "artifact_version": { + "type": "string", + "pattern": "^(\\d+\\.)(\\d+\\.)(\\*|\\d+)$" + }, + "namespace": { + "type": "string", + "description": "optional" + }, + "programs": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/definitions/cdap_program" + } + } + }, + "required": [ + "streamname", + "programs", + "artifact_name", + "artifact_version" + ] + }, + "cdap_program_type": { + "type": "string", + "enum": [ + "flows", + "mapreduce", + "schedules", + "spark", + "workflows", + "workers", + "services" + ] + }, + "cdap_program": { + "type": "object", + "properties": { + "program_type": { + "$ref": "#/definitions/cdap_program_type" + }, + "program_id": { + "type": "string" + } + }, + "required": [ + "program_type", + "program_id" + ] + }, + "auxilary_docker": { + "title": "Docker component specification schema", + "type": "object", + "properties": { + "helm": { + "type": "object", + "properties": { + "applicationEnv": { + "type": "object" + }, + "service": { + "description": "Mapping for kubernetes services", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "NodePort", + "Cluster" + ] + }, + "name": { + "type": "string" + }, + "ports": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": [ + "type", + "name", + "ports" + ] + } + }, + "required": [ + "service" + ] + }, + "healthcheck": { + "description": "Define the health check that Consul should perfom for this component", + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/docker_healthcheck_http" + }, + { + "$ref": "#/definitions/docker_healthcheck_script" + } + ] + }, + "ports": { + "description": "Port mapping to be used for Docker containers. Each entry is of the format :.", + "type": "array", + "items": { + "type": "string" + } + }, + "log_info": { + "description": "Component specific details for logging", + "type": "object", + "properties": { + "log_directory": { + "description": "The path in the container where the component writes its logs. If the component is following the EELF requirements, this would be the directory where the four EELF files are being written. (Other logs can be placed in the directory--if their names in '.log', they'll also be sent into ELK.)", + "type": "string" + }, + "alternate_fb_path": { + "description": "By default, the log volume is mounted at /var/log/onap/ in the sidecar container's file system. 'alternate_fb_path' allows overriding the default. Will affect how the log data can be found in the ELK system.", + "type": "string" + } + }, + "additionalProperties": false + }, + "tls_info": { + "description": "Component information to use tls certificates", + "type": "object", + "properties": { + "cert_directory": { + "description": "The path in the container where the component certificates will be placed by the init container", + "type": "string" + }, + "use_tls": { + "description": "Boolean flag to determine if the application is using tls certificates", + "type": "boolean" + }, + "use_external_tls": { + "description": "Boolean flag to determine if the application is using tls certificates for external communication", + "type": "boolean" + } + }, + "required": [ + "cert_directory", + "use_tls" + ], + "additionalProperties": false + }, + "databases": { + "description": "The databases the application is connecting to using the pgaas", + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "postgres" + ] + } + }, + "policy": { + "properties": { + "trigger_type": { + "description": "Only value of docker is supported at this time.", + "type": "string", + "enum": [ + "docker" + ] + }, + "script_path": { + "description": "Script command that will be executed for policy reconfiguration", + "type": "string" + } + }, + "required": [ + "trigger_type", + "script_path" + ], + "additionalProperties": false + }, + "volumes": { + "description": "Volume mapping to be used for Docker containers. Each entry is of the format below", + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/host_path_volume" + }, + { + "$ref": "#/definitions/config_map_volume" + } + ] + } + } + }, + "required": [ + "healthcheck" + ], + "additionalProperties": false + }, + "host_path_volume": { + "type": "object", + "properties": { + "host": { + "type": "object", + "path": { + "type": "string" + } + }, + "container": { + "type": "object", + "bind": { + "type": "string" + }, + "mode": { + "type": "string" + } + } + }, + "required": [ + "host", + "container" + ] + }, + "config_map_volume": { + "type": "object", + "properties": { + "config_volume": { + "type": "object", + "name": { + "type": "string" + } + }, + "container": { + "type": "object", + "bind": { + "type": "string" + }, + "mode": { + "type": "string" + } + } + }, + "required": [ + "config_volume", + "container" + ] + }, + "docker_healthcheck_http": { + "properties": { + "type": { + "description": "Consul health check type", + "type": "string", + "enum": [ + "http", + "https", + "HTTP", + "HTTPS" + ] + }, + "interval": { + "description": "Interval duration in seconds i.e. 10s", + "default": "15s", + "type": "string" + }, + "timeout": { + "description": "Timeout in seconds i.e. 10s", + "default": "1s", + "type": "string" + }, + "endpoint": { + "description": "Relative endpoint used by Consul to check health by making periodic HTTP GET calls", + "type": "string" + }, + "port": { + "description": "Port mapping for readiness section", + "type": "integer" + }, + "initialDelaySeconds": { + "description": "Initial delay in seconds for readiness section", + "type": "integer" + } + }, + "required": [ + "type", + "endpoint" + ] + }, + "docker_healthcheck_script": { + "properties": { + "type": { + "description": "Consul health check type", + "type": "string", + "enum": [ + "script", + "docker" + ] + }, + "interval": { + "description": "Interval duration in seconds i.e. 10s", + "default": "15s", + "type": "string" + }, + "timeout": { + "description": "Timeout in seconds i.e. 10s", + "default": "1s", + "type": "string" + }, + "script": { + "description": "Script command that will be executed by Consul to check health", + "type": "string" + } + }, + "required": [ + "type", + "script" + ] + } + } +} \ No newline at end of file diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/ves.json b/mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/ves.json new file mode 100644 index 0000000..3829a1a --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/input/specs/ves.json @@ -0,0 +1,455 @@ +{ + "self": { + "version": "1.8.0", + "name": "dcae-ves-collector", + "description": "Collector for receiving VES events through restful interface", + "component_type": "docker" + }, + "streams": { + "subscribes": [], + "publishes": [ + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-fault" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-measurement" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-syslog" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-heartbeat" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-other" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-mobileflow" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-statechange" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-thresholdCrossingAlert" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-voicequality" + }, + { + "format": "VES_specification", + "version": "5.28.4", + "type": "message router", + "config_key": "ves-sipsignaling" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-pnfRegistration" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-notification" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-perf3gpp" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-fault-supervision" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-provisioning" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-heartbeat" + }, + { + "format": "VES_specification", + "version": "7.30.2", + "type": "message router", + "config_key": "ves-3gpp-performance-assurance" + } + ] + }, + "services": { + "calls": [], + "provides": [ + { + "route": "/eventListener/v1", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v2", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v3", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v4", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "4.27.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v5", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "5.28.4" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + }, + { + "route": "/eventListener/v7", + "verb": "POST", + "request": { + "format": "VES_specification", + "version": "7.30.2" + }, + "response": { + "format": "ves.coll.response", + "version": "1.0.0" + } + } + ] + }, + "parameters": [ + { + "name": "streams_publishes", + "value": { + "ves-fault": { + "dmaap_info": { + "topic_url": "http://message-router:3904/events/unauthenticated.SEC_FAULT_OUTPUT" + }, + "type": "message_router" + } + }, + "description": "standard http port collector will open for listening;", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + } + , + { + "name": "collector.service.port", + "value": 8080, + "description": "standard http port collector will open for listening;", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.service.secure.port", + "value": 8443, + "description": "secure http port collector will open for listening ", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": true + }, + { + "name": "collector.keystore.file.location", + "value": "/opt/app/dcae-certificate/cert.jks", + "description": "fs location of keystore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.keystore.passwordfile", + "value": "/opt/app/dcae-certificate/jks.pass", + "description": "location of keystore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.file.location", + "value": "/opt/app/dcae-certificate/trust.jks", + "description": "fs location of truststore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.passwordfile", + "value": "/opt/app/dcae-certificate/trust.pass", + "description": "location of truststore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.dmaap.streamid", + "value": "fault=ves-fault|syslog=ves-syslog|heartbeat=ves-heartbeat|measurementsForVfScaling=ves-measurement|mobileFlow=ves-mobileflow|other=ves-other|stateChange=ves-statechange|thresholdCrossingAlert=ves-thresholdCrossingAlert|voiceQuality=ves-voicequality|sipSignaling=ves-sipsignaling|notification=ves-notification|pnfRegistration=ves-pnfRegistration|3GPP-FaultSupervision=ves-3gpp-fault-supervision|3GPP-Heartbeat=ves-3gpp-heartbeat|3GPP-Provisioning=ves-3gpp-provisioning|3GPP-PerformanceAssurance=ves-3gpp-performance-assurance", + "description": "domain-to-streamid mapping used by VESCollector to distributes events based on domain. Both primary and secondary config_key are included for resilency (multiple streamid can be included commma separated). The streamids MUST match to topic config_keys. For single site without resiliency deployment - configkeys with -secondary suffix can be removed", + "sourced_at_deployment": true, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "auth.method", + "value": "noAuth", + "description": "Property to manage application mode, possible configurations: noAuth - default option - no security (http) , certOnly - auth by certificate (https), basicAuth - auth by basic auth username and password (https),certBasicAuth - auth by certificate and basic auth username / password (https),", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "header.authlist", + "value": "sample1,$2a$10$pgjaxDzSuc6XVFEeqvxQ5u90DKJnM/u7TJTcinAlFJVaavXMWf/Zi|userid1,$2a$10$61gNubgJJl9lh3nvQvY9X.x4e5ETWJJ7ao7ZhJEvmfJigov26Z6uq|userid2,$2a$10$G52y/3uhuhWAMy.bx9Se8uzWinmbJa.dlm1LW6bYPdPkkywLDPLiy", + "description": "List of id and base 64 encoded password.For each onboarding VNF - unique userid and password should be assigned and communicated to VNF owner. Password value should be base64 encoded in config here", + "policy_editable": false, + "sourced_at_deployment": true, + "designer_editable": true + }, + { + "name": "collector.schema.checkflag", + "value": 1, + "description": "Schema check validation flag. When enabled, collector will validate input VES events against VES Schema defined on collector.schema.file ", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.schema.file", + "value": "{\"v1\":\"./etc/CommonEventFormat_27.2.json\",\"v2\":\"./etc/CommonEventFormat_27.2.json\",\"v3\":\"./etc/CommonEventFormat_27.2.json\",\"v4\":\"./etc/CommonEventFormat_27.2.json\",\"v5\":\"./etc/CommonEventFormat_28.4.1.json\",\"v7\":\"./etc/CommonEventFormat_30.2.1_ONAP.json\"}", + "description": "VES schema file name per version used for validation", + "designer_editable": true, + "sourced_at_deployment": false, + "policy_editable": false + }, + { + "name": "event.transform.flag", + "value": 1, + "description": "flag to enable tranformation rules defined under eventTransform.json; this is applicable when event tranformation rules preset should be activated for transforming ChartTemplateStructureValidator.validateChartTemplateStructure(validStructureLocation)); + } + + @Test + void invalidateTemplateStructureShouldThrowRuntimeError() { + String invalidStructureLocation = "test"; + assertThrows(RuntimeException.class, () -> ChartTemplateStructureValidator.validateChartTemplateStructure(invalidStructureLocation)); + } +} diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ComponentSpecParserTest.java b/mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ComponentSpecParserTest.java new file mode 100644 index 0000000..c14edda --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ComponentSpecParserTest.java @@ -0,0 +1,186 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.onap.dcaegen2.platform.helmchartgenerator.chartbuilder.ComponentSpecParser; +import org.onap.dcaegen2.platform.helmchartgenerator.models.chartinfo.ChartInfo; +import org.onap.dcaegen2.platform.helmchartgenerator.validation.ComponentSpecValidator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith({MockitoExtension.class}) +class ComponentSpecParserTest { + + ComponentSpecParser parser; + + @Mock + ComponentSpecValidator validator; + + @BeforeEach + void setUp() { + parser = new ComponentSpecParser(validator); + } + + @Test + void extractChartInfo() throws Exception{ + String specLocation = "src/test/input/specs/ves.json"; + String chartTemplateLocation = "src/test/input/blueprint"; + String specSchemaLocation = ""; + ChartInfo chartInfo = parser.extractChartInfo(specLocation, chartTemplateLocation, specSchemaLocation); + + assertMetadata(chartInfo); + assertOuterKeyValues(chartInfo); + assertApplicationConfigSection(chartInfo); + assertReadinessCheck(chartInfo); + assertApplicationEnv(chartInfo); + assertService(chartInfo); + assertPolicyInfo(chartInfo); + assertCertificates(chartInfo); + assertConfigMap(chartInfo); + assertPostgres(chartInfo); + assertSecrets(chartInfo); + } + + private void assertApplicationConfigSection(ChartInfo chartInfo) { + Map applicationConfig = (Map) chartInfo.getValues().get("applicationConfig"); + assertThat(applicationConfig.size()).isEqualTo(20); + } + + private void assertOuterKeyValues(ChartInfo chartInfo) { + Map outerKv = chartInfo.getValues(); + assertThat(outerKv.get("image")).isEqualTo("nexus3.onap.org:10001/onap/org.onap.dcaegen2.collectors.ves.vescollector:latest"); + assertThat(outerKv.get("logDirectory")).isEqualTo("/opt/app/VESCollector/logs/"); + assertThat(outerKv.get("certDirectory")).isEqualTo("/opt/app/dcae-certificate/"); + assertThat(outerKv.get("tlsServer")).isEqualTo(true); + } + + private void assertMetadata(ChartInfo chartInfo) { + assertThat(chartInfo.getMetadata().getName()).isEqualTo("dcae-ves-collector"); + assertThat(chartInfo.getMetadata().getVersion()).isEqualTo("1.8.0"); + assertThat(chartInfo.getMetadata().getDescription()). + isEqualTo("Collector for receiving VES events through restful interface"); + } + + private void assertReadinessCheck(ChartInfo chartInfo) { + Map readiness = (Map) chartInfo.getValues().get("readiness"); + assertThat(readiness.get("scheme")).isEqualTo("http"); + assertThat(readiness.get("path")).isEqualTo("/healthcheck"); + assertThat(readiness.get("periodSeconds")).isEqualTo(15); + assertThat(readiness.get("port")).isEqualTo(8080); + assertThat(readiness.get("initialDelaySeconds")).isEqualTo(5); + assertThat(readiness.get("timeoutSeconds")).isEqualTo(1); + } + + private void assertApplicationEnv(ChartInfo chartInfo) { + ObjectMapper oMapper = new ObjectMapper(); + Map applicationEnv = (Map) chartInfo.getValues().get("applicationEnv"); + Map PMSH_PG_USERNAME = oMapper.convertValue(applicationEnv.get("PMSH_PG_USERNAME"), Map.class); + Map PMSH_PG_PASSWORD = oMapper.convertValue(applicationEnv.get("PMSH_PG_PASSWORD"), Map.class); + + assertThat(applicationEnv.get("PMSH_PG_URL")).isEqualTo("dcae-pmsh-pg-primary"); + assertThat(PMSH_PG_USERNAME.get("secretUid")).isEqualTo("pgUserCredsSecretUid"); + assertThat(PMSH_PG_USERNAME.get("key")).isEqualTo("login"); + assertThat(PMSH_PG_PASSWORD.get("secretUid")).isEqualTo("pgUserCredsSecretUid"); + assertThat(PMSH_PG_PASSWORD.get("key")).isEqualTo("password"); + } + + private void assertService(ChartInfo chartInfo) { + Map service = (Map) chartInfo.getValues().get("service"); + List ports = new ArrayList(); + for(Object portsGroup : (ArrayList) service.get("ports")){ + ports.add((Map) portsGroup); + } + assertThat(service.get("type")).isEqualTo("NodePort"); + assertThat(service.get("name")).isEqualTo("dcae-ves-collector"); + assertThat(service.get("has_internal_only_ports")).isEqualTo(true); + assertThat(ports.get(0).get("name")).isEqualTo("http"); + assertThat(ports.get(0).get("port")).isEqualTo(8443); + assertThat(ports.get(0).get("plain_port")).isEqualTo(8080); + assertThat(ports.get(0).get("port_protocol")).isEqualTo("http"); + assertThat(ports.get(0).get("nodePort")).isEqualTo(17); + assertThat(ports.get(0).get("useNodePortExt")).isEqualTo(true); + assertThat(ports.get(1).get("name")).isEqualTo("metrics"); + assertThat(ports.get(1).get("port")).isEqualTo(4444); + assertThat(ports.get(1).get("internal_only")).isEqualTo(true); + } + + private void assertPolicyInfo(ChartInfo chartInfo) { + Map policyInfo = (Map) chartInfo.getValues().get("policies"); + assertThat(policyInfo.get("policyID")).isEqualTo("'[\"tca_policy_id_10\", \"tca_policy_id_11\"]'\n"); + } + + private void assertCertificates(ChartInfo chartInfo) { + List certificates = (List) chartInfo.getValues().get("certificates"); + Map certificate = (Map) certificates.get(0); + assertThat(certificate.get("mountPath")).isEqualTo("/opt/app/dcae-certificate/external"); + assertThat(certificate.get("commonName")).isEqualTo("dcae-ves-collector"); + assertThat(((List) certificate.get("dnsNames")).get(0)).isEqualTo("dcae-ves-collector"); + assertThat(((List) ((Map) certificate.get("keystore")).get("outputType")).get(0)).isEqualTo("jks"); + assertThat((((Map) certificate.get("keystore")).get("passwordSecretRef")).get("name")).isEqualTo("ves-collector-cmpv2-keystore-password"); + assertThat((((Map) certificate.get("keystore")).get("passwordSecretRef")).get("key")).isEqualTo("password"); + assertThat((((Map) certificate.get("keystore")).get("passwordSecretRef")).get("create")).isEqualTo(true); + } + + private void assertConfigMap(ChartInfo chartInfo) { + List externalVolumes = (List) chartInfo.getValues().get("externalVolumes"); + Map volume_one = (Map) externalVolumes.get(0); + Map volume_two = (Map) externalVolumes.get(1); + assertThat(volume_one.get("name")).isEqualTo("dcae-external-repo-configmap-schema-map"); + assertThat(volume_one.get("type")).isEqualTo("configMap"); + assertThat(volume_one.get("mountPath")).isEqualTo("/opt/app/VESCollector/etc/externalRepo/"); + assertThat(volume_one.get("optional")).isEqualTo(true); + } + + private void assertPostgres(ChartInfo chartInfo) { + Map postgres = (Map) chartInfo.getValues().get("postgres"); + assertThat(postgres.get("nameOverride")).isEqualTo("dcae-ves-collector-postgres"); + assertThat(((Map) postgres.get("service")).get("name")).isEqualTo("dcae-ves-collector-postgres"); + assertThat(((Map) postgres.get("service")).get("name2")).isEqualTo("dcae-ves-collector-pg-primary"); + assertThat(((Map) postgres.get("service")).get("name3")).isEqualTo("dcae-ves-collector-pg-replica"); + assertThat(((Map) ((Map) postgres.get("container")).get("name")).get("primary")).isEqualTo("dcae-ves-collector-pg-primary"); + assertThat(((Map) ((Map) postgres.get("container")).get("name")).get("replica")).isEqualTo("dcae-ves-collector-pg-replica"); + assertThat(((Map) postgres.get("persistence")).get("mountSubPath")).isEqualTo("dcae-ves-collector/data"); + assertThat(((Map) postgres.get("persistence")).get("mountInitPath")).isEqualTo("dcae-ves-collector"); + assertThat(((Map) postgres.get("config")).get("pgUserName")).isEqualTo("ves-collector"); + assertThat(((Map) postgres.get("config")).get("pgDatabase")).isEqualTo("ves-collector"); + assertThat(((Map) postgres.get("config")).get("pgUserExternalSecret")).isEqualTo("{{ include \"common.release\" . }}-ves-collector-pg-user-creds"); + } + + private void assertSecrets(ChartInfo chartInfo) { + List secrets = (List) chartInfo.getValues().get("secrets"); + Map secret1 = (Map) secrets.get(0); + assertThat(secret1.get("uid")).isEqualTo("pg-user-creds"); + assertThat(secret1.get("name")).isEqualTo("{{ include \"common.release\" . }}-ves-collector-pg-user-creds"); + assertThat(secret1.get("type")).isEqualTo("basicAuth"); + assertThat(secret1.get("externalSecret")).isEqualTo("{{ ternary \"\" (tpl (default \"\" .Values.postgres.config.pgUserExternalSecret) .) (hasSuffix \"ves-collector-pg-user-creds\" .Values.postgres.config.pgUserExternalSecret) }}"); + assertThat(secret1.get("login")).isEqualTo("{{ .Values.postgres.config.pgUserName }}"); + assertThat(secret1.get("password")).isEqualTo("{{ .Values.postgres.config.pgUserPassword }}"); + assertThat(secret1.get("passwordPolicy")).isEqualTo("generate"); + } +} \ No newline at end of file diff --git a/mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ComponentSpecValidatorTest.java b/mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ComponentSpecValidatorTest.java new file mode 100644 index 0000000..c987c11 --- /dev/null +++ b/mod2/helm-generator/helmchartgenerator-core/src/test/java/org/onap/dcaegen2/platform/helmchartgenerator/ComponentSpecValidatorTest.java @@ -0,0 +1,68 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2021 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +package org.onap.dcaegen2.platform.helmchartgenerator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.onap.dcaegen2.platform.helmchartgenerator.validation.ComponentSpecValidator; +import org.onap.dcaegen2.platform.helmchartgenerator.validation.ComponentSpecValidatorImpl; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +public class ComponentSpecValidatorTest { + + private static final String SPEC_SCHEMA = "src/test/input/specs/schemas/component-spec-schema.json"; + + ComponentSpecValidator validator; + + @BeforeEach + void setUp() { + validator = new ComponentSpecValidatorImpl(); + } + + @Test + void validSpecShouldNotThrowAnyException() { + String specSchema = "src/test/input/specs/schemas/component-spec-schema.json"; + String specFileLocation = "src/test/input/specs/ves.json"; + assertDoesNotThrow(() -> validator.validateSpecFile(specFileLocation, specSchema)); + } + + @Test + void invalidSpecShouldThrowRuntimeException() { + String specFileLocation = "src/test/input/specs/invalidSpecSchema.json"; + assertThrows(RuntimeException.class, () -> validator.validateSpecFile(specFileLocation, SPEC_SCHEMA)); + } + + @Test + void ifNoHelmSectionFoundThrowRuntimeError() { + String specFileLocation = "src/test/input/specs/invalidSpecNoHelm.json"; + assertThrows(RuntimeException.class, () -> validator.validateSpecFile(specFileLocation, SPEC_SCHEMA)); + } + + @Test + void ifNoServiceSectionFoundThrowRuntimeError() { + String specFileLocation = "src/test/input/specs/invalidSpecNoServices.json"; + assertThrows(RuntimeException.class, () -> validator.validateSpecFile(specFileLocation, SPEC_SCHEMA)); + } + +} diff --git a/mod2/helm-generator/lombok.config b/mod2/helm-generator/lombok.config new file mode 100644 index 0000000..8f7e8aa --- /dev/null +++ b/mod2/helm-generator/lombok.config @@ -0,0 +1 @@ +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/mod2/helm-generator/pom.xml b/mod2/helm-generator/pom.xml new file mode 100644 index 0000000..592611c --- /dev/null +++ b/mod2/helm-generator/pom.xml @@ -0,0 +1,129 @@ + + + + + 4.0.0 + pom + + helmchartgenerator-core + helmchartgenerator-cli + + + org.onap.oparent + oparent + 2.0.0 + + + org.onap.dcaegen2.platform + helmchartgenerator + 1.0.0-SNAPSHOT + helm-chart-generator + Helm chart generator + + 11 + ${java.version} + ${java.version} + 2.4.0 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + commons-io + commons-io + 2.4 + + + org.projectlombok + lombok + 1.18.20 + + + com.fasterxml.jackson.core + jackson-databind + 2.10.3 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.9.8 + + + org.everit.json + org.everit.json.schema + 1.3.0 + + + com.vaadin.external.google + android-json + 0.0.20131108.vaadin1 + compile + + + com.squareup.okhttp3 + okhttp + 4.0.1 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + + + + + org.codehaus.mojo + cobertura-maven-plugin + 2.7 + + + + + -- 2.16.6