Python based rapp manager
Change-Id: I16338699bd8e009a3f5f09b02cbe37ceebd3fc5d
diff --git a/python-rapp-manager/README.md b/python-rapp-manager/README.md
new file mode 100644
index 0000000..cd30d08
--- /dev/null
+++ b/python-rapp-manager/README.md
@@ -0,0 +1 @@
+# See the README.md in src #
\ No newline at end of file
diff --git a/python-rapp-manager/chart/README.md b/python-rapp-manager/chart/README.md
new file mode 100644
index 0000000..2de6cad
--- /dev/null
+++ b/python-rapp-manager/chart/README.md
@@ -0,0 +1,2 @@
+# Chart name #
+The chart is named "my-cherry-chart"
\ No newline at end of file
diff --git a/python-rapp-manager/chart/buildachart-0.1.0.tgz b/python-rapp-manager/chart/buildachart-0.1.0.tgz
new file mode 100644
index 0000000..f9c571a
--- /dev/null
+++ b/python-rapp-manager/chart/buildachart-0.1.0.tgz
Binary files differ
diff --git a/python-rapp-manager/chart/override.yaml b/python-rapp-manager/chart/override.yaml
new file mode 100644
index 0000000..fe39324
--- /dev/null
+++ b/python-rapp-manager/chart/override.yaml
@@ -0,0 +1,5 @@
+
+
+service:
+ type: NodePort
+ port: 80
diff --git a/python-rapp-manager/src/.gitignore b/python-rapp-manager/src/.gitignore
new file mode 100644
index 0000000..3384196
--- /dev/null
+++ b/python-rapp-manager/src/.gitignore
@@ -0,0 +1 @@
+.dockererr
\ No newline at end of file
diff --git a/python-rapp-manager/src/Dockerfile b/python-rapp-manager/src/Dockerfile
new file mode 100644
index 0000000..5ca004e
--- /dev/null
+++ b/python-rapp-manager/src/Dockerfile
@@ -0,0 +1,49 @@
+# ============LICENSE_START===============================================
+# Copyright (C) 2020 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=================================================
+#
+
+ARG NEXUS_PROXY_REPO
+
+FROM ${NEXUS_PROXY_REPO}python:3.8
+
+#install nginx
+RUN apt-get update
+RUN apt-get install -y nginx=1.14.*
+
+WORKDIR /usr/src/app
+
+COPY app/ /usr/src/app/
+COPY cert/ /usr/src/app/cert/
+
+COPY tiller-serviceaccount.yaml /usr/src/app/tiller-serviceaccount.yaml
+
+RUN pip install -r requirements.txt
+
+RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
+RUN chmod 700 get_helm.sh
+RUN ./get_helm.sh
+
+RUN curl -LO https://dl.k8s.io/release/v1.20.2/bin/linux/amd64/kubectl
+
+RUN chmod +x ./kubectl
+
+RUN mv ./kubectl /usr/local/bin/kubectl
+
+RUN mkdir -p /var/rappman
+
+RUN chmod +x start.sh
+
+CMD [ "./start.sh" ]
diff --git a/python-rapp-manager/src/README.md b/python-rapp-manager/src/README.md
new file mode 100644
index 0000000..bdc2002
--- /dev/null
+++ b/python-rapp-manager/src/README.md
@@ -0,0 +1,44 @@
+# Intro #
+
+The rapp manger can be executed in python on the local machine see CMD1. A kube cluster need to be running and accessable by kubectrl
+Or the rapp manager can be installed in the kube cluster - service and deployment, see CMD2 and CMD3.
+
+In both cases, rest is used
+
+
+# CMD1 Run in python #
+
+`python3 app/rappman.py $PWD`
+
+# CMD2 Build rapp manger container #
+
+`docker build --build-arg NEXUS_PROXY_REPO=nexus3.onap.org:10001/ -t rappman .`
+
+# CMD3 Apply rapp manger in kube #
+
+`kubectl apply -f svc-app.yaml`
+
+# CMD4 Delete rapp manger in kube #
+
+`kubectl delete -f svc-app.yaml`
+
+# Interface #
+
+The host and port in the below url examples is for local python execution.
+For running towards a local kube cluster, a Function test config should running or at least the kubeproxy as configured by a function test script. All curl cmds need to specify the proxy `--proxy http://localhost:<proxy-port` and `localhost:2222` shall be replaced with `rapp-man.nonrtric:9970`.
+
+| cmd | description |
+|-|-|
+| `curl http://localhost:2222/charts` | json list of charts |
+| `curl -X PUT http://localhost:2222/charts/<chart-name> -F "chart=@<filename1>.tgz" -F "values=@<filename1>.yaml"` | Upload char and overridde, at least one file need to be given. Multiple uploads are possible|
+| `curl http://localhost:2222/charts/<chart-name>/status` | json structure of status, filenames etc |
+| `curl -X POST 'http://localhost:2222/charts/<chart-name>?action=install&namespace=<namespace>' `| Install a chart in a namespace. If the chart is already installed, a helm upgrade is attempted |
+| `curl -X POST 'http://localhost:2222/charts/<chart-name>?action=uninstall'` | Uninstall an previously installed chart |
+| `curl -X DELETE 'http://localhost:2222/charts/<chart-name>'` | Deletes a chart. The chart cannot currently be installed |
+
+
+
+
+
+
+
diff --git a/python-rapp-manager/src/app/nginx.conf b/python-rapp-manager/src/app/nginx.conf
new file mode 100644
index 0000000..3ef5809
--- /dev/null
+++ b/python-rapp-manager/src/app/nginx.conf
@@ -0,0 +1,103 @@
+user www-data;
+worker_processes auto;
+pid /run/nginx.pid;
+include /etc/nginx/modules-enabled/*.conf;
+
+events {
+ worker_connections 768;
+ # multi_accept on;
+}
+
+http {
+
+ ##
+ # Basic Settings
+ ##
+
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+ keepalive_timeout 65;
+ types_hash_max_size 2048;
+ # server_tokens off;
+
+ # server_names_hash_bucket_size 64;
+ # server_name_in_redirect off;
+
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ server { # simple reverse-proxy
+ listen 9970;
+ listen [::]:9970;
+ listen 9971 ssl;
+ listen [::]:9971 ssl;
+ server_name localhost;
+ ssl_certificate /usr/src/app/cert/cert.crt;
+ ssl_certificate_key /usr/src/app/cert/key.crt;
+ ssl_password_file /usr/src/app/cert/pass;
+
+ # serve dynamic requests
+ location / {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_pass http://localhost:2222;
+ }
+ }
+ ##
+ # SSL Settings
+ ##
+
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
+ ssl_prefer_server_ciphers on;
+
+ ##
+ # Logging Settings
+ ##
+
+ access_log /var/log/nginx/access.log;
+ error_log /var/log/nginx/error.log;
+
+ ##
+ # Gzip Settings
+ ##
+
+ gzip on;
+
+ # gzip_vary on;
+ # gzip_proxied any;
+ # gzip_comp_level 6;
+ # gzip_buffers 16 8k;
+ # gzip_http_version 1.1;
+ # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+
+ ##
+ # Virtual Host Configs
+ ##
+
+ include /etc/nginx/conf.d/*.conf;
+ include /etc/nginx/sites-enabled/*;
+}
+
+
+#mail {
+# # See sample authentication script at:
+# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
+#
+# # auth_http localhost/auth.php;
+# # pop3_capabilities "TOP" "USER";
+# # imap_capabilities "IMAP4rev1" "UIDPLUS";
+#
+# server {
+# listen localhost:110;
+# protocol pop3;
+# proxy on;
+# }
+#
+# server {
+# listen localhost:143;
+# protocol imap;
+# proxy on;
+# }
+#}
diff --git a/python-rapp-manager/src/app/rappman.py b/python-rapp-manager/src/app/rappman.py
new file mode 100644
index 0000000..2ae7d3d
--- /dev/null
+++ b/python-rapp-manager/src/app/rappman.py
@@ -0,0 +1,397 @@
+
+# ============LICENSE_START===============================================
+# Copyright (C) 2020 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=================================================
+#
+
+from flask import Flask, request, Response
+from flask import send_file
+from time import sleep
+import time
+from datetime import datetime
+import json
+import traceback
+import logging
+import socket
+import subprocess
+import os
+import shutil
+import sys
+
+app = Flask(__name__)
+
+# list of callback messages
+msg_callbacks={}
+
+# Server info
+HOST_IP = "::"
+HOST_PORT = 2222
+
+
+
+NOTINSTALLED="NOT-INSTALLED"
+INSTALLED="INSTALLED"
+NONE="NONE"
+UNDEFINED_NAMESPACE="#NONE#"
+STATUS_JSON="chart_status.json"
+
+BASE_DIR=None
+
+if len(sys.argv) > 1:
+ if isinstance(sys.argv[1], str):
+ BASE_DIR = sys.argv[1]
+
+if BASE_DIR is None and os.path.isdir("/var"):
+ BASE_DIR="/var"
+ try:
+ os.chdir(BASE_DIR)
+ except OSError:
+ print("Cannot change dir to "+BASE_DIR)
+
+if BASE_DIR is None:
+ print("BASE_DIR cannot be set")
+ exit(1)
+
+print("base dir: " + str(BASE_DIR))
+
+CHART_REPO=BASE_DIR+"/rappman"
+
+#I'm alive function
+@app.route('/',
+ methods=['GET'])
+def index():
+ return "rappman OK", 200
+
+@app.route('/charts',
+ methods=['GET'])
+def get_charts():
+
+ files=[]
+ for f in os.listdir(CHART_REPO):
+ files.append(f)
+ return json.dumps(files), 200
+
+@app.route('/charts/<string:chartname>',
+ methods=['PUT'])
+def put_upload_chart(chartname):
+
+ print("put_upload_chart Chartname: "+chartname)
+
+ for f in request.files:
+ print(str(f))
+
+ chart_status=None
+
+
+ chart_dir=CHART_REPO+"/"+chartname
+ status_file=chart_dir+"/"+STATUS_JSON
+ if not os.path.isdir(chart_dir):
+ try:
+ os.mkdir(chart_dir)
+ except OSError:
+ return "Cannot create chart dir "+chart_dir,400
+
+ if os.path.isfile(status_file):
+ with open(status_file, 'r') as infile:
+ chart_status=json.load(infile)
+ else:
+ chart_status={}
+ chart_status["name"]=chartname
+ chart_status["install_state"]=NOTINSTALLED
+ chart_status["install_namespace"]=UNDEFINED_NAMESPACE
+ chart_status["chart_file_name"]=NONE
+ chart_status["chart_file_status"]=NOTINSTALLED
+ chart_status["override_file_name"]=NONE
+ chart_status["override_file_status"]=NOTINSTALLED
+ with open(status_file, 'w') as outfile:
+ json.dump(chart_status, outfile)
+
+ file_count=0
+ if 'chart' in request.files:
+ file=request.files['chart']
+ if not file.filename.endswith(".tgz"):
+ return "Chart file has incorrect file extension, expected 'tgz'",400
+ chart_file_name=chart_dir+"/"+file.filename
+ file.save(chart_file_name)
+ file_count=file_count+1
+ chart_status["chart_file_status"]=NOTINSTALLED
+ chart_status["chart_file_name"]=file.filename
+
+ if 'values' in request.files:
+ file=request.files['values']
+ if not file.filename.endswith(".yaml") and not file.filename.endswith(".yml"):
+ return "Override file has incorrect file extension, expected 'yaml' or 'yml'",400
+ chart_file_name=chart_dir+"/"+file.filename
+ file.save(chart_file_name)
+ file_count=file_count+1
+ chart_status["override_file_status"]=NOTINSTALLED
+ chart_status["override_file_name"]=file.filename
+
+ if file_count==0:
+ return "Nor chart or override file in request",400
+
+ if file_count != len(request.files):
+ return "Unknown file(s) sent in request",400
+
+ with open(status_file, 'w') as outfile:
+ json.dump(chart_status, outfile)
+
+ return "OK", 200
+
+@app.route('/charts/<string:chartname>',
+ methods=['POST'])
+def post_action_chart(chartname):
+
+ print("post_action_chart Chartname: "+chartname)
+
+ chart_dir=CHART_REPO+"/"+chartname
+
+ action_id=request.args.get('action')
+ if (action_id is None):
+ return "Action id missing", 400
+
+ if action_id != "install" and action_id != "uninstall":
+ return "Action id should be install or uninstall", 400
+
+ if not os.path.isdir(chart_dir):
+ return "No chart with name: " + chartname, 404
+
+ status_file=chart_dir+"/"+STATUS_JSON
+ try:
+ with open(status_file, 'r') as infile:
+ chart_status=json.load(infile)
+ except Exception as err:
+ return "Internal error - "+str(err), 500
+ os.chdir(chart_dir)
+
+ print("curdir: " + str(os.getcwd()))
+ if action_id == "install":
+
+ namespace=request.args.get("namespace")
+ if namespace is None:
+ namespace=chart_status["install_namespace"]
+ if namespace == UNDEFINED_NAMESPACE:
+ return "Namespace missing", 400
+ else:
+ if len(namespace) == 0:
+ return "Namespace is zero lengths", 400
+ if chart_status["install_namespace"] == UNDEFINED_NAMESPACE:
+ chart_status["install_namespace"]=namespace
+ elif chart_status["install_state"]==NOTINSTALLED:
+ chart_status["install_namespace"]=namespace
+ elif chart_status["install_namespace"] != namespace and chart_status["install_state"]==INSTALLED:
+ return "Cannot change namespace for installed chart", 400
+
+ cmd_arr=["kubectl", "get", "namespace", namespace]
+ print("cmd arr: "+str(cmd_arr))
+ result = subprocess.run(cmd_arr, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ print(str(result.stderr))
+ print(str(result.stdout))
+
+ if result.returncode != 0:
+ cmd_arr=["kubectl", "create", "namespace", namespace]
+ print("cmd arr: "+str(cmd_arr))
+ result = subprocess.run(cmd_arr, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ print(str(result.stderr))
+ print(str(result.stdout))
+ if result.returncode != 0:
+ return "Cannot create namespace: "+namespace, 500
+
+ upgrade=False
+ cmd_arr=[]
+ cmd_arr.append("helm")
+ if chart_status["install_state"]==NOTINSTALLED:
+ cmd_arr.append("install")
+ else:
+ cmd_arr.append("upgrade")
+ upgrade=True
+
+ cmd_arr.append("-n")
+ cmd_arr.append(namespace)
+
+ cmd_arr.append(chartname)
+ if chart_status["chart_file_name"]==NONE:
+ return "Chart is missing", 500
+
+ chart_status["install_state"]=INSTALLED
+ chart_status["chart_file_status"]=INSTALLED
+ cmd_arr.append(chart_status["chart_file_name"])
+ if chart_status["override_file_name"]!=NONE:
+ chart_status["override_file_status"]=INSTALLED
+ cmd_arr.append("-f")
+ cmd_arr.append(chart_status["override_file_name"])
+
+ print("cmd arr: "+str(cmd_arr))
+ result = subprocess.run(cmd_arr, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ print(str(result.stderr))
+ print(str(result.stdout))
+
+ res_str="Installed"
+ if upgrade == True:
+ res_str="Upgraded"
+ res_code=200
+ if result.returncode != 0:
+ res_str="Installation failed"
+ if upgrade == True:
+ res_str="Upgrade failed"
+ res_code=400
+ else:
+ with open(status_file, 'w') as outfile:
+ json.dump(chart_status, outfile)
+
+ return res_str,res_code
+
+ else:
+ if chart_status["install_state"]==NOTINSTALLED:
+ return "Chart not installed",400
+
+ cmd_arr=[]
+ cmd_arr.append("helm")
+ cmd_arr.append("uninstall")
+ cmd_arr.append("-n")
+ cmd_arr.append(chart_status["install_namespace"])
+ cmd_arr.append(chartname)
+
+ print("cmd arr: "+str(cmd_arr))
+ result = subprocess.run(cmd_arr, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ print(str(result.stderr))
+ print(str(result.stdout))
+
+ res_str="Uninstalled"
+ res_code=200
+ if result.returncode != 0:
+ res_str="Uninstallation failed"
+ res_code=400
+ else:
+ chart_status["install_state"]=NOTINSTALLED
+ chart_status["chart_file_status"]=NOTINSTALLED
+ if chart_status["override_file_name"]!=NONE:
+ chart_status["override_file_status"]=NOTINSTALLED
+ with open(status_file, 'w') as outfile:
+ json.dump(chart_status, outfile)
+
+ return res_str,res_code
+
+
+@app.route('/charts/<string:chartname>',
+ methods=['DELETE'])
+def delete_uninstall_chart(chartname):
+
+ print("put_install_chart Chartname: "+chartname)
+
+ chart_dir=CHART_REPO+"/"+chartname
+
+ if not os.path.isdir(chart_dir):
+ return "No chart with name: " + chartname, 404
+
+ status_file=chart_dir+"/"+STATUS_JSON
+ try:
+ with open(status_file, 'r') as infile:
+ chart_status=json.load(infile)
+ except Exception as err:
+ return "Internal error - "+str(err), 500
+
+ if chart_status["install_state"]!=NOTINSTALLED:
+ return "Cannot delete, chart: " + chartname + " is still installed", 400
+ try:
+ shutil.rmtree(chart_dir)
+ except OSError as e:
+ print("Error: %s : %s" % (chart_dir, e.strerror))
+ return "Delete chart failed", 500
+
+ return "OK",200
+
+
+@app.route('/charts/<string:chartname>/status',
+ methods=['GET'])
+def get_chart_status(chartname):
+
+ print("get_chart_status Chartname: "+chartname)
+
+
+ chart_dir=CHART_REPO+"/"+chartname
+
+ if not os.path.isdir(chart_dir):
+ return "No chart with name: " + chartname, 404
+
+ status_file=chart_dir+"/"+STATUS_JSON
+ try:
+ with open(status_file, 'r') as infile:
+ chart_status=json.load(infile)
+ return json.dumps(chart_status),200
+ except Exception as err:
+ return "Internal error - " + str(err), 500
+
+@app.route('/charts/<string:chartname>/chart',
+ methods=['GET'])
+def get_chart(chartname):
+
+ print("get_chart Chartname: "+chartname)
+
+ chart_dir=CHART_REPO+"/"+chartname
+
+ if not os.path.isdir(chart_dir):
+ return "No chart with name: " + chartname, 404
+
+ status_file=chart_dir+"/"+STATUS_JSON
+ try:
+ with open(status_file, 'r') as outfile:
+ chart_status=json.load(outfile)
+ except Exception as err:
+ return "Internal error - " + str(err), 500
+
+ if chart_status["chart_file_name"] == NONE:
+ return "File not found", 404
+ chart_file=chart_dir+"/"+chart_status["chart_file_name"]
+
+ try:
+ return send_file(chart_file, attachment_filename=chart_status["chart_file_name"])
+ except Exception as err:
+ return "Internal error - " + str(err), 500
+
+@app.route('/charts/<string:chartname>/override',
+ methods=['GET'])
+def get_override(chartname):
+
+ print("get_chart Chartname: "+chartname)
+
+ chart_dir=CHART_REPO+"/"+chartname
+
+ if not os.path.isdir(chart_dir):
+ return "No chart with name: " + chartname, 404
+
+ status_file=chart_dir+"/"+STATUS_JSON
+ try:
+ with open(status_file, 'r') as outfile:
+ chart_status=json.load(outfile)
+ except Exception as err:
+ return "Internal error - " + str(err), 500
+
+ if chart_status["override_file_name"] == NONE:
+ return "File not found", 404
+ ovr_file=chart_dir+"/"+chart_status["override_file_name"]
+
+ try:
+ return send_file(ovr_file, attachment_filename=chart_status["override_file_name"])
+ except Exception as err:
+ return "Internal error - " + str(err), 500
+
+
+### Main function ###
+
+if __name__ == "__main__":
+ app.run(port=HOST_PORT, host=HOST_IP)
diff --git a/python-rapp-manager/src/app/requirements.txt b/python-rapp-manager/src/app/requirements.txt
new file mode 100644
index 0000000..1dabaa8
--- /dev/null
+++ b/python-rapp-manager/src/app/requirements.txt
@@ -0,0 +1,3 @@
+Flask==1.1.1
+
+
diff --git a/python-rapp-manager/src/app/start.sh b/python-rapp-manager/src/app/start.sh
new file mode 100755
index 0000000..a176ef7
--- /dev/null
+++ b/python-rapp-manager/src/app/start.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# ============LICENSE_START===============================================
+# Copyright (C) 2020 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=================================================
+#
+
+kubectl create -f /usr/src/app/tiller-serviceaccount.yaml
+
+#start nginx
+nginx -c /usr/src/app/nginx.conf
+
+#start mrstub
+python3 -u rappman.py
diff --git a/python-rapp-manager/src/build-start.sh b/python-rapp-manager/src/build-start.sh
new file mode 100755
index 0000000..0d73420
--- /dev/null
+++ b/python-rapp-manager/src/build-start.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# ============LICENSE_START===============================================
+# Copyright (C) 2020 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=================================================
+#
+
+#Builds the callback receiver container and starts it in interactive mode
+
+docker build --build-arg NEXUS_PROXY_REPO=nexus3.onap.org:10001/ -t rappman .
+
+docker run --rm -it -p 9970:9970 -p 9971:9971 --name rappman rappman
diff --git a/python-rapp-manager/src/cert/cert.crt b/python-rapp-manager/src/cert/cert.crt
new file mode 100644
index 0000000..a24dfc4
--- /dev/null
+++ b/python-rapp-manager/src/cert/cert.crt
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICljCCAX4CCQCv7SV/aTc/YjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJT
+RTAeFw0yMDA1MDMwMDI0MzdaFw00NzA5MTgwMDI0MzdaMA0xCzAJBgNVBAYTAlNF
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApsGQcCv+Ce/+AbHx+3Wu
+ujGtWF7uLX+/MahOHPfdXqidwG7OpmYnGkL06cA52P0BcZdc1hPGQbQdFJC8aW6U
+5X9owRz9IRiwpzRhRqmMJfeqrLaqLL9K5MpCv+qsDzXu9ngRLJDk5CyeEfTjosEr
+GWDywWahQKHChamdH701djFGwWGP3gttGvQoMnaSpzeyDKitBZql6bSxKkhWgFop
+yxfU7qjbzOASLWaMx2r+MIJ88+AYDqYBTj649N534AYrIdjlQnvEKzGH0sOgHFYO
+oaTTvmE/vRPlmbSX1U7mo/SvMWNPZkKUPDltyapOpBltfMiRJH4ndLOXJWRgmYha
+SQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAdAwQpntpgUWUxCTk/Pw2+w5v+VxMM
+K6QWhm9JdRn3XKQnKrFexVRso/x8TA8V50EUGwQwbnKApNXvJsV2jvbP/YwDsG2u
+jBxs0DSspjDvbhUTkuWNYufQZIUGYMyccHap+CKD4rD2loMkmwbh5rII3SGEzUFE
+rOY4VhqDjGCcILbChiY/QMA6Uyb6jLGxTARhgblWi9RWr9LuKv7raaUcnAIz1GO8
+z559kUnOKbsB46RZKRa0uIumz9qqXqxnVLWnIwT3DinpXsnzcPqNyyhTk6XR+W5o
+0AuUCyT1WKlejrfMmmV6hRNHbT4x7cQrx4EjNf5hM00mN++F+QdGMa/G
+-----END CERTIFICATE-----
diff --git a/python-rapp-manager/src/cert/generate_cert_and_key.sh b/python-rapp-manager/src/cert/generate_cert_and_key.sh
new file mode 100755
index 0000000..0f79bbf
--- /dev/null
+++ b/python-rapp-manager/src/cert/generate_cert_and_key.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# ============LICENSE_START===============================================
+# Copyright (C) 2020 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=================================================
+#
+
+# This will generate a self-signed certificate with password 'test'
+openssl req -x509 -passout pass:"test" -newkey rsa:2048 -keyout key.crt -out cert.crt -days 9999
diff --git a/python-rapp-manager/src/cert/key.crt b/python-rapp-manager/src/cert/key.crt
new file mode 100644
index 0000000..105ee75
--- /dev/null
+++ b/python-rapp-manager/src/cert/key.crt
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIpz2Uxhl1+ZwCAggA
+MB0GCWCGSAFlAwQBKgQQu1or54X1Bk5IMPGoDrdxkASCBNCBKcePejHXlG0fb2qt
+TtQrpEr8UR60iFOaeUQ2Lc1zK0wzFCXAIXEWEcaozv75mJ5ReemkBMCyuzPJnoiM
+LTeKuoUw8l48S9arB9l+/vVgUnMY0fm+QDsnPffkXKxC2kNwwFgGCT7tIGezuo/e
+a9a5JJY707YEnkhUKWAQI2Oz/I95tbeYu64d/WtSN2OLu5JVLsCGAhV4cqcShjEb
+pFlfgOHrT0z+qK7YXVR9P74qAZtGsH2ydUrtPtdvddKRpOAm4LzDNmox4Bs6e9nr
+jY56sVRiHGhqeeqW04qRks5ReZF7zuwEgUSzGNlAcbbHn6FNJPOZKuN0e8KYexEM
+y0G04rSNW8qppMsvez6txsou62CeIZ5LyAumwaJJYzwkob0nCmWYcZl5tSpkXZly
+HsQKI2UlO3tiRKd057a46/kxcK85Pwav3Il+FaRXJkzl2rkU3DSy9SjaGL0ROD0U
+1EaZCjeDdzN2GmqRQ1WhN5ivowQyWVf6H/mrxtkWZ3qLKmpa1JmvUgOybPcbqqQr
+tqjj3Oj0zvLFZDqBjfIlTAAimXPgh6qLHH+qUGrI62pMpaldNZNy/swnpPuTX2sF
+TUxFZvnGOmG3qHyvPm91+PypbdVSMb0PeB75XQFqWmajwnua7xfWrH8PLSijp5xQ
+aLyiJ1jjFqXWE9D2v7JhB2BNCYlHxP98UI8kHxh7Fw5y0EKT5pCcbrg2nuLzMrCz
+D4QaxZRuiIiPgy21kowk3WbHLYAjG7f9cIcbbX6Khc/3ulbB8xJ24WNRuzv4EHeh
+TATHqk8nIgpkn1zmvPsKILdWzqZh70IlSctSzoIGzI6C2J76ycSZmcKtar2BZya9
+f1coUlFgXMvdmrf4bt4j2u/biA48OJaVlWBYVfIXUbliFTAQ8biRZFC2n3Xg+W8t
+U2xqW14lZWBOIQFJp27foG6Z4JzyL2WZgQ0PWe0m0+tDaKA/LSWB2Qpwt4o2n0cb
+RCs++c0eFCeOgErEfmmeburMhzQsfkUqpsL+J/ZMaRSiuTCpYM8qbz+KKT/Z6zbl
+2cHWxSFRIqRKAMsj2a61IANjNIdwi2uBHZrWH1HMVVXAbGUJQFKZhxdpn5PBrXqg
+vHRa9u0MQFCjs9NcQAGnBQDS6u+pUVO02WT4MvTker+hbu+f6NPU9FMLu+QbQUEP
+SUdEZL4W9ZuBTdS3n/fTHEL8wKRB5yEW/CS5JuD+8YinZZXrsd3n3Oky05fdk6Bk
+QH9cjMXdsd0Sb0Epw3CWGtXZ6YTHlVWqjdTNlOQdzQ7qfzktgcKujGwvQK0Mgd8x
+nmG+f/HWMOss0JEL3ZR+K9Rr50u8/R+W5+e4VE57yw1fg9Jpq2/sVe2Pt8S7isFK
+qDLoFZtF5RXi1O9KcA9BpnQX1ihPSC1RoY1pGXoF2D4KkV9U4/4j2qM6MGxjQ6lw
+MN0qJ/N70Lti3YWqvYiTymLwVJr8FqoMQsV19MB8012Xd51Bvy6igddhrO83wuuV
+b8PlUzl3Tl7yOviYqxiJ0xd8qw+Hs4+FkHbZIFJcUzTHVbb4SlPUE3wn6nrrIcfK
+rT4wsYhK3afrlvK3ILi6kzzazS1dK+Hv9+mNozNf5u5nNBFQ+7MhtttzLWIaiV6D
+ilLpOwcoO0X0qrzXKR7a+rQ/Dw==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/python-rapp-manager/src/cert/pass b/python-rapp-manager/src/cert/pass
new file mode 100644
index 0000000..30d74d2
--- /dev/null
+++ b/python-rapp-manager/src/cert/pass
@@ -0,0 +1 @@
+test
\ No newline at end of file
diff --git a/python-rapp-manager/src/svc-app.yaml b/python-rapp-manager/src/svc-app.yaml
new file mode 100644
index 0000000..24758a7
--- /dev/null
+++ b/python-rapp-manager/src/svc-app.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: rapp-man
+ namespace: nonrtric
+ labels:
+ run: rapp-man
+spec:
+ type: NodePort
+ ports:
+ - port: 9970
+ targetPort: 9970
+ protocol: TCP
+ name: http
+ nodePort:
+ - port: 9971
+ targetPort: 9971
+ protocol: TCP
+ name: https
+ nodePort:
+ selector:
+ run: rapp-man
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ name: rapp-man
+ namespace: nonrtric
+ labels:
+ run: rapp-man
+spec:
+ volumes:
+ - name: shared-data
+ emptyDir: {}
+ containers:
+ - name: rapp-man
+ image: rappman:latest
+ imagePullPolicy: Never
+ ports:
+ - name: http
+ containerPort: 9970
+ - name: https
+ containerPort: 9971
+ volumeMounts:
+ - name: shared-data
+ mountPath: /usr/share/nginx/html
+ dnsPolicy: Default
\ No newline at end of file
diff --git a/python-rapp-manager/src/tiller-serviceaccount.yaml b/python-rapp-manager/src/tiller-serviceaccount.yaml
new file mode 100644
index 0000000..798f217
--- /dev/null
+++ b/python-rapp-manager/src/tiller-serviceaccount.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: tiller
+ namespace: kube-system
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1beta1
+metadata:
+ name: tiller-clusterrolebinding
+subjects:
+- kind: ServiceAccount
+ name: tiller
+ namespace: kube-system
+roleRef:
+ kind: ClusterRole
+ name: cluster-admin
+ apiGroup: ""
\ No newline at end of file