rApp Prototype
Change-Id: I70e3e7235abb810784e41ebd70977d9133db2b16
Signed-off-by: Lathish <lathishbabu.ganesan@est.tech>
diff --git a/rapp-manager/.checkstyle b/rapp-manager/.checkstyle
new file mode 100644
index 0000000..0db75ec
--- /dev/null
+++ b/rapp-manager/.checkstyle
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">
+ <local-check-config name="maven-checkstyle-plugin check-license" location="jar:file:/Users/lathish/.m2/repository/org/onap/oparent/checkstyle/1.1.1/checkstyle-1.1.1.jar!/onap-checkstyle/check-license.xml" type="remote" description="maven-checkstyle-plugin configuration check-license">
+ <property name="checkstyle.header.file" value="/Users/lathish/Documents/workspace/ccsdk-oran/.metadata/.plugins/org.eclipse.core.resources/.projects/rapp-manager/com.basistech.m2e.code.quality.checkstyleConfigurator/checkstyle-header-check-license.txt"/>
+ <property name="checkstyle.cache.file" value="${project_loc}/target/checkstyle-cachefile"/>
+ </local-check-config>
+ <local-check-config name="maven-checkstyle-plugin check-style" location="jar:file:/Users/lathish/.m2/repository/org/onap/oparent/checkstyle/1.1.1/checkstyle-1.1.1.jar!/onap-checkstyle/onap-java-style.xml" type="remote" description="maven-checkstyle-plugin configuration check-style">
+ <property name="checkstyle.header.file" value="/Users/lathish/Documents/workspace/ccsdk-oran/.metadata/.plugins/org.eclipse.core.resources/.projects/rapp-manager/com.basistech.m2e.code.quality.checkstyleConfigurator/checkstyle-header-check-style.txt"/>
+ <property name="checkstyle.cache.file" value="${project_loc}/target/checkstyle-cachefile"/>
+ </local-check-config>
+ <fileset name="java-sources-check-license" enabled="true" check-config-name="maven-checkstyle-plugin check-license" local="true">
+ <file-match-pattern match-pattern="^src/test/java/.*\/.*\.java" include-pattern="true"/>
+ <file-match-pattern match-pattern="^src/main/java/.*\/.*\.java" include-pattern="true"/>
+ </fileset>
+ <fileset name="java-sources-check-style" enabled="true" check-config-name="maven-checkstyle-plugin check-style" local="true">
+ <file-match-pattern match-pattern="^src/test/java/.*\/.*\.java" include-pattern="true"/>
+ <file-match-pattern match-pattern="^src/main/java/.*\/.*\.java" include-pattern="true"/>
+ <file-match-pattern match-pattern="^src/main/java/src/main/java.*\/.*\.java" include-pattern="true"/>
+ <file-match-pattern match-pattern="^src/main/resources.*\.properties" include-pattern="true"/>
+ <file-match-pattern match-pattern="^src/main/resources.*\.properties" include-pattern="true"/>
+ <file-match-pattern match-pattern="^src/test/resources.*\.properties" include-pattern="true"/>
+ </fileset>
+</fileset-config>
diff --git a/rapp-manager/.classpath b/rapp-manager/.classpath
new file mode 100644
index 0000000..1f94f5f
--- /dev/null
+++ b/rapp-manager/.classpath
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" output="target/classes" path="src/main/java">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="src" output="target/test-classes" path="src/test/java">
+ <attributes>
+ <attribute name="test" value="true"/>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+ <attributes>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="src" output="target/classes" path="target/generated-sources/annotations">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ <attribute name="maven.pomderived" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/rapp-manager/.gitignore b/rapp-manager/.gitignore
new file mode 100644
index 0000000..df309a1
--- /dev/null
+++ b/rapp-manager/.gitignore
@@ -0,0 +1,20 @@
+# Documentation
+.idea/
+.tox
+docs/_build/
+.DS_STORE
+.swagger*
+docs/offeredapis/swagger/README.md
+
+# Eclipse
+.checkstyle
+.classpath
+target/
+.sts4-cache
+.project
+.settings
+.pydevproject
+infer-out/
+
+.vscode
+.factorypath
diff --git a/rapp-manager/.project b/rapp-manager/.project
new file mode 100644
index 0000000..1f1a6f8
--- /dev/null
+++ b/rapp-manager/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>rapp-manager</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ </natures>
+</projectDescription>
diff --git a/rapp-manager/.settings/org.eclipse.core.resources.prefs b/rapp-manager/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..c6a8b3d
--- /dev/null
+++ b/rapp-manager/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/test/java=UTF-8
+encoding//target/generated-sources/annotations=UTF-8
+encoding/<project>=UTF-8
diff --git a/rapp-manager/.settings/org.eclipse.jdt.core.prefs b/rapp-manager/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..55e549b
--- /dev/null
+++ b/rapp-manager/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
+org.eclipse.jdt.core.compiler.compliance=11
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=11
diff --git a/rapp-manager/.settings/org.eclipse.m2e.core.prefs b/rapp-manager/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/rapp-manager/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/rapp-manager/Dockerfile b/rapp-manager/Dockerfile
new file mode 100644
index 0000000..786b12c
--- /dev/null
+++ b/rapp-manager/Dockerfile
@@ -0,0 +1,35 @@
+#
+# ============LICENSE_START=======================================================
+# Copyright (C) 2021 Nordix Foundation.
+# ================================================================================
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=========================================================
+#
+FROM openjdk:11-jre-slim
+
+ARG JAR
+
+WORKDIR /opt/app/rapp-manager
+RUN mkdir -p /var/log/rapp-manager
+
+RUN apt-get update && apt install -y wget
+RUN wget https://get.helm.sh/helm-v3.5.2-linux-amd64.tar.gz && tar xvf helm-v3.5.2-linux-amd64.tar.gz && mv linux-amd64/helm /usr/local/bin
+RUN rm -rf linux-amd64 && rm helm-v3.5.2-linux-amd64.tar.gz
+
+EXPOSE 8080
+ADD /config/application.yaml /opt/app/rapp-manager/config/application.yaml
+ADD target/${JAR} /opt/app/rapp-manager/rapp-manager.jar
+
+CMD ["java", "-jar", "/opt/app/rapp-manager/rapp-manager.jar"]
\ No newline at end of file
diff --git a/rapp-manager/config/application.yaml b/rapp-manager/config/application.yaml
new file mode 100644
index 0000000..2100be1
--- /dev/null
+++ b/rapp-manager/config/application.yaml
@@ -0,0 +1,31 @@
+ # ========================LICENSE_START=================================
+ # Copyright (C) 2021 Nordix Foundation. 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===================================
+
+management:
+ endpoints:
+ web:
+ exposure:
+ include: "loggers,logfile,health,info,metrics,threaddump,heapdump"
+server:
+ port : 8080
+logging:
+ level:
+ ROOT: INFO
+app:
+ basepath: https://kubernetes.docker.internal:6443
+ certpath: /opt/app/rapp-manager/cert/ca.crt
+ tokenpath: /opt/app/rapp-manager/cert/token.txt
+ chartregistry: http://localhost:30081
diff --git a/rapp-manager/pom.xml b/rapp-manager/pom.xml
new file mode 100644
index 0000000..1758a17
--- /dev/null
+++ b/rapp-manager/pom.xml
@@ -0,0 +1,437 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+* ========================LICENSE_START=================================
+* O-RAN-SC
+* %%
+* Copyright (C) 2021 Nordix Foundation
+* %%
+* 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===================================
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>2.3.8.RELEASE</version>
+ <relativePath/>
+ </parent>
+ <groupId>org.o-ran-sc.nonrtric</groupId>
+ <artifactId>rapp-manager</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <properties>
+ <java.version>11</java.version>
+ <springfox.version>3.0.0</springfox.version>
+ <immutable.version>2.8.8</immutable.version>
+ <json.version>20200518</json.version>
+ <formatter-maven-plugin.version>2.13.0</formatter-maven-plugin.version>
+ <spotless-maven-plugin.version>2.5.0</spotless-maven-plugin.version>
+ <commons-io.version>2.5</commons-io.version>
+ <docker-maven-plugin>0.30.0</docker-maven-plugin>
+ <surefire-maven-plugin.version>3.0.0-M5</surefire-maven-plugin.version>
+ <jacoco-maven-plugin.version>0.8.6</jacoco-maven-plugin.version>
+ <swagger-codegen-maven-plugin.version>3.0.11</swagger-codegen-maven-plugin.version>
+ <exec.skip>true</exec.skip>
+ <sdk.version>1.1.6</sdk.version>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-webflux</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-reactor-netty</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-autoconfigure</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webflux</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-spring-web</artifactId>
+ <version>${springfox.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-spi</artifactId>
+ <version>${springfox.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-core</artifactId>
+ <version>${springfox.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.aspectj</groupId>
+ <artifactId>aspectjweaver</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat.embed</groupId>
+ <artifactId>tomcat-embed-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.validation</groupId>
+ <artifactId>validation-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>30.0-jre</version>
+ </dependency>
+ <dependency>
+ <groupId>org.immutables</groupId>
+ <artifactId>value</artifactId>
+ <version>${immutable.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.immutables</groupId>
+ <artifactId>gson</artifactId>
+ <version>${immutable.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>${json.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Actuator dependencies -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <!--REQUIRED TO GENERATE DOCUMENTATION -->
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger2</artifactId>
+ <version>${springfox.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.springfox</groupId>
+ <artifactId>springfox-swagger-ui</artifactId>
+ <version>${springfox.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <!-- For development help -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-devtools</artifactId>
+ <optional>true</optional>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ <optional>true</optional>
+ <scope>runtime</scope>
+ </dependency>
+ <!-- TEST -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.projectreactor</groupId>
+ <artifactId>reactor-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>mockwebserver</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons-io.version}</version>
+ <!-- <scope>test</scope> -->
+ </dependency>
+ <dependency>
+ <groupId>io.kubernetes</groupId>
+ <artifactId>client-java</artifactId>
+ <version>10.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-fileupload</groupId>
+ <artifactId>commons-fileupload</artifactId>
+ <version>1.4</version>
+ </dependency>
+
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${project.build.directory}/generated-sources/annotations/</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>net.revelc.code.formatter</groupId>
+ <artifactId>formatter-maven-plugin</artifactId>
+ <version>${formatter-maven-plugin.version}</version>
+ <configuration>
+ <lineEnding>LF</lineEnding>
+ <configFile>${project.basedir}/eclipse-formatter.xml</configFile>
+ </configuration>
+ <!-- https://code.revelc.net/formatter-maven-plugin/ use mvn formatter:format
+ spotless:apply process-sources -->
+ </plugin>
+ <plugin>
+ <groupId>com.diffplug.spotless</groupId>
+ <artifactId>spotless-maven-plugin</artifactId>
+ <version>${spotless-maven-plugin.version}</version>
+ <configuration>
+ <lineEndings>UNIX</lineEndings>
+ <java>
+ <removeUnusedImports/>
+ <importOrder>
+ <order>com,java,javax,org</order>
+ </importOrder>
+ </java>
+ </configuration>
+ <!-- https://github.com/diffplug/spotless/tree/master/plugin-maven use
+ mvn spotless:apply to rewrite source files use mvn spotless:check to validate
+ source files -->
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire-maven-plugin.version}</version>
+ <configuration>
+ <skipTests>false</skipTests>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${project.build.directory}/generated-sources/annotations/</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>${jacoco-maven-plugin.version}</version>
+ <executions>
+ <execution>
+ <id>default-prepare-agent</id>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-report</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- <plugin>
+ <groupId>io.swagger.codegen.v3</groupId>
+ <artifactId>swagger-codegen-maven-plugin</artifactId>
+ <version>${swagger-codegen-maven-plugin.version}</version>
+ <executions>
+ <execution>
+ <phase>test</phase>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ <configuration>
+ <inputSpec>${project.basedir}/api/rapp-manager.json</inputSpec>
+ <language>openapi-yaml</language>
+ <output>${project.basedir}/api</output>
+ <configOptions>
+ <outputFile>rapp-manager-api.yaml</outputFile>
+ </configOptions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin> -->
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-resource-one</id>
+ <phase>install</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.basedir}/../docs/offeredapis/swagger</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${project.basedir}/api</directory>
+ <includes>
+ <include>rapp-manager-api.*</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>io.fabric8</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <version>${docker-maven-plugin}</version>
+ <inherited>false</inherited>
+ <executions>
+ <execution>
+ <id>generate-rapp-manager-image</id>
+ <phase>package</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ <configuration>
+ <images>
+ <image>
+ <name>o-ran-sc/nonrtric-rapp-manager:${project.version}</name>
+ <build>
+ <cleanup>try</cleanup>
+ <contextDir>${basedir}</contextDir>
+ <dockerFile>Dockerfile</dockerFile>
+ <args>
+ <JAR>${project.build.finalName}.jar</JAR>
+ </args>
+ <tags>
+ <tag>${project.version}</tag>
+ </tags>
+ </build>
+ </image>
+ </images>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-rapp-manager-image</id>
+ <goals>
+ <goal>build</goal>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <images>
+ <image>
+ <name>o-ran-sc/nonrtric-rapp-manager:${project.version}</name>
+ <build>
+ <contextDir>${basedir}</contextDir>
+ <dockerFile>Dockerfile</dockerFile>
+ <args>
+ <JAR>${project.build.finalName}.jar</JAR>
+ </args>
+ <tags>
+ <tag>${project.docker.latestminortag.version}</tag>
+ <tag>${project.docker.latestfulltag.version}</tag>
+ <tag>${project.docker.latesttagtimestamp.version}</tag>
+ </tags>
+ </build>
+ </image>
+ </images>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/Application.java b/rapp-manager/src/main/java/org/oransc/rappmanager/Application.java
new file mode 100644
index 0000000..e897b45
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/Application.java
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/BeanFactory.java b/rapp-manager/src/main/java/org/oransc/rappmanager/BeanFactory.java
new file mode 100644
index 0000000..b35e5bb
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/BeanFactory.java
@@ -0,0 +1,47 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager;
+
+import org.oransc.rappmanager.client.AsyncRestClient;
+import org.oransc.rappmanager.configuration.ApplicationConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.multipart.MultipartResolver;
+import org.springframework.web.multipart.commons.CommonsMultipartResolver;
+
+@Configuration
+public class BeanFactory {
+
+ @Autowired
+ private ApplicationConfig appConfig;
+
+ @Bean
+ public AsyncRestClient getChartClient() {
+ return new AsyncRestClient(appConfig.getChartApi());
+ }
+
+ @Bean(name = "multipartResolver")
+ public MultipartResolver multipartResolver() {
+ CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
+ multipartResolver.setMaxUploadSize(100000);
+ return multipartResolver;
+ }
+
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/client/AsyncRestClient.java b/rapp-manager/src/main/java/org/oransc/rappmanager/client/AsyncRestClient.java
new file mode 100644
index 0000000..8f8bd82
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/client/AsyncRestClient.java
@@ -0,0 +1,298 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.client;
+
+import io.netty.channel.ChannelOption;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.netty.handler.timeout.ReadTimeoutHandler;
+import io.netty.handler.timeout.WriteTimeoutHandler;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.immutables.value.Value;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.MultipartBodyBuilder;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.util.ResourceUtils;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.BodyInserters.MultipartInserter;
+import org.springframework.web.reactive.function.client.ExchangeStrategies;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import reactor.core.publisher.Mono;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.resources.ConnectionProvider;
+import reactor.netty.tcp.TcpClient;
+
+/**
+ * Generic reactive REST client.
+ */
+public class AsyncRestClient {
+
+ @Value.Immutable
+ @Value.Style(redactedMask = "####")
+ public interface WebClientConfig {
+ public boolean isTrustStoreUsed();
+
+ @Value.Redacted
+ public String trustStorePassword();
+
+ public String trustStore();
+ }
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private WebClient webClient = null;
+ private final String baseUrl;
+ private static final AtomicInteger sequenceNumber = new AtomicInteger();
+ private final WebClientConfig clientConfig;
+ static KeyStore clientTrustStore = null;
+
+ public AsyncRestClient(String baseUrl) {
+ this(baseUrl, ImmutableWebClientConfig.builder().isTrustStoreUsed(false).trustStore("").trustStorePassword("")
+ .build());
+ }
+
+ public AsyncRestClient(String baseUrl, WebClientConfig config) {
+ this.baseUrl = baseUrl;
+ this.clientConfig = config;
+ }
+
+ public Mono<ResponseEntity<String>> putForEntity(String uri, String body) {
+ Object traceTag = createTraceTag();
+ logger.debug("{} PUT uri = '{}{}''", traceTag, baseUrl, uri);
+ logger.trace("{} PUT body: {}", traceTag, body);
+ return getWebClient() //
+ .flatMap(client -> {
+ RequestHeadersSpec<?> request = client.put() //
+ .uri(uri) //
+ .contentType(MediaType.APPLICATION_JSON) //
+ .bodyValue(body);
+ return retrieve(traceTag, request);
+ });
+ }
+
+ public Mono<ResponseEntity<String>> postForEntity(String uri, File file) {
+ Object traceTag = createTraceTag();
+ logger.debug("{} PUT uri = '{}{}''", traceTag, baseUrl, uri);
+ logger.trace("{} PUT body: {}", traceTag, file.getName());
+ return getWebClient() //
+ .flatMap(client -> {
+ RequestHeadersSpec<?> request = client.post() //
+ .uri(uri) //
+ .contentType(MediaType.MULTIPART_FORM_DATA) //
+ .bodyValue(createPostFileBody(file));
+ return retrieve(traceTag, request);
+ });
+ }
+
+ private MultipartInserter createPostFileBody(File file) {
+ MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
+ bodyBuilder.part("file", new FileSystemResource(file));
+ return BodyInserters.fromMultipartData(bodyBuilder.build());
+ }
+
+ public Mono<ResponseEntity<String>> putForEntity(String uri) {
+ Object traceTag = createTraceTag();
+ logger.debug("{} PUT uri = '{}{}''", traceTag, baseUrl, uri);
+ logger.trace("{} PUT body: <empty>", traceTag);
+ return getWebClient() //
+ .flatMap(client -> {
+ RequestHeadersSpec<?> request = client.put() //
+ .uri(uri);
+ return retrieve(traceTag, request);
+ });
+ }
+
+ public Mono<String> put(String uri, String body) {
+ return putForEntity(uri, body) //
+ .flatMap(this::toBody);
+ }
+
+ public Mono<ResponseEntity<String>> getForEntity(String uri) {
+ Object traceTag = createTraceTag();
+ logger.debug("{} GET uri = '{}{}''", traceTag, baseUrl, uri);
+ return getWebClient() //
+ .flatMap(client -> {
+ RequestHeadersSpec<?> request = client.get().uri(uri);
+ return retrieve(traceTag, request);
+ });
+ }
+
+ public Mono<String> get(String uri) {
+ return getForEntity(uri) //
+ .flatMap(this::toBody);
+ }
+
+ public Mono<ResponseEntity<String>> deleteForEntity(String uri) {
+ Object traceTag = createTraceTag();
+ logger.debug("{} DELETE uri = '{}{}''", traceTag, baseUrl, uri);
+ return getWebClient() //
+ .flatMap(client -> {
+ RequestHeadersSpec<?> request = client.delete().uri(uri);
+ return retrieve(traceTag, request);
+ });
+ }
+
+ public Mono<String> delete(String uri) {
+ return deleteForEntity(uri) //
+ .flatMap(this::toBody);
+ }
+
+ private Mono<ResponseEntity<String>> retrieve(Object traceTag, RequestHeadersSpec<?> request) {
+ final Class<String> clazz = String.class;
+ return request.retrieve() //
+ .toEntity(clazz) //
+ .doOnNext(entity -> logger.trace("{} Received: {}", traceTag, entity.getBody())) //
+ .doOnError(throwable -> onHttpError(traceTag, throwable));
+ }
+
+ private static Object createTraceTag() {
+ return sequenceNumber.incrementAndGet();
+ }
+
+ private void onHttpError(Object traceTag, Throwable t) {
+ if (t instanceof WebClientResponseException) {
+ WebClientResponseException exception = (WebClientResponseException) t;
+ logger.debug("{} HTTP error status = '{}', body '{}'", traceTag, exception.getStatusCode(),
+ exception.getResponseBodyAsString());
+ } else {
+ logger.debug("{} HTTP error: {}", traceTag, t.getMessage());
+ }
+ }
+
+ private Mono<String> toBody(ResponseEntity<String> entity) {
+ if (entity.getBody() == null) {
+ return Mono.just("");
+ } else {
+ return Mono.just(entity.getBody());
+ }
+ }
+
+ private boolean isCertificateEntry(KeyStore trustStore, String alias) {
+ try {
+ return trustStore.isCertificateEntry(alias);
+ } catch (KeyStoreException e) {
+ logger.error("Error reading truststore {}", e.getMessage());
+ return false;
+ }
+ }
+
+ private Certificate getCertificate(KeyStore trustStore, String alias) {
+ try {
+ return trustStore.getCertificate(alias);
+ } catch (KeyStoreException e) {
+ logger.error("Error reading truststore {}", e.getMessage());
+ return null;
+ }
+ }
+
+ private static synchronized KeyStore getTrustStore(String trustStorePath, String trustStorePass)
+ throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
+ if (clientTrustStore == null) {
+ KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
+ store.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());
+ clientTrustStore = store;
+ }
+ return clientTrustStore;
+ }
+
+ private SslContext createSslContextRejectingUntrustedPeers(String trustStorePath, String trustStorePass)
+ throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
+
+ final KeyStore trustStore = getTrustStore(trustStorePath, trustStorePass);
+ List<Certificate> certificateList = Collections.list(trustStore.aliases()).stream() //
+ .filter(alias -> isCertificateEntry(trustStore, alias)) //
+ .map(alias -> getCertificate(trustStore, alias)) //
+ .collect(Collectors.toList());
+ final X509Certificate[] certificates = certificateList.toArray(new X509Certificate[certificateList.size()]);
+
+ return SslContextBuilder.forClient() //
+ .trustManager(certificates) //
+ .build();
+ }
+
+ private SslContext createSslContext()
+ throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
+ if (this.clientConfig.isTrustStoreUsed()) {
+ return createSslContextRejectingUntrustedPeers(this.clientConfig.trustStore(),
+ this.clientConfig.trustStorePassword());
+ } else {
+ // Trust anyone
+ return SslContextBuilder.forClient() //
+ .trustManager(InsecureTrustManagerFactory.INSTANCE) //
+ .build();
+ }
+ }
+
+ private WebClient createWebClient(String baseUrl, SslContext sslContext) {
+ ConnectionProvider connectionProvider = ConnectionProvider.newConnection();
+ TcpClient tcpClient = TcpClient.create(connectionProvider) //
+ .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000) //
+ .secure(c -> c.sslContext(sslContext)) //
+
+ .doOnConnected(connection -> {
+ connection.addHandlerLast(new ReadTimeoutHandler(30));
+ connection.addHandlerLast(new WriteTimeoutHandler(30));
+ });
+ HttpClient httpClient = HttpClient.from(tcpClient);
+ ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
+
+ ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder() //
+ .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)) //
+ .build();
+
+ return WebClient.builder() //
+ .clientConnector(connector) //
+ .baseUrl(baseUrl) //
+ .exchangeStrategies(exchangeStrategies) //
+ .build();
+ }
+
+ private Mono<WebClient> getWebClient() {
+ if (this.webClient == null) {
+ try {
+ SslContext sslContext = createSslContext();
+ this.webClient = createWebClient(this.baseUrl, sslContext);
+ } catch (Exception e) {
+ logger.error("Could not create WebClient {}", e.getMessage());
+ return Mono.error(e);
+ }
+ }
+ return Mono.just(this.webClient);
+ }
+
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/configuration/ApplicationConfig.java b/rapp-manager/src/main/java/org/oransc/rappmanager/configuration/ApplicationConfig.java
new file mode 100644
index 0000000..7b8ced0
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/configuration/ApplicationConfig.java
@@ -0,0 +1,75 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.configuration;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import javax.validation.constraints.NotEmpty;
+import lombok.Getter;
+import org.oransc.rappmanager.exception.ServiceException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties()
+@EnableConfigurationProperties
+public class ApplicationConfig {
+
+ @NotEmpty
+ @Getter
+ @Value("${app.certpath}")
+ private String certPath;
+
+ @NotEmpty
+ @Getter
+ @Value("${app.tokenpath}")
+ private String tokenPath;
+
+ @NotEmpty
+ @Getter
+ @Value("${app.basepath}")
+ private String basePath;
+
+ @NotEmpty
+ @Getter
+ @Value("${app.chartregistry}")
+ private String chartApi;
+
+ public String getToken() throws ServiceException {
+ try {
+ Path path = Path.of(tokenPath);
+ return Files.readString(path);
+ } catch (IOException e) {
+ throw new ServiceException("Error Reading token", e);
+ }
+ }
+
+ public InputStream getCert() throws ServiceException {
+ try {
+ return new FileInputStream(certPath);
+ } catch (IOException e) {
+ throw new ServiceException("Error Reading token", e);
+ }
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/configuration/KubernetesClusterConfig.java b/rapp-manager/src/main/java/org/oransc/rappmanager/configuration/KubernetesClusterConfig.java
new file mode 100644
index 0000000..ce2b348
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/configuration/KubernetesClusterConfig.java
@@ -0,0 +1,64 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.configuration;
+
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.Configuration;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.util.ClientBuilder;
+import java.io.IOException;
+import org.oransc.rappmanager.exception.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class KubernetesClusterConfig {
+
+ @Autowired
+ private ApplicationConfig appConfig;
+
+ private CoreV1Api api;
+
+ private static final Logger logger = LoggerFactory.getLogger(KubernetesClusterConfig.class);
+
+ private void connectToKubernetesCluster() throws ServiceException {
+ logger.debug("Reading Kubernetes Configuration");
+ ApiClient client;
+ try {
+ client = ClientBuilder.cluster().build();
+ client.setBasePath(appConfig.getBasePath());
+ client.setApiKey(appConfig.getToken());
+ client.setSslCaCert(appConfig.getCert());
+ Configuration.setDefaultApiClient(client);
+ api = new CoreV1Api();
+ } catch (IOException e) {
+ throw new ServiceException("Failed to Read the Kubernetes Configuration", e);
+ }
+ }
+
+ public CoreV1Api getKubernetesClient() throws ServiceException {
+ if (api == null) {
+ connectToKubernetesCluster();
+ }
+ return api;
+ }
+}
+
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppController.java b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppController.java
new file mode 100644
index 0000000..bf2e551
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/AppController.java
@@ -0,0 +1,88 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.oransc.rappmanager.exception.ServiceException;
+import org.oransc.rappmanager.service.App;
+import org.oransc.rappmanager.service.AppList;
+import org.oransc.rappmanager.service.AppService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController("rAppController")
+@RequestMapping("rms")
+@Api(tags = {"rapp"})
+public class AppController {
+
+ @Autowired
+ private AppService appService;
+
+ @GetMapping(path = "/apps", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Return all Apps")
+ @ApiResponses(value = {@ApiResponse(code = 200, message = "rApp List")})
+ public ResponseEntity<AppList> getAllApps() throws ServiceException {
+ return new ResponseEntity<>(appService.getAllApps(), HttpStatus.OK);
+ }
+
+ @GetMapping(path = "/apps/{name}", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Return a App")
+ @ApiResponses(value = {@ApiResponse(code = 200, message = "rApp")})
+ public ResponseEntity<App> getApp(@PathVariable("name") String name) {
+ return new ResponseEntity<>(appService.getApp(name), HttpStatus.OK);
+ }
+
+ @GetMapping(path = "/apps/{name}/status", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Return the status of an App")
+ @ApiResponses(value = {@ApiResponse(code = 200, message = "rApp")})
+ public ResponseEntity<App> getAppStatus(@PathVariable("name") String name) throws ServiceException {
+ return new ResponseEntity<>(appService.getAppStatus(name), HttpStatus.OK);
+ }
+
+ @PostMapping(path = "/apps", consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Install the rApp")
+ @ApiResponses(value = {@ApiResponse(code = 201, message = "rApp Installed")})
+ public ResponseEntity<?> installApp(@RequestBody App app) throws ServiceException {
+ appService.installApp(app);
+ return new ResponseEntity<>(HttpStatus.CREATED);
+ }
+
+ @DeleteMapping(path = "/apps/{name}", consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Uninstall the App")
+ @ApiResponses(value = {@ApiResponse(code = 201, message = "rApp Uninstalled")})
+ public ResponseEntity<?> deleteApp(@PathVariable("name") String name,
+ @PathVariable("namespace") String releaseNamespace) throws ServiceException {
+ appService.uninstallApp(name, releaseNamespace);
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/controller/Chart.java b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/Chart.java
new file mode 100644
index 0000000..6974a25
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/Chart.java
@@ -0,0 +1,46 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.controller;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.web.multipart.MultipartFile;
+
+@ApiModel(value = "Chart", description = "Chart Information")
+@Getter
+@Setter
+@JsonInclude(Include.NON_NULL)
+public class Chart {
+
+ private String name;
+ private String version;
+ private MultipartFile chart;
+ private String description;
+ private String apiVersion;
+ private String appVersion;
+ private List<String> urls;
+ private String digest;
+ @JsonProperty(required = false)
+ private boolean upgrade;
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/controller/ChartController.java b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/ChartController.java
new file mode 100644
index 0000000..275da2a
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/ChartController.java
@@ -0,0 +1,89 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import java.util.Map;
+import org.oransc.rappmanager.exception.ServiceException;
+import org.oransc.rappmanager.service.ChartService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController("chartController")
+@RequestMapping("rms")
+@Api(tags = {"chart"})
+public class ChartController {
+
+ @Autowired
+ private ChartService chartService;
+
+ @GetMapping(path = "/charts", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Return all Charts")
+ @ApiResponses(value = {@ApiResponse(code = 200, message = "Chart List")})
+ public ResponseEntity<Map<String, Chart[]>> getCharts() throws ServiceException {
+ return new ResponseEntity<>(chartService.getCharts(), HttpStatus.OK);
+ }
+
+ @GetMapping(path = "/charts/{name}", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Return a chart")
+ @ApiResponses(value = {@ApiResponse(code = 200, message = "Chart")})
+ public ResponseEntity<Chart[]> getChart(@PathVariable("name") String name) throws ServiceException {
+ return new ResponseEntity<>(chartService.getChart(name), HttpStatus.OK);
+ }
+
+ @PostMapping(path = "/charts", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Onboard the Chart")
+ @ApiResponses(value = {@ApiResponse(code = 201, message = "Chart Onboarded")})
+ public ResponseEntity<?> onboardChart(@ModelAttribute Chart chart) throws ServiceException {
+ chartService.saveChart(chart);
+ return new ResponseEntity<>(HttpStatus.CREATED);
+ }
+
+ @DeleteMapping(path = "/charts/{id}", consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Delete the Chart")
+ @ApiResponses(value = {@ApiResponse(code = 201, message = "Chart Deleted")})
+ public ResponseEntity<?> deleteChart(@PathVariable("name") String name, @PathVariable("version") String version)
+ throws ServiceException {
+ chartService.deleteChart(name, version);
+ return new ResponseEntity<>(HttpStatus.CREATED);
+ }
+
+ @PostMapping(path = "/charts", consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Update the Chart to new Version")
+ @ApiResponses(value = {@ApiResponse(code = 201, message = "Chart Upgraded")})
+ public ResponseEntity<?> updateChart(@ModelAttribute Chart chart) throws ServiceException {
+ chartService.saveChart(chart);
+ return new ResponseEntity<>(HttpStatus.CREATED);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/controller/KubernetesConfigController.java b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/KubernetesConfigController.java
new file mode 100644
index 0000000..170a34f
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/controller/KubernetesConfigController.java
@@ -0,0 +1,56 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.oransc.rappmanager.configuration.KubernetesClusterConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController("KubernetesConfigController")
+@RequestMapping("rms")
+@Api(tags = {"kubeconfig"})
+public class KubernetesConfigController {
+
+ @Autowired
+ private KubernetesClusterConfig kubeConfig;
+
+ @GetMapping(path = "/config", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Return all Connected Kubernetes Cluster")
+ @ApiResponses(value = {@ApiResponse(code = 200, message = "Kubernetes Cluster List")})
+ public ResponseEntity<Object> getAllClusters() {
+ return new ResponseEntity<>(null, HttpStatus.OK);
+ }
+
+ @GetMapping(path = "/config/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+ @ApiOperation(value = "Return a Kubernetes Cluster")
+ @ApiResponses(value = {@ApiResponse(code = 200, message = "Kubernetes Cluster")})
+ public ResponseEntity<Object> getCluster(@PathVariable("id") String clusterId) {
+ return new ResponseEntity<>(null, HttpStatus.OK);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/exception/EntityNotFoundException.java b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/EntityNotFoundException.java
new file mode 100644
index 0000000..2c01b08
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/EntityNotFoundException.java
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.exception;
+
+public class EntityNotFoundException extends ServiceException {
+
+ private static final long serialVersionUID = 1L;
+
+ public EntityNotFoundException(String message) {
+ super(message);
+ }
+
+ public EntityNotFoundException(String message, Exception originalException) {
+ super(message, originalException);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/exception/ErrorInfo.java b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/ErrorInfo.java
new file mode 100644
index 0000000..69a878a
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/ErrorInfo.java
@@ -0,0 +1,17 @@
+package org.oransc.rappmanager.exception;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+public class ErrorInfo {
+
+ private String message;
+
+ private int code;
+
+}
+
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/exception/GlobalExceptionHandler.java b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..019f6ca
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/GlobalExceptionHandler.java
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@ControllerAdvice(annotations = RestController.class)
+public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
+
+ @ExceptionHandler(EntityNotFoundException.class)
+ public final ResponseEntity<ErrorInfo> handleNotFoundException(EntityNotFoundException ex) {
+ return getError(ex, HttpStatus.NOT_FOUND);
+ }
+
+ @ExceptionHandler(InvalidRequestException.class)
+ public final ResponseEntity<ErrorInfo> handleInvalidRequestException(InvalidRequestException ex) {
+ return getError(ex, HttpStatus.BAD_REQUEST);
+ }
+
+ private ResponseEntity<ErrorInfo> getError(Exception ex, HttpStatus status) {
+ ErrorInfo errorInfo = ErrorInfo.builder().message(ex.getMessage()).code(status.value()).build();
+ return new ResponseEntity<ErrorInfo>(errorInfo, status);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/exception/InvalidRequestException.java b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/InvalidRequestException.java
new file mode 100644
index 0000000..b0cd5e2
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/InvalidRequestException.java
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.exception;
+
+public class InvalidRequestException extends ServiceException {
+
+ private static final long serialVersionUID = 1L;
+
+ public InvalidRequestException(String message) {
+ super(message);
+ }
+
+ public InvalidRequestException(String message, Exception originalException) {
+ super(message, originalException);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/exception/ServiceException.java b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/ServiceException.java
new file mode 100644
index 0000000..28ae8d5
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/exception/ServiceException.java
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.exception;
+
+public class ServiceException extends Exception {
+
+ private static final long serialVersionUID = 6810785674716590648L;
+
+ public ServiceException(String message) {
+ super(message);
+ }
+
+ public ServiceException(String message, Exception originalException) {
+ super(message, originalException);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmClient.java b/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmClient.java
new file mode 100644
index 0000000..c98b40a
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmClient.java
@@ -0,0 +1,69 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.helm;
+
+import java.io.IOException;
+import org.oransc.rappmanager.exception.ServiceException;
+import org.oransc.rappmanager.service.App;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+// Supports only helm3 + version
+@Component
+public class HelmClient {
+
+ @Autowired
+ private HelmUtil helmUtil;
+
+
+ public void addRepo(String repoName, String url) {
+
+ }
+
+ public void searchRepo(String chartName) {
+
+ }
+
+ public void repoUpdate() {
+
+ }
+
+ public boolean installApp(App app) throws ServiceException {
+ ProcessBuilder builder = helmUtil.prepareHelmCommand(app.getName(), app.getVersion(), app.getChartName(),
+ app.getReleaseNamespace(), "install");
+ return executeCommand(builder);
+ }
+
+ public boolean uninstallApp(String name, String releaseNamespace) throws ServiceException {
+ ProcessBuilder builder = helmUtil.prepareHelmCommand(name, null, null, releaseNamespace, "uninstall");
+ return executeCommand(builder);
+ }
+
+ private boolean executeCommand(ProcessBuilder builder) throws ServiceException {
+ try {
+ Process process = builder.start();
+ process.waitFor();
+ return process.exitValue() == 0;
+ // String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
+ // return output.contains("uninstalled");
+ } catch (IOException | InterruptedException e) {
+ throw new ServiceException("Failed to execute the Command", e);
+ }
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmUtil.java b/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmUtil.java
new file mode 100644
index 0000000..3005e0d
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/helm/HelmUtil.java
@@ -0,0 +1,59 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.helm;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.oransc.rappmanager.configuration.ApplicationConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class HelmUtil {
+
+ private final String KUBE_API = "--kube-apiserver";
+ private final String KUBE_TOKEN = "--kube-token";
+ private final String KUBE_CERT = "--kube-ca-file";
+
+ // Responsible for adding default chart registry to helm repo
+ // Responsible for identifying the k8's config information required for helm
+ @Autowired
+ private ApplicationConfig appConfig;
+
+ public ProcessBuilder prepareHelmCommand(String name, String version, String chartName, String releaseNamespace,
+ String operation) {
+ List<String> helmArguments = null;
+ ProcessBuilder processBuilder = new ProcessBuilder();
+ if ("install".equalsIgnoreCase(operation)) {
+ helmArguments = Arrays.asList("helm", "install", name, chartName, "--version", version, "--namespace",
+ releaseNamespace);
+ } else if ("uninstall".equalsIgnoreCase(operation)) {
+ helmArguments = Arrays.asList("helm", "uninstall", name, "--namespace", releaseNamespace);
+ }
+ addKubeApiInfo(helmArguments);
+ return processBuilder.command(helmArguments);
+ }
+
+ private void addKubeApiInfo(List<String> helmArguments) {
+ List<String> kubeApiInfo = Arrays.asList(KUBE_API, "", KUBE_TOKEN, "", KUBE_CERT, "");
+ helmArguments.addAll(kubeApiInfo);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/App.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/App.java
new file mode 100644
index 0000000..58f5271
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/service/App.java
@@ -0,0 +1,46 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.service;
+
+import java.util.List;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.web.multipart.MultipartFile;
+
+@Getter
+@Setter
+@Builder
+public class App {
+
+ private String name;
+ private String chartName;
+ private String version;
+ private MultipartFile overrideFile;
+ private String releaseNamespace; // helm
+ // verify that later
+ private String namespace; // kube
+
+ /**
+ * private Status status; // how to determine the status of the app. Which kubernetes object will tell me //that the
+ * app is running? It can have pod or deployment or jobs, statefulset. //Easy way is to get meta-data of the app
+ *
+ * public enum Status { RUNNING, ERROR, }
+ **/
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppList.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppList.java
new file mode 100644
index 0000000..09689b4
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppList.java
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.service;
+
+import java.util.List;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+public class AppList {
+
+ private List<App> apps;
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppService.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppService.java
new file mode 100644
index 0000000..eae8a60
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppService.java
@@ -0,0 +1,65 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.service;
+
+import org.oransc.rappmanager.exception.ServiceException;
+import org.oransc.rappmanager.helm.HelmClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AppService {
+
+ @Autowired
+ private AppStore appStore;
+
+ @Autowired
+ private HelmClient helmClient;
+
+ public AppList getAllApps() throws ServiceException {
+ // Call Kubernetes api to get the apps- pod, deplyment, statefulset, job?
+ // Get the apps list this ms managing.
+ return AppList.builder().apps(appStore.getAllApp()).build();
+ }
+
+ public App getApp(String name) {
+ return appStore.getApp(name);
+ }
+
+ public App getAppStatus(String name) throws ServiceException {
+ // Check the app status in the kubernetes cluster
+ return appStore.getApp(name);
+ }
+
+ public void installApp(App app) throws ServiceException {
+ // call the kubernetes api to create objects (helm call)
+ // Call the kubernetes api to create namespace
+ // Validate the override file
+ if (helmClient.installApp(app)) {
+ appStore.addApp(app);
+ }
+ }
+
+ public void uninstallApp(String name, String releaseNamespace) throws ServiceException {
+ // call the kubernetes api to delete objects (helm call)
+ if (helmClient.uninstallApp(name, releaseNamespace)) {
+ appStore.deleteApp(name);
+ }
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppStore.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppStore.java
new file mode 100644
index 0000000..55f3456
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/service/AppStore.java
@@ -0,0 +1,56 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.oransc.rappmanager.exception.ServiceException;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AppStore {
+
+ /**
+ * The appStore map contains app name as key & App as value
+ */
+ private Map<String, App> appStore = new HashMap<>();
+
+ public synchronized void addApp(App app) throws ServiceException {
+
+ if (!appStore.containsKey(app.getName())) {
+ appStore.put(app.getName(), app);
+ } else {
+ throw new ServiceException("App Already Exist");
+ }
+ }
+
+ public synchronized App getApp(String name) {
+ return appStore.get(name);
+ }
+
+ public synchronized List<App> getAllApp() {
+ return new ArrayList<App>(appStore.values());
+ }
+
+ public synchronized void deleteApp(String name) {
+ appStore.remove(name);
+ }
+}
diff --git a/rapp-manager/src/main/java/org/oransc/rappmanager/service/ChartService.java b/rapp-manager/src/main/java/org/oransc/rappmanager/service/ChartService.java
new file mode 100644
index 0000000..18691af
--- /dev/null
+++ b/rapp-manager/src/main/java/org/oransc/rappmanager/service/ChartService.java
@@ -0,0 +1,105 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. 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.oransc.rappmanager.service;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.Map;
+import org.oransc.rappmanager.client.AsyncRestClient;
+import org.oransc.rappmanager.controller.Chart;
+import org.oransc.rappmanager.exception.ServiceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+// Need to support multiple Chart registry. So add an interface and use it based on the registry choice.
+@Service
+public class ChartService {
+
+ private static final Logger logger = LoggerFactory.getLogger(ChartService.class);
+
+ @Autowired
+ private AsyncRestClient chartClient;
+
+ private static Gson gson = new GsonBuilder().serializeNulls().create();
+
+ private static String basePath = "/api/charts";
+
+ public Map<String, Chart[]> getCharts() throws ServiceException {
+ ResponseEntity<String> rsp = chartClient.getForEntity(basePath).block();
+ if (rsp.getStatusCodeValue() != 200) {
+ throw new ServiceException("Error Getting Charts");
+ }
+ logger.debug("Sucessfully fetched the charts");
+ return parseChart(rsp.getBody());
+ }
+
+ public Chart[] getChart(String name) throws ServiceException {
+ ResponseEntity<String> rsp = chartClient.getForEntity(basePath + "/" + name).block();
+ if (rsp.getStatusCodeValue() != 200) {
+ throw new ServiceException("Error Getting Chart " + name);
+ }
+ logger.debug("Sucessfully fetched the chart");
+ return gson.fromJson(rsp.getBody(), Chart[].class);
+ }
+
+ // Not working. Need to check
+ public void saveChart(Chart chart) throws ServiceException {
+ ResponseEntity<String> rsp;
+ try {
+ File file = multipartToFile(chart.getChart(), chart.getName());
+ rsp = chartClient.postForEntity(basePath, file).block();
+ } catch (IOException e) {
+ throw new ServiceException("Error in saving Chart", e);
+ }
+ if (rsp.getStatusCodeValue() != 201) {
+ throw new ServiceException("Error in saving Chart " + chart.getName());
+ }
+ logger.debug("Sucessfully fetched the chart");
+ }
+
+ public void deleteChart(String name, String version) throws ServiceException {
+ ResponseEntity<String> rsp = chartClient.deleteForEntity(basePath + "/" + name + "/" + version).block();
+ if (rsp.getStatusCodeValue() != 200) {
+ throw new ServiceException("Error in deleting Chart " + name);
+ }
+ logger.debug("Sucessfully deleted the chart");
+ }
+
+ private Map<String, Chart[]> parseChart(String json) {
+ Type mapType = new TypeToken<Map<String, Chart[]>>() {}.getType();
+ return gson.fromJson(json, mapType);
+ }
+
+ private static File multipartToFile(MultipartFile multipart, String fileName)
+ throws IllegalStateException, IOException {
+ File convFile = new File("/opt/app/rapp-manager" + "/" + fileName);
+ multipart.transferTo(convFile);
+ return convFile;
+ }
+
+}
+
diff --git a/rapp-manager/src/test/java/org/oransc/rappmanager/controller/AppControllerTest.java b/rapp-manager/src/test/java/org/oransc/rappmanager/controller/AppControllerTest.java
new file mode 100644
index 0000000..062804d
--- /dev/null
+++ b/rapp-manager/src/test/java/org/oransc/rappmanager/controller/AppControllerTest.java
@@ -0,0 +1,59 @@
+package org.oransc.rappmanager.controller;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.oransc.rappmanager.Application;
+import org.oransc.rappmanager.service.App;
+import org.oransc.rappmanager.service.AppList;
+import org.oransc.rappmanager.service.AppService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
+@AutoConfigureMockMvc
+@EnableAutoConfiguration
+public class AppControllerTest {
+
+ @LocalServerPort
+ private int port;
+
+ @Autowired
+ private MockMvc mvc;
+
+ @MockBean
+ private AppService appService;
+
+ private AppList apps;
+
+ @Test
+ public void testGetAllApps() throws Exception {
+ apps = getAllApps();
+ given(appService.getAllApps()).willReturn(apps);
+ mvc.perform(get("/rms/apps").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.apps.[0].name", is("helloworldApp")));
+ }
+
+ private AppList getAllApps() {
+ return AppList.builder()
+ .apps(Arrays.asList(App.builder().name("helloworldApp").releaseNamespace("helmrelease").build()))
+ .build();
+ }
+}
+
diff --git a/rapp-manager/src/test/java/org/oransc/rappmanager/controller/ChartControllerTest.java b/rapp-manager/src/test/java/org/oransc/rappmanager/controller/ChartControllerTest.java
new file mode 100644
index 0000000..8fc9171
--- /dev/null
+++ b/rapp-manager/src/test/java/org/oransc/rappmanager/controller/ChartControllerTest.java
@@ -0,0 +1,60 @@
+package org.oransc.rappmanager.controller;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.oransc.rappmanager.Application;
+import org.oransc.rappmanager.service.ChartService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
+@AutoConfigureMockMvc
+@EnableAutoConfiguration
+public class ChartControllerTest {
+
+ @LocalServerPort
+ private int port;
+
+ @Autowired
+ private MockMvc mvc;
+
+ @MockBean
+ private ChartService chartService;
+
+ private Map<String, Chart[]> charts = new HashMap<>();
+
+ @Test
+ public void testGetCharts() throws Exception {
+ chartList();
+ given(chartService.getCharts()).willReturn(charts);
+ mvc.perform(get("/rms/charts").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.helloworldApp.[0].name", is("helloworldApp")));
+ }
+
+ private void chartList() {
+ Chart chart = new Chart();
+ chart.setName("helloworldApp");
+ Chart[] chartArray = new Chart[1];
+ chartArray[0] = chart;
+ charts.put("helloworldApp", chartArray);
+ }
+}
+