Swagger-baser server REST API interface

Change-Id: Ic2057a93bd9dfbb19c3b00cc497a4243120c6662
Signed-off-by: Mohamed Abukar <abukar.mohamed@nokia.com>
diff --git a/Dockerfile b/Dockerfile
old mode 100644
new mode 100755
index adc6a04..027e372
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,23 +32,33 @@
 # Install kubectl from Docker Hub
 COPY --from=lachlanevenson/k8s-kubectl:v1.10.3 /usr/local/bin/kubectl /usr/local/bin/kubectl
 
-RUN mkdir -p /ws
-WORKDIR "/ws"
 ENV GOPATH="/go"
 
+# Swagger
+RUN mkdir -p /go/bin
+RUN cd /go/bin \
+    && wget --quiet https://github.com/go-swagger/go-swagger/releases/download/v0.19.0/swagger_linux_amd64 \
+    && mv swagger_linux_amd64 swagger \
+    && chmod +x swagger
+
+RUN mkdir -p /go/src/ws
+WORKDIR "/go/src/ws"
+
 # Module prepare (if go.mod/go.sum updated)
-COPY go.mod /ws
-COPY go.sum /ws
+COPY go.mod /go/src/ws
+COPY go.sum /go/src/ws
 RUN GO111MODULE=on go mod download
 
 # build and test
-COPY . /ws
+COPY . /go/src/ws
 
-RUN make -C /ws go-build
+RUN /go/bin/swagger generate server -f api/appmgr_rest_api.yaml --name AppManager -t pkg/ --exclude-main
 
-RUN make -C /ws go-test-fmt
+COPY . /go/src/ws
 
-#RUN make -C /ws go-test
+RUN make -C /go/src/ws go-build
+
+RUN make -C /go/src/ws go-test-fmt
 
 CMD ["/bin/bash"]
 
@@ -70,12 +80,12 @@
 RUN ldconfig
 
 #
-# xApp
+# xApp Manager
 #
 RUN mkdir -p /opt/xAppManager \
     && chmod -R 755 /opt/xAppManager
 
-COPY --from=appmgr-build /ws/cache/go/cmd/appmgr /opt/xAppManager/appmgr
+COPY --from=appmgr-build /go/src/ws/cache/go/cmd/appmgr /opt/xAppManager/appmgr
 
 WORKDIR /opt/xAppManager
 
diff --git a/api/appmgr_rest_api.json b/api/appmgr_rest_api.json
deleted file mode 100644
index 2b08195..0000000
--- a/api/appmgr_rest_api.json
+++ /dev/null
@@ -1,840 +0,0 @@
-{
-  "swagger": "2.0",
-  "info": {
-    "description": "This is a draft API for RIC appmgr",
-    "version": "0.1.7",
-    "title": "RIC appmgr",
-    "license": {
-      "name": "Apache 2.0",
-      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
-    }
-  },
-  "host": "hostname",
-  "basePath": "/ric/v1",
-  "schemes": [
-    "http"
-  ],
-  "paths": {
-    "/health/alive": {
-      "get": {
-        "summary": "Health check of xApp Manager - Liveness probe",
-        "tags": [
-          "health"
-        ],
-        "operationId": "getHealthAlive",
-        "responses": {
-          "200": {
-            "description": "Status of xApp Manager is ok"
-          }
-        }
-      }
-    },
-    "/health/ready": {
-      "get": {
-        "summary": "Readiness check of xApp Manager - Readiness probe",
-        "tags": [
-          "health"
-        ],
-        "operationId": "getHealthReady",
-        "responses": {
-          "200": {
-            "description": "xApp Manager is ready for service"
-          },
-          "503": {
-            "description": "xApp Manager is not ready for service"
-          }
-        }
-      }
-    },
-    "/xapps": {
-      "post": {
-        "summary": "Deploy a xapp",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "deployXapp",
-        "consumes": [
-          "application/json"
-        ],
-        "produces": [
-          "application/json"
-        ],
-        "parameters": [
-          {
-            "name": "xAppInfo",
-            "in": "body",
-            "description": "xApp information",
-            "schema": {
-              "type": "object",
-              "required": [
-                "name"
-              ],
-              "properties": {
-                "name": {
-                  "type": "string",
-                  "description": "Name of the xApp.",
-                  "example": "xapp-dummy"
-                },
-                "configName": {
-                  "type": "string",
-                  "description": "Name of the xApp configmap. Overrides the value given in Helm chart value file.",
-                  "example": "xapp-dummy-configmap"
-                },
-                "namespace": {
-                  "type": "string",
-                  "description": "Name of the namespace to which xApp is deployed. Overrides the value given in Helm chart value file.",
-                  "example": "ricxapps"
-                },
-                "serviceName": {
-                  "type": "string",
-                  "description": "Name of the service xApp is providing. Overrides the value given in Helm chart value file.",
-                  "example": "xapp-dummy-service"
-                },
-                "imageRepo": {
-                  "type": "string",
-                  "description": "Name of the docker repository xApp is located. Overrides the value given in Helm chart value file.",
-                  "example": "xapprepo"
-                },
-                "hostname": {
-                  "type": "string",
-                  "description": "Hostname for the pod. Used by messaging library. Overrides the value given in Helm chart value file.",
-                  "example": "xapp-dummy"
-                }
-              }
-            }
-          }
-        ],
-        "responses": {
-          "201": {
-            "description": "xApp successfully created",
-            "schema": {
-              "$ref": "#/definitions/Xapp"
-            }
-          },
-          "400": {
-            "description": "Invalid input"
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      },
-      "get": {
-        "summary": "Returns the status of all xapps",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "getAllXapps",
-        "produces": [
-          "application/json"
-        ],
-        "responses": {
-          "200": {
-            "description": "successful query of xApps",
-            "schema": {
-              "$ref": "#/definitions/AllDeployedXapps"
-            }
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      }
-    },
-    "/xapps/search": {
-        "get": {
-        "summary": "Returns the list of all deployable xapps",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "listAllDeployableXapps",
-        "produces": [
-          "application/json"
-        ],
-        "responses": {
-          "200": {
-            "description": "successful list of deployable xApps",
-            "schema": {
-              "$ref": "#/definitions/AllDeployableXapps"
-            }
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      }
-    },
-    "/xapps/{xAppName}": {
-      "get": {
-        "summary": "Returns the status of a given xapp",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "getXappByName",
-        "produces": [
-          "application/json"
-        ],
-        "parameters": [
-          {
-            "name": "xAppName",
-            "in": "path",
-            "description": "Name of xApp",
-            "required": true,
-            "type": "string"
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "successful operation",
-            "schema": {
-              "$ref": "#/definitions/Xapp"
-            }
-          },
-          "400": {
-            "description": "Invalid ID supplied"
-          },
-          "404": {
-            "description": "Xapp not found"
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      },
-      "delete": {
-        "summary": "Undeploy an existing xapp",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "undeployXapp",
-        "parameters": [
-          {
-            "name": "xAppName",
-            "in": "path",
-            "description": "Xapp to be undeployed",
-            "required": true,
-            "type": "string"
-          }
-        ],
-        "responses": {
-          "204": {
-            "description": "Successful deletion of xApp"
-          },
-          "400": {
-            "description": "Invalid xApp name supplied"
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      }
-    },
-    "/xapps/{xAppName}/instances/{xAppInstanceName}": {
-      "get": {
-        "summary": "Returns the status of a given xapp",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "getXappInstanceByName",
-        "produces": [
-          "application/json"
-        ],
-        "parameters": [
-          {
-            "name": "xAppName",
-            "in": "path",
-            "description": "Name of xApp",
-            "required": true,
-            "type": "string"
-          },
-          {
-            "name": "xAppInstanceName",
-            "in": "path",
-            "description": "Name of xApp instance to get information",
-            "required": true,
-            "type": "string"
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "successful operation",
-            "schema": {
-              "$ref": "#/definitions/XappInstance"
-            }
-          },
-          "400": {
-            "description": "Invalid name supplied"
-          },
-          "404": {
-            "description": "Xapp not found"
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      }
-    },
-    "/config": {
-      "post": {
-        "summary": "Create xApp config",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "createXappConfig",
-        "consumes": [
-          "application/json"
-        ],
-        "produces": [
-          "application/json"
-        ],
-        "parameters": [
-          {
-            "name": "XAppConfig",
-            "in": "body",
-            "description": "xApp config",
-            "schema": {
-              "$ref": "#/definitions/XAppConfig"
-            }
-          }
-        ],
-        "responses": {
-          "201": {
-            "description": "xApp config successfully created",
-            "schema": {
-              "$ref": "#/definitions/XAppConfig"
-            }
-          },
-          "400": {
-            "description": "Invalid input"
-          },
-          "422": {
-            "description": "Validation of configuration failed"
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      },
-      "put": {
-        "summary": "Modify xApp config",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "ModifyXappConfig",
-        "consumes": [
-          "application/json"
-        ],
-        "produces": [
-          "application/json"
-        ],
-        "parameters": [
-          {
-            "name": "XAppConfig",
-            "in": "body",
-            "description": "xApp config",
-            "schema": {
-              "$ref": "#/definitions/XAppConfig"
-            }
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "xApp config successfully modified",
-            "schema": {
-              "$ref": "#/definitions/XAppConfig"
-            }
-          },
-          "400": {
-            "description": "Invalid input"
-          },
-          "422": {
-            "description": "Validation of configuration failed"
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      },
-      "get": {
-        "summary": "Returns the configuration of all xapps",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "getAllXappConfig",
-        "produces": [
-          "application/json"
-        ],
-        "responses": {
-          "200": {
-            "description": "successful query of xApp config",
-            "schema": {
-              "$ref": "#/definitions/AllXappConfig"
-            }
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      },
-      "delete": {
-        "summary": "Delete xApp configuration",
-        "tags": [
-          "xapp"
-        ],
-        "operationId": "deleteXappConfig",
-        "parameters": [
-          {
-            "name": "ConfigMetadata",
-            "in": "body",
-            "description": "xApp configuration information",
-            "schema": {
-              "$ref": "#/definitions/ConfigMetadata"
-            }
-          }
-        ],
-        "responses": {
-          "204": {
-            "description": "Successful deletion of xApp config"
-          },
-          "400": {
-            "description": "Invalid parameters supplied"
-          },
-          "500": {
-            "description": "Internal error"
-          }
-        }
-      }
-    },
-    "/subscriptions": {
-      "post": {
-        "summary": "Subscribe event",
-        "tags": [
-          "xapp",
-          "subscriptions"
-        ],
-        "operationId": "addSubscription",
-        "consumes": [
-          "application/json"
-        ],
-        "produces": [
-          "application/json"
-        ],
-        "parameters": [
-          {
-            "name": "subscriptionRequest",
-            "in": "body",
-            "description": "New subscription",
-            "required": true,
-            "schema": {
-              "$ref": "#/definitions/subscriptionRequest"
-            }
-          }
-        ],
-        "responses": {
-          "201": {
-            "description": "Subscription successfully created",
-            "schema": {
-              "$ref": "#/definitions/subscriptionResponse"
-            }
-          },
-          "400": {
-            "description": "Invalid input"
-          }
-        }
-      },
-      "get": {
-        "summary": "Returns all subscriptions",
-        "tags": [
-          "xapp",
-          "subscriptions"
-        ],
-        "operationId": "getSubscriptions",
-        "produces": [
-          "application/json"
-        ],
-        "responses": {
-          "200": {
-            "description": "successful query of subscriptions",
-            "schema": {
-              "$ref": "#/definitions/allSubscriptions"
-            }
-          }
-        }
-      }
-    },
-    "/subscriptions/{subscriptionId}": {
-      "get": {
-        "summary": "Returns the information of subscription",
-        "tags": [
-          "xapp",
-          "subscriptions"
-        ],
-        "operationId": "getSubscriptionById",
-        "produces": [
-          "application/json"
-        ],
-        "parameters": [
-          {
-            "name": "subscriptionId",
-            "in": "path",
-            "description": "ID of subscription",
-            "required": true,
-            "type": "string"
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "successful operation",
-            "schema": {
-              "$ref": "#/definitions/subscription"
-            }
-          },
-          "400": {
-            "description": "Invalid ID supplied"
-          },
-          "404": {
-            "description": "Subscription not found"
-          }
-        }
-      },
-      "put": {
-        "summary": "Modify event subscription",
-        "tags": [
-          "xapp",
-          "subscriptions"
-        ],
-        "operationId": "modifySubscription",
-        "consumes": [
-          "application/json"
-        ],
-        "produces": [
-          "application/json"
-        ],
-        "parameters": [
-          {
-            "name": "subscriptionId",
-            "in": "path",
-            "description": "ID of subscription",
-            "required": true,
-            "type": "string"
-          },
-          {
-            "in": "body",
-            "name": "subscriptionRequest",
-            "description": "Modified subscription",
-            "required": true,
-            "schema": {
-              "$ref": "#/definitions/subscriptionRequest"
-            }
-          }
-        ],
-        "responses": {
-          "200": {
-            "description": "Subscription modification successful",
-            "schema": {
-              "$ref": "#/definitions/subscriptionResponse"
-            }
-          },
-          "400": {
-            "description": "Invalid input"
-          }
-        }
-      },
-      "delete": {
-        "summary": "Unsubscribe event",
-        "tags": [
-          "xapp",
-          "subscriptions"
-        ],
-        "description": "",
-        "operationId": "deleteSubscription",
-        "parameters": [
-          {
-            "name": "subscriptionId",
-            "in": "path",
-            "description": "ID of subscription",
-            "required": true,
-            "type": "string"
-          }
-        ],
-        "responses": {
-          "204": {
-            "description": "Successful deletion of subscription"
-          },
-          "400": {
-            "description": "Invalid subscription supplied"
-          }
-        }
-      }
-    }
-  },
-  "definitions": {
-    "AllDeployableXapps": {
-      "type": "array",
-      "items": {
-        "type": "string",
-        "example": "xapp-dummy"
-      }
-    },
-    "AllDeployedXapps": {
-      "type": "array",
-      "items": {
-        "$ref": "#/definitions/Xapp"
-      }
-    },
-    "Xapp": {
-      "type": "object",
-      "required": [
-        "name"
-      ],
-      "properties": {
-        "name": {
-          "type": "string",
-          "example": "xapp-dummy"
-        },
-        "status": {
-          "type": "string",
-          "description": "xapp status in the RIC",
-          "enum": [
-            "unknown",
-            "deployed",
-            "deleted",
-            "superseded",
-            "failed",
-            "deleting"
-          ]
-        },
-        "version": {
-          "type": "string",
-          "example": "1.2.3"
-        },
-        "instances": {
-          "type": "array",
-          "items": {
-            "$ref": "#/definitions/XappInstance"
-          }
-        }
-      }
-    },
-    "XappInstance": {
-      "type": "object",
-      "required": [
-        "name"
-      ],
-      "properties": {
-        "name": {
-          "type": "string",
-          "example": "xapp-dummy-6cd577d9-4v255"
-        },
-        "status": {
-          "type": "string",
-          "description": "xapp instance status",
-          "enum": [
-            "pending",
-            "running",
-            "succeeded",
-            "failed",
-            "unknown",
-            "completed",
-            "crashLoopBackOff"
-          ]
-        },
-        "ip": {
-          "type": "string",
-          "example": "192.168.0.1"
-        },
-        "port": {
-          "type": "integer",
-          "example": 32300
-        },
-        "txMessages": {
-          "type": "array",
-          "items": {
-            "type": "string",
-            "example": "ControlIndication"
-          }
-        },
-        "rxMessages": {
-          "type": "array",
-          "items": {
-            "type": "string",
-            "example": "LoadIndication"
-          }
-        }
-      }
-    },
-    "ConfigMetadata": {
-      "type": "object",
-      "required": [
-        "name",
-        "configName",
-        "namespace"
-      ],
-      "properties": {
-        "name": {
-          "type": "string",
-          "description": "Name of the xApp",
-          "example": "xapp-dummy"
-        },
-        "configName": {
-          "type": "string",
-          "description": "Name of the config map",
-          "example": "xapp-dummy-config-map"
-        },
-        "namespace": {
-          "type": "string",
-          "description": "Name of the namespace",
-          "example": "ricxapp"
-        }
-      }
-    },
-    "XAppConfig": {
-      "type": "object",
-      "required": [
-        "metadata",
-        "descriptor",
-        "config"
-      ],
-      "properties": {
-        "metadata": {
-          "$ref": "#/definitions/ConfigMetadata"
-        },
-        "descriptor": {
-          "type": "object",
-          "description": "Schema of configuration in JSON format"
-        },
-        "config": {
-          "type": "object",
-          "description": "Configuration in JSON format"
-        }
-      }
-    },
-    "AllXappConfig": {
-      "type": "array",
-      "items": {
-        "$ref": "#/definitions/XAppConfig"
-      }
-    },
-    "subscriptionRequest": {
-      "type": "object",
-      "required": [
-        "targetUrl",
-        "eventType",
-        "maxRetries",
-        "retryTimer"
-      ],
-      "properties": {
-        "targetUrl": {
-          "type": "string",
-          "example": "http://localhost:11111/apps/webhook/"
-        },
-        "eventType": {
-          "type": "string",
-          "description": "Event which is subscribed",
-          "enum": [
-            "created",
-            "deleted",
-            "all"
-          ]
-        },
-        "maxRetries": {
-          "type": "integer",
-          "description": "Maximum number of retries",
-          "example": 11
-        },
-        "retryTimer": {
-          "type": "integer",
-          "description": "Time in seconds to wait before next retry",
-          "example": 22
-        }
-      }
-    },
-    "subscriptionResponse": {
-      "type": "object",
-      "properties": {
-        "id": {
-          "type": "string",
-          "example": "1ILBltYYzEGzWRrVPZKmuUmhwcc"
-        },
-        "version": {
-          "type": "integer",
-          "example": 2
-        },
-        "eventType": {
-          "type": "string",
-          "description": "Event which is subscribed",
-          "enum": [
-            "created",
-            "deleted",
-            "updated",
-            "all"
-          ]
-        }
-      }
-    },
-    "allSubscriptions": {
-      "type": "array",
-      "items": {
-        "$ref": "#/definitions/subscription"
-      }
-    },
-    "subscription": {
-      "type": "object",
-      "properties": {
-        "id": {
-          "type": "string",
-          "example": "1ILBltYYzEGzWRrVPZKmuUmhwcc"
-        },
-        "targetUrl": {
-          "type": "string",
-          "example": "http://localhost:11111/apps/webhook/"
-        },
-        "eventType": {
-          "type": "string",
-          "description": "Event which is subscribed",
-          "enum": [
-            "created",
-            "deleted",
-            "updated",
-            "all"
-          ]
-        },
-        "maxRetries": {
-          "type": "integer",
-          "description": "Maximum number of retries",
-          "example": 11
-        },
-        "retryTimer": {
-          "type": "integer",
-          "description": "Time in seconds to wait before next retry",
-          "example": 22
-        }
-      }
-    },
-    "subscriptionNotification": {
-      "type": "object",
-      "properties": {
-        "id": {
-          "type": "string",
-          "example": "1ILBltYYzEGzWRrVPZKmuUmhwcc"
-        },
-        "version": {
-          "type": "integer",
-          "example": 2
-        },
-        "eventType": {
-          "type": "string",
-          "description": "Event to be notified",
-          "enum": [
-            "created",
-            "deleted",
-            "updated"
-          ]
-        },
-        "xApps": {
-          "$ref": "#/definitions/AllDeployedXapps"
-        }
-      }
-    }
-  }
-}
diff --git a/api/appmgr_rest_api.yaml b/api/appmgr_rest_api.yaml
index 9e7c15b..04f4f0a 100644
--- a/api/appmgr_rest_api.yaml
+++ b/api/appmgr_rest_api.yaml
@@ -1,7 +1,7 @@
 swagger: '2.0'
 info:
   description: This is a draft API for RIC appmgr
-  version: 0.1.7
+  version: 0.2.0
   title: RIC appmgr
   license:
     name: Apache 2.0
@@ -42,38 +42,11 @@
       produces:
         - application/json
       parameters:
-        - name: xAppInfo
+        - name: XappDescriptor
           in: body
-          description: xApp information
+          description: xApp deployment info
           schema:
-            type: object
-            required:
-              - name
-            properties:
-              name:
-                type: string
-                description: Name of the xApp.
-                example: xapp-dummy
-              configName:
-                type: string
-                description: Name of the xApp configmap. Overrides the value given in Helm chart value file.
-                example: xapp-dummy-configmap
-              namespace:
-                type: string
-                description: Name of the namespace to which xApp is deployed. Overrides the value given in Helm chart value file.
-                example: ricxapps
-              serviceName:
-                type: string
-                description: Name of the service xApp is providing. Overrides the value given in Helm chart value file.
-                example: xapp-dummy-service
-              imageRepo:
-                type: string
-                description: Name of the docker repository xApp is located. Overrides the value given in Helm chart value file.
-                example: xapprepo
-              hostname:
-                type: string
-                description: Hostname for the pod. Used by messaging library. Overrides the value given in Helm chart value file.
-                example: xapp-dummy
+            $ref: '#/definitions/XappDescriptor'
       responses:
         '201':
           description: xApp successfully created
@@ -97,12 +70,12 @@
             $ref: '#/definitions/AllDeployedXapps'
         '500':
           description: Internal error
-  '/xapps/search':
+  /xapps/list:
     get:
       summary: Returns the list of all deployable xapps
       tags:
         - xapp
-      operationId: listAllDeployableXapps
+      operationId: listAllXapps
       produces:
         - application/json
       responses:
@@ -112,7 +85,7 @@
             $ref: '#/definitions/AllDeployableXapps'
         '500':
           description: Internal error
-  '/xapps/{xAppName}':
+  /xapps/{xAppName}:
     get:
       summary: Returns the status of a given xapp
       tags:
@@ -155,7 +128,7 @@
           description: Invalid xApp name supplied
         '500':
           description: Internal error
-  '/xapps/{xAppName}/instances/{xAppInstanceName}':
+  /xapps/{xAppName}/instances/{xAppInstanceName}:
     get:
       summary: Returns the status of a given xapp
       tags:
@@ -185,6 +158,62 @@
           description: Xapp not found
         '500':
           description: Internal error
+  /xapps/{xAppName}/instances/{xAppInstanceName}/start:
+    put:
+      summary: Start given xapp instance
+      tags:
+        - xapp
+      operationId: startXappInstanceByName
+      produces:
+        - application/json
+      parameters:
+        - name: xAppName
+          in: path
+          description: Name of xApp
+          required: true
+          type: string
+        - name: xAppInstanceName
+          in: path
+          description: Name of xApp instance to get information
+          required: true
+          type: string
+      responses:
+        '200':
+          description: successful operation
+        '400':
+          description: Invalid name supplied
+        '404':
+          description: Xapp not found
+        '500':
+          description: Internal error
+  /xapps/{xAppName}/instances/{xAppInstanceName}/stop:
+    put:
+      summary: Stop given xapp instance
+      tags:
+        - xapp
+      operationId: stopXappInstanceByName
+      produces:
+        - application/json
+      parameters:
+        - name: xAppName
+          in: path
+          description: Name of xApp
+          required: true
+          type: string
+        - name: xAppInstanceName
+          in: path
+          description: Name of xApp instance to get information
+          required: true
+          type: string
+      responses:
+        '200':
+          description: successful operation
+        '400':
+          description: Invalid name supplied
+        '404':
+          description: Xapp not found
+        '500':
+          description: Internal error
   /config:
     post:
       summary: Create xApp config
@@ -205,7 +234,7 @@
         '201':
           description: xApp config successfully created
           schema:
-            $ref: '#/definitions/XAppConfig'
+            $ref: '#/definitions/ConfigValidationErrors'
         '400':
           description: Invalid input
         '422':
@@ -231,7 +260,7 @@
         '200':
           description: xApp config successfully modified
           schema:
-            $ref: '#/definitions/XAppConfig'
+            $ref: '#/definitions/ConfigValidationErrors'
         '400':
           description: Invalid input
         '422':
@@ -270,6 +299,27 @@
           description: Invalid parameters supplied
         '500':
           description: Internal error
+  /config/{configName}:
+    get:
+      summary: Returns the configuration of a single xapp
+      tags:
+        - xapp
+      operationId: getXappConfig
+      produces:
+        - application/json
+      parameters:
+        - name: configName
+          in: path
+          description: Name of xApp
+          required: true
+          type: string
+      responses:
+        '200':
+          description: successful query of xApp config
+          schema:
+            $ref: '#/definitions/XAppConfig'
+        '500':
+          description: Internal error
   /subscriptions:
     post:
       summary: Subscribe event
@@ -308,7 +358,7 @@
           description: successful query of subscriptions
           schema:
             $ref: '#/definitions/allSubscriptions'
-  '/subscriptions/{subscriptionId}':
+  /subscriptions/{subscriptionId}:
     get:
       summary: Returns the information of subscription
       tags:
@@ -384,7 +434,6 @@
     type: array
     items:
       type: string
-      example: "xapp-dummy"
   AllDeployedXapps:
     type: array
     items:
@@ -396,7 +445,6 @@
     properties:
       name:
         type: string
-        example: xapp-dummy
       status:
         type: string
         description: xapp status in the RIC
@@ -409,7 +457,6 @@
           - deleting
       version:
         type: string
-        example: 1.2.3
       instances:
         type: array
         items:
@@ -421,7 +468,6 @@
     properties:
       name:
         type: string
-        example: xapp-dummy-6cd577d9-4v255
       status:
         type: string
         description: xapp instance status
@@ -435,39 +481,70 @@
           - crashLoopBackOff
       ip:
         type: string
-        example: 192.168.0.1
       port:
         type: integer
-        example: 32300
       txMessages:
         type: array
         items:
           type: string
-          example: ControlIndication
       rxMessages:
         type: array
         items:
           type: string
-          example: LoadIndication
+  XappDescriptor:
+    type: object
+    required:
+      - xappName
+    properties:
+      xappName:
+        type: string
+        description: Name of the xApp in helm chart
+      helmVersion:
+        type: string
+        description: The exact xapp helm chart version to install
+      releaseName:
+        type: string
+        description: Name of the xapp to be visible in Kubernetes
+      namespace:
+        type: string
+        description: Name of the namespace to which xApp is deployed. Overrides the value given in Helm chart value file.
+      overrideFile:
+        type: object
+        description: JSON string of override file for 'helm install' command
+  XappDescriptorList:
+    type: array
+    items:
+      $ref: '#/definitions/XappDescriptor'
   ConfigMetadata:
     type: object
     required:
       - name
-      - configName
-      - namespace
     properties:
       name:
         type: string
         description: Name of the xApp
-        example: xapp-dummy
       configName:
         type: string
         description: Name of the config map
-        example: xapp-dummy-config-map
       namespace:
         type: string
         description: Name of the namespace
-        example: ricxapp
+  ConfigValidationError:
+    type: object
+    required:
+      - field
+      - error
+    properties:
+      field:
+        type: string
+        description: Name of the parameter
+      error:
+        type: string
+        description: Description of validation error
+  ConfigValidationErrors:
+    type: array
+    items:
+      $ref: '#/definitions/ConfigValidationError'
   XAppConfig:
     type: object
     required:
@@ -487,7 +564,18 @@
     type: array
     items:
       $ref: '#/definitions/XAppConfig'
-  subscriptionRequest:
+  EventType:
+    type: string
+    description: Event which is subscribed
+    enum:
+      - deployed
+      - undeployed
+      - created
+      - modified
+      - deleted
+      - restarted
+      - all
+  SubscriptionData:
     type: object
     required:
       - targetUrl
@@ -499,37 +587,29 @@
         type: string
         example: 'http://localhost:11111/apps/webhook/'
       eventType:
-        type: string
-        description: Event which is subscribed
-        enum:
-          - created
-          - deleted
-          - all
+        $ref: '#/definitions/EventType'
       maxRetries:
         type: integer
         description: Maximum number of retries
-        example: 11
       retryTimer:
         type: integer
         description: Time in seconds to wait before next retry
-        example: 22
+  subscriptionRequest:
+    type: object
+    required:
+      - data
+    properties:
+      data:
+        $ref: '#/definitions/SubscriptionData'
   subscriptionResponse:
     type: object
     properties:
       id:
         type: string
-        example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
       version:
         type: integer
-        example: 2
       eventType:
-        type: string
-        description: Event which is subscribed
-        enum:
-          - created
-          - deleted
-          - updated
-          - all
+        $ref: '#/definitions/EventType'
   allSubscriptions:
     type: array
     items:
@@ -539,41 +619,16 @@
     properties:
       id:
         type: string
-        example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
-      targetUrl:
-        type: string
-        example: 'http://localhost:11111/apps/webhook/'
-      eventType:
-        type: string
-        description: Event which is subscribed
-        enum:
-          - created
-          - deleted
-          - updated
-          - all
-      maxRetries:
-        type: integer
-        description: Maximum number of retries
-        example: 11
-      retryTimer:
-        type: integer
-        description: Time in seconds to wait before next retry
-        example: 22
+      data:
+        $ref: '#/definitions/SubscriptionData'
   subscriptionNotification:
     type: object
     properties:
       id:
         type: string
-        example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
       version:
         type: integer
-        example: 2
       eventType:
-        type: string
-        description: Event to be notified
-        enum:
-          - created
-          - deleted
-          - updated
+        $ref: '#/definitions/EventType'
       xApps:
         $ref: '#/definitions/AllDeployedXapps'
diff --git a/build/make.go.mk b/build/make.go.mk
old mode 100644
new mode 100755
index 286d9b1..2208487
--- a/build/make.go.mk
+++ b/build/make.go.mk
@@ -58,13 +58,13 @@
 .SECONDEXPANSION:
 $(GO_CACHE_DIR)/%: $(GOFILES) $(GOMODFILES) $$(BUILDDEPS)
 	@echo "Building:\t$*"
-	GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOBUILD) -o $@ ./$*
+	GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOBUILD) -o $@ cmd/appmgr.go
 
 
 .SECONDEXPANSION:
 $(GO_CACHE_DIR)/%_test: $(GOALLFILES) $(GOMODFILES) $$(BUILDDEPS) FORCE
 	@echo "Testing:\t$*"
-	GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOTEST) -coverprofile $(COVEROUT) -c -o $@ ./$*
+	GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOTEST) -coverprofile $(COVEROUT) ./pkg/resthooks/ ./pkg/helm/ ./pkg/cm/
 	test -e $@ && (eval $(TESTENV) $@ -test.coverprofile $(COVEROUT) || false) || true
 	test -e $@ && (go tool cover -html=$(COVEROUT) -o $(COVERHTML) || false) || true
 
diff --git a/cmd/appmgr/main.go b/cmd/appmgr.go
similarity index 82%
rename from cmd/appmgr/main.go
rename to cmd/appmgr.go
index 8788405..b658367 100755
--- a/cmd/appmgr/main.go
+++ b/cmd/appmgr.go
@@ -19,15 +19,12 @@
 
 package main
 
-var Logger *Log
+import (
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restful"
+)
 
 func main() {
-	Logger = NewLogger("xapp-manager")
-	Logger.SetMdc("appmgr", "0.1.9")
-	loadConfig()
-
-	m := XappManager{}
-	m.Initialize(&Helm{}, &ConfigMap{})
-
-	m.Run()
+	appmgr.Init()
+	restful.NewRestful().Run()
 }
diff --git a/cmd/appmgr/api_test.go b/cmd/appmgr/api_test.go
deleted file mode 100755
index 248ffee..0000000
--- a/cmd/appmgr/api_test.go
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"bytes"
-	"encoding/json"
-	"errors"
-	"github.com/gorilla/mux"
-	"net/http"
-	"net/http/httptest"
-	"os"
-	"reflect"
-	"strconv"
-	"testing"
-	"time"
-)
-
-var x XappManager
-var xapp Xapp
-var xapps []Xapp
-var helmError error
-
-type MockedHelmer struct {
-}
-
-func (h *MockedHelmer) SetCM(cm ConfigMapper) {
-}
-
-func (sd *MockedHelmer) Initialize() {
-}
-
-func (h *MockedHelmer) Status(name string) (Xapp, error) {
-	return xapp, helmError
-}
-
-func (h *MockedHelmer) StatusAll() ([]Xapp, error) {
-	return xapps, helmError
-}
-
-func (h *MockedHelmer) SearchAll() (s []string) {
-	return s
-}
-
-func (h *MockedHelmer) List() (names []string, err error) {
-	return names, helmError
-}
-
-func (h *MockedHelmer) Install(m XappDeploy) (Xapp, error) {
-	return xapp, helmError
-}
-
-func (h *MockedHelmer) Delete(name string) (Xapp, error) {
-	return xapp, helmError
-}
-
-// Test cases
-func TestMain(m *testing.M) {
-	Logger = NewLogger("xapp-manager")
-	loadConfig()
-
-	xapp = Xapp{}
-	xapps = []Xapp{}
-
-	cm := MockedConfigMapper{}
-	h := MockedHelmer{}
-	x = XappManager{}
-	x.Initialize(&h, &cm)
-
-	// Just run on the background (for coverage)
-	go x.Run()
-	x.ready = true
-
-	time.Sleep(time.Duration(2 * time.Second))
-
-	code := m.Run()
-	os.Exit(code)
-}
-
-func TestGetHealthCheck(t *testing.T) {
-	req, _ := http.NewRequest("GET", "/ric/v1/health/ready", nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusOK, response.Code)
-}
-
-func TestGetAppsReturnsEmpty(t *testing.T) {
-	req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusOK, response.Code)
-	if body := response.Body.String(); body != "[]" {
-		t.Errorf("handler returned unexpected body: got %v want []", body)
-	}
-}
-
-func TestCreateXApp(t *testing.T) {
-	xapp = generateXapp("dummy-xapp", "started", "1.0", "dummy-xapp-1234-5678", "running", "127.0.0.1", "9999")
-
-	payload := []byte(`{"name":"dummy-xapp"}`)
-	req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(payload))
-	response := executeRequest(req)
-
-	checkResponseData(t, response, http.StatusCreated, false)
-}
-
-func TestGetAppsReturnsListOfXapps(t *testing.T) {
-	xapps = append(xapps, xapp)
-	req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil)
-	response := executeRequest(req)
-
-	checkResponseData(t, response, http.StatusOK, true)
-}
-
-func TestGetAppByIdReturnsGivenXapp(t *testing.T) {
-	req, _ := http.NewRequest("GET", "/ric/v1/xapps/"+xapp.Name, nil)
-	response := executeRequest(req)
-
-	checkResponseData(t, response, http.StatusOK, false)
-}
-
-func TestGetAppInstanceByIdReturnsGivenXapp(t *testing.T) {
-	req, _ := http.NewRequest("GET", "/ric/v1/xapps/"+xapp.Name+"/instances/dummy-xapp-1234-5678", nil)
-	response := executeRequest(req)
-
-	var ins XappInstance
-	checkResponseCode(t, http.StatusOK, response.Code)
-	json.NewDecoder(response.Body).Decode(&ins)
-
-	if !reflect.DeepEqual(ins, xapp.Instances[0]) {
-		t.Errorf("handler returned unexpected body: got: %v, expected: %v", ins, xapp.Instances[0])
-	}
-}
-
-func TestDeleteAppRemovesGivenXapp(t *testing.T) {
-	req, _ := http.NewRequest("DELETE", "/ric/v1/xapps/"+xapp.Name, nil)
-	response := executeRequest(req)
-
-	checkResponseData(t, response, http.StatusNoContent, false)
-
-	// Xapp not found from the Redis DB
-	helmError = errors.New("Not found")
-
-	req, _ = http.NewRequest("GET", "/ric/v1/xapps/"+xapp.Name, nil)
-	response = executeRequest(req)
-	checkResponseCode(t, http.StatusNotFound, response.Code)
-}
-
-func TestGetConfigReturnsEmpty(t *testing.T) {
-	req, _ := http.NewRequest("GET", "/ric/v1/config", nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusOK, response.Code)
-}
-
-func TestCreateConfigFailsWithMethodNotAllowed(t *testing.T) {
-	req, _ := http.NewRequest("POST", "/ric/v1/config", nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusMethodNotAllowed, response.Code)
-}
-
-func TestCreateConfigOk(t *testing.T) {
-	payload := []byte(`{"name":"dummy-xapp"}`)
-	req, _ := http.NewRequest("POST", "/ric/v1/config", bytes.NewBuffer(payload))
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusCreated, response.Code)
-}
-
-func TestDeleteConfigOk(t *testing.T) {
-	payload := []byte(`{"name":"dummy-xapp"}`)
-	req, _ := http.NewRequest("DELETE", "/ric/v1/config", bytes.NewBuffer(payload))
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusNoContent, response.Code)
-}
-
-// Error handling
-func TestGetXappReturnsError(t *testing.T) {
-	helmError = errors.New("Not found")
-
-	req, _ := http.NewRequest("GET", "/ric/v1/xapps/invalidXappName", nil)
-	response := executeRequest(req)
-	checkResponseCode(t, http.StatusNotFound, response.Code)
-}
-
-func TestGetXappInstanceReturnsError(t *testing.T) {
-	helmError = errors.New("Some error")
-
-	req, _ := http.NewRequest("GET", "/ric/v1/xapps/"+xapp.Name+"/instances/invalidXappName", nil)
-	response := executeRequest(req)
-	checkResponseCode(t, http.StatusNotFound, response.Code)
-}
-
-func TestGetXappListReturnsError(t *testing.T) {
-	helmError = errors.New("Internal error")
-
-	req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil)
-	response := executeRequest(req)
-	checkResponseCode(t, http.StatusInternalServerError, response.Code)
-}
-
-func TestCreateXAppWithoutXappData(t *testing.T) {
-	req, _ := http.NewRequest("POST", "/ric/v1/xapps", nil)
-	response := executeRequest(req)
-	checkResponseData(t, response, http.StatusMethodNotAllowed, false)
-}
-
-func TestCreateXAppWithInvalidXappData(t *testing.T) {
-	body := []byte("Invalid JSON data ...")
-
-	req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(body))
-	response := executeRequest(req)
-	checkResponseData(t, response, http.StatusMethodNotAllowed, false)
-}
-
-func TestCreateXAppReturnsError(t *testing.T) {
-	helmError = errors.New("Not found")
-
-	payload := []byte(`{"name":"dummy-xapp"}`)
-	req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(payload))
-	response := executeRequest(req)
-
-	checkResponseData(t, response, http.StatusInternalServerError, false)
-}
-
-func TestDeleteXappListReturnsError(t *testing.T) {
-	helmError = errors.New("Internal error")
-
-	req, _ := http.NewRequest("DELETE", "/ric/v1/xapps/invalidXappName", nil)
-	response := executeRequest(req)
-	checkResponseCode(t, http.StatusInternalServerError, response.Code)
-}
-
-// Helper functions
-type fn func(w http.ResponseWriter, r *http.Request)
-
-func executeRequest(req *http.Request) *httptest.ResponseRecorder {
-	rr := httptest.NewRecorder()
-
-	vars := map[string]string{
-		"id": "1",
-	}
-	req = mux.SetURLVars(req, vars)
-
-	x.router.ServeHTTP(rr, req)
-
-	return rr
-}
-
-func checkResponseCode(t *testing.T, expected, actual int) {
-	if expected != actual {
-		t.Errorf("Expected response code %d. Got %d\n", expected, actual)
-	}
-}
-
-func checkResponseData(t *testing.T, response *httptest.ResponseRecorder, expectedHttpStatus int, isList bool) {
-	expectedData := xapp
-
-	checkResponseCode(t, expectedHttpStatus, response.Code)
-	if isList == true {
-		jsonResp := []Xapp{}
-		json.NewDecoder(response.Body).Decode(&jsonResp)
-
-		if !reflect.DeepEqual(jsonResp[0], expectedData) {
-			t.Errorf("handler returned unexpected body: %v", jsonResp)
-		}
-	} else {
-		json.NewDecoder(response.Body).Decode(&xapp)
-
-		if !reflect.DeepEqual(xapp, expectedData) {
-			t.Errorf("handler returned unexpected body: got: %v, expected: %v", xapp, expectedData)
-		}
-	}
-}
-
-func generateXapp(name, status, ver, iname, istatus, ip, port string) (x Xapp) {
-	x.Name = name
-	x.Status = status
-	x.Version = ver
-	p, _ := strconv.Atoi(port)
-	var msgs MessageTypes
-
-	instance := XappInstance{
-		Name:       iname,
-		Status:     istatus,
-		Ip:         ip,
-		Port:       p,
-		TxMessages: msgs.TxMessages,
-		RxMessages: msgs.RxMessages,
-	}
-	x.Instances = append(x.Instances, instance)
-
-	return
-}
diff --git a/cmd/appmgr/db.go b/cmd/appmgr/db.go
deleted file mode 100755
index 97865c2..0000000
--- a/cmd/appmgr/db.go
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"encoding/json"
-	sdl "gerrit.oran-osc.org/r/ric-plt/sdlgo"
-	cmap "github.com/orcaman/concurrent-map"
-	"github.com/spf13/viper"
-	"time"
-)
-
-type DB struct {
-	session *sdl.SdlInstance
-}
-
-func (d *DB) Create() {
-	ns := viper.GetString("db.sessionNamespace")
-	d.session = sdl.NewSdlInstance(ns, sdl.NewDatabase())
-
-	// Test DB connection, and wait until ready!
-	for {
-		if _, err := d.session.GetAll(); err == nil {
-			return
-		}
-		Logger.Error("Database connection not ready, waiting ...")
-		time.Sleep(time.Duration(5 * time.Second))
-	}
-}
-
-func (d *DB) StoreSubscriptions(m cmap.ConcurrentMap) {
-	for v := range m.Iter() {
-		s := v.Val.(Subscription)
-
-		data, err := json.Marshal(s.req)
-		if err != nil {
-			Logger.Error("json.marshal failed: %v ", err.Error())
-			return
-		}
-
-		if err := d.session.Set(s.req.Id, data); err != nil {
-			Logger.Error("DB.session.Set failed: %v ", err.Error())
-		}
-	}
-}
-
-func (d *DB) RestoreSubscriptions() (m cmap.ConcurrentMap) {
-	m = cmap.New()
-
-	keys, err := d.session.GetAll()
-	if err != nil {
-		Logger.Error("DB.session.GetAll failed: %v ", err.Error())
-		return
-	}
-
-	for _, key := range keys {
-		value, err := d.session.Get([]string{key})
-		if err != nil {
-			Logger.Error("DB.session.Get failed: %v ", err.Error())
-			return
-		}
-
-		var item SubscriptionReq
-		if err = json.Unmarshal([]byte(value[key].(string)), &item); err != nil {
-			Logger.Error("json.Unmarshal failed: %v ", err.Error())
-			return
-		}
-
-		resp := SubscriptionResp{key, 0, item.EventType}
-		m.Set(key, Subscription{item, resp})
-	}
-
-	return m
-}
diff --git a/cmd/appmgr/desc.go b/cmd/appmgr/desc.go
deleted file mode 100755
index 8cc39a5..0000000
--- a/cmd/appmgr/desc.go
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"encoding/json"
-	"errors"
-	"fmt"
-	"github.com/spf13/viper"
-	"github.com/valyala/fastjson"
-	"github.com/xeipuuv/gojsonschema"
-	"io/ioutil"
-	"log"
-	"os"
-	"path"
-	"regexp"
-	"strings"
-	"time"
-)
-
-type ConfigMetadata struct {
-	Name       string `json:"name"`
-	ConfigName string `json:"configName, omitempty"`
-	Namespace  string `json:"namespace, omitempty"`
-}
-
-type XAppConfig struct {
-	Metadata      ConfigMetadata `json:"metadata"`
-	Descriptor    interface{}    `json:"descriptor, omitempty"`
-	Configuration interface{}    `json:"config, omitempty"`
-}
-
-type ConfigMap struct {
-	Kind       string      `json:"kind"`
-	ApiVersion string      `json:"apiVersion"`
-	Data       interface{} `json:"data"`
-	Metadata   CMMetadata  `json:"metadata"`
-}
-
-type CMMetadata struct {
-	Name      string `json:"name"`
-	Namespace string `json:"namespace"`
-}
-
-type CMError struct {
-	Field       string `json:"field"`
-	Description string `json:"description"`
-}
-
-func (cm *ConfigMap) UploadConfig() (cfg []XAppConfig) {
-	ns := cm.GetNamespace("")
-	for _, name := range cm.GetNamesFromHelmRepo() {
-		if name == "appmgr" {
-			continue
-		}
-
-		c := XAppConfig{
-			Metadata: ConfigMetadata{Name: name, Namespace: ns, ConfigName: cm.GetConfigMapName(name, ns)},
-		}
-
-		err := cm.ReadSchema(name, &c)
-		if err != nil {
-			continue
-		}
-
-		err = cm.ReadConfigMap(c.Metadata.ConfigName, ns, &c.Configuration)
-		if err != nil {
-			log.Println("No active configMap found, using default!")
-		}
-
-		cfg = append(cfg, c)
-	}
-	return
-}
-
-func (cm *ConfigMap) ReadSchema(name string, c *XAppConfig) (err error) {
-	if err = cm.FetchChart(name); err != nil {
-		return
-	}
-
-	tarDir := viper.GetString("xapp.tarDir")
-	err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor)
-	if err != nil {
-		return
-	}
-
-	err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Configuration)
-	if err != nil {
-		return
-	}
-
-	if err = os.RemoveAll(path.Join(tarDir, name)); err != nil {
-		log.Println("RemoveAll failed", err)
-	}
-
-	return
-}
-
-func (cm *ConfigMap) ReadConfigMap(ConfigName string, ns string, c *interface{}) (err error) {
-	args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, ConfigName)
-	configMapJson, err := KubectlExec(args)
-	if err != nil {
-		return
-	}
-
-	err = json.Unmarshal([]byte(configMapJson), &c)
-	if err != nil {
-		return
-	}
-
-	return
-}
-
-func (cm *ConfigMap) ApplyConfigMap(r XAppConfig, action string) (err error) {
-	c := ConfigMap{
-		Kind:       "ConfigMap",
-		ApiVersion: "v1",
-		Metadata:   CMMetadata{Name: r.Metadata.Name, Namespace: r.Metadata.Namespace},
-		Data:       r.Configuration,
-	}
-
-	cmJson, err := json.Marshal(c.Data)
-	if err != nil {
-		log.Println("Config marshalling failed: ", err)
-		return
-	}
-
-	cmFile := viper.GetString("xapp.tmpConfig")
-	err = ioutil.WriteFile(cmFile, cmJson, 0644)
-	if err != nil {
-		log.Println("WriteFile failed: ", err)
-		return
-	}
-
-	cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl %s -f -"
-	args := fmt.Sprintf(cmd, r.Metadata.Namespace, r.Metadata.ConfigName, cmFile, action)
-	_, err = KubectlExec(args)
-	if err != nil {
-		return
-	}
-	log.Println("Configmap changes done!")
-
-	return
-}
-
-func (cm *ConfigMap) GetConfigMap(m XappDeploy, c *interface{}) (err error) {
-	if m.ConfigName == "" {
-		m.ConfigName = cm.GetConfigMapName(m.Name, m.Namespace)
-	}
-	return cm.ReadConfigMap(m.ConfigName, m.Namespace, c)
-}
-
-func (cm *ConfigMap) CreateConfigMap(r XAppConfig) (errList []CMError, err error) {
-	if errList, err = cm.Validate(r); err != nil {
-		return
-	}
-	err = cm.ApplyConfigMap(r, "create")
-	return
-}
-
-func (cm *ConfigMap) UpdateConfigMap(r XAppConfig) (errList []CMError, err error) {
-	if errList, err = cm.Validate(r); err != nil {
-		return
-	}
-
-	// Re-create the configmap with the new parameters
-	err = cm.ApplyConfigMap(r, "apply")
-	return
-}
-
-func (cm *ConfigMap) DeleteConfigMap(r XAppConfig) (c interface{}, err error) {
-	err = cm.ReadConfigMap(r.Metadata.ConfigName, r.Metadata.Namespace, &c)
-	if err == nil {
-		args := fmt.Sprintf(" delete configmap --namespace=%s %s", r.Metadata.Namespace, r.Metadata.ConfigName)
-		_, err = KubectlExec(args)
-	}
-	return
-}
-
-func (cm *ConfigMap) PurgeConfigMap(m XappDeploy) (c interface{}, err error) {
-	if m.ConfigName == "" {
-		m.ConfigName = cm.GetConfigMapName(m.Name, m.Namespace)
-	}
-	md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName}
-
-	return cm.DeleteConfigMap(XAppConfig{Metadata: md})
-}
-
-func (cm *ConfigMap) RestoreConfigMap(m XappDeploy, c interface{}) (err error) {
-	if m.ConfigName == "" {
-		m.ConfigName = cm.GetConfigMapName(m.Name, m.Namespace)
-	}
-	md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName}
-	time.Sleep(time.Duration(10 * time.Second))
-
-	return cm.ApplyConfigMap(XAppConfig{Metadata: md, Configuration: c}, "create")
-}
-
-func (cm *ConfigMap) GetNamesFromHelmRepo() (names []string) {
-	rname := viper.GetString("helm.repo-name")
-
-	cmdArgs := strings.Join([]string{"search ", rname}, "")
-	out, err := HelmExec(cmdArgs)
-	if err != nil {
-		return
-	}
-
-	re := regexp.MustCompile(rname + `/.*`)
-	result := re.FindAllStringSubmatch(string(out), -1)
-	if result != nil {
-		var tmp string
-		for _, v := range result {
-			fmt.Sscanf(v[0], "%s", &tmp)
-			names = append(names, strings.Split(tmp, "/")[1])
-		}
-	}
-	return names
-}
-
-func (cm *ConfigMap) Validate(req XAppConfig) (errList []CMError, err error) {
-	c := XAppConfig{}
-	err = cm.ReadSchema(req.Metadata.Name, &c)
-	if err != nil {
-		log.Printf("No schema file found for '%s', aborting ...", req.Metadata.Name)
-		return
-	}
-	return cm.doValidate(c.Descriptor, req.Configuration)
-}
-
-func (cm *ConfigMap) doValidate(schema, cfg interface{}) (errList []CMError, err error) {
-	schemaLoader := gojsonschema.NewGoLoader(schema)
-	documentLoader := gojsonschema.NewGoLoader(cfg)
-
-	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
-	if err != nil {
-		log.Println("Validation failed: ", err)
-		return
-	}
-
-	if result.Valid() == false {
-		log.Println("The document is not valid, Errors: ", result.Errors())
-		for _, desc := range result.Errors() {
-			errList = append(errList, CMError{Field: desc.Field(), Description: desc.Description()})
-		}
-		return errList, errors.New("Validation failed!")
-	}
-	return
-}
-
-func (cm *ConfigMap) ReadFile(name string, data interface{}) (err error) {
-	f, err := ioutil.ReadFile(name)
-	if err != nil {
-		log.Printf("Reading '%s' file failed: %v", name, err)
-		return
-	}
-
-	err = json.Unmarshal(f, &data)
-	if err != nil {
-		log.Printf("Unmarshalling '%s' file failed: %v", name, err)
-		return
-	}
-
-	return
-}
-
-func (cm *ConfigMap) FetchChart(name string) (err error) {
-	tarDir := viper.GetString("xapp.tarDir")
-	repo := viper.GetString("helm.repo-name")
-	fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
-
-	_, err = HelmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
-	return
-}
-
-func (cm *ConfigMap) GetMessages(name string) (msgs MessageTypes) {
-	log.Println("Fetching tx/rx messages for: ", name)
-
-	ns := cm.GetNamespace("")
-	args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
-	out, err := KubectlExec(args)
-	if err != nil {
-		return
-	}
-
-	var p fastjson.Parser
-	v, err := p.Parse(string(out))
-	if err != nil {
-		log.Printf("fastjson.Parser for '%s' failed: %v", name, err)
-		return
-	}
-
-	for _, m := range v.GetArray("rmr", "txMessages") {
-		msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
-	}
-	for _, m := range v.GetArray("rmr", "rxMessages") {
-		msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
-	}
-
-	return
-}
-
-func (cm *ConfigMap) GetConfigMapName(xappName, namespace string) string {
-	return " configmap-" + namespace + "-" + xappName + "-appconfig"
-}
-
-func (cm *ConfigMap) GetNamespace(ns string) string {
-	if ns != "" {
-		return ns
-	}
-
-	ns = viper.GetString("xapp.namespace")
-	if ns == "" {
-		ns = "ricxapp"
-	}
-	return ns
-}
diff --git a/cmd/appmgr/desc_test.go b/cmd/appmgr/desc_test.go
deleted file mode 100755
index 8f005a1..0000000
--- a/cmd/appmgr/desc_test.go
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"encoding/json"
-	"errors"
-	"log"
-	"reflect"
-	"testing"
-)
-
-var helmSearchOutput = `
-helm-repo/anr           0.0.1           1.0             Helm Chart for Nokia ANR (Automatic Neighbour Relation) xAPP
-helm-repo/appmgr        0.0.2           1.0             Helm Chart for xAppManager
-helm-repo/dualco        0.0.1           1.0             Helm Chart for Nokia dualco xAPP
-helm-repo/reporter      0.0.1           1.0             Helm Chart for Reporting xAPP
-helm-repo/uemgr         0.0.1           1.0             Helm Chart for Nokia uemgr xAPP
-`
-
-var kubectlConfigmapOutput = `
-{
-    "local": {
-        "host": ":8080"
-    },
-    "logger": {
-        "level": 3
-    },
-    "rmr": {
-       "protPort": "tcp:4560",
-       "maxSize": 2072,
-       "numWorkers": 1,
-       "txMessages": ["RIC_X2_LOAD_INFORMATION"],
-       "rxMessages": ["RIC_X2_LOAD_INFORMATION"]
-    },
-    "db": {
-        "namespace": "ricxapp",
-        "host": "dbaas",
-        "port": 6379
-    }
-}
-`
-
-type ConfigSample struct {
-	Level int
-	Host  string
-}
-
-type MockedConfigMapper struct {
-}
-
-func (cm *MockedConfigMapper) ReadSchema(name string, c *XAppConfig) (err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) UploadConfig() (cfg []XAppConfig) {
-	return
-}
-
-func (cm *MockedConfigMapper) CreateConfigMap(r XAppConfig) (errList []CMError, err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) GetConfigMap(m XappDeploy, c *interface{}) (err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) UpdateConfigMap(r XAppConfig) (errList []CMError, err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) DeleteConfigMap(r XAppConfig) (c interface{}, err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) PurgeConfigMap(m XappDeploy) (c interface{}, err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) RestoreConfigMap(m XappDeploy, c interface{}) (err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) ReadConfigMap(name string, ns string, c *interface{}) (err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) ApplyConfigMap(r XAppConfig, action string) (err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) FetchChart(name string) (err error) {
-	return
-}
-
-func (cm *MockedConfigMapper) GetMessages(name string) (msgs MessageTypes) {
-	return
-}
-
-func (cm *MockedConfigMapper) GetNamespace(ns string) (n string) {
-	return
-}
-
-func (cm *MockedConfigMapper) GetNamesFromHelmRepo() (names []string) {
-	return
-}
-
-// Test cases
-func TestGetMessages(t *testing.T) {
-	cm := ConfigMap{}
-	expectedMsgs := MessageTypes{
-		TxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
-		RxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
-	}
-
-	KubectlExec = func(args string) (out []byte, err error) {
-		return []byte(kubectlConfigmapOutput), nil
-	}
-
-	result := cm.GetMessages("dummy-xapp")
-	if !reflect.DeepEqual(result, expectedMsgs) {
-		t.Errorf("TestGetMessages failed: expected: %v, got: %v", expectedMsgs, result)
-	}
-}
-
-func TestHelmNamespace(t *testing.T) {
-	cm := ConfigMap{}
-
-	if cm.GetNamespace("pltxapp") != "pltxapp" {
-		t.Errorf("getNamespace failed!")
-	}
-
-	if cm.GetNamespace("") != "default" {
-		t.Errorf("getNamespace failed!")
-	}
-}
-
-func TestFetchChartFails(t *testing.T) {
-	cm := ConfigMap{}
-
-	if cm.FetchChart("dummy-xapp") == nil {
-		t.Errorf("TestFetchChart failed!")
-	}
-}
-
-func TestFetchChartSuccess(t *testing.T) {
-	cm := ConfigMap{}
-
-	HelmExec = func(args string) (out []byte, err error) {
-		return
-	}
-
-	if cm.FetchChart("dummy-xapp") != nil {
-		t.Errorf("TestFetchChart failed!")
-	}
-}
-
-func TestGetNamesFromHelmRepoSuccess(t *testing.T) {
-	cm := ConfigMap{}
-	expectedResult := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
-	HelmExec = func(args string) (out []byte, err error) {
-		return []byte(helmSearchOutput), nil
-	}
-
-	names := cm.GetNamesFromHelmRepo()
-	if !reflect.DeepEqual(names, expectedResult) {
-		t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
-	}
-}
-
-func TestGetNamesFromHelmRepoFailure(t *testing.T) {
-	cm := ConfigMap{}
-	expectedResult := []string{}
-	HelmExec = func(args string) (out []byte, err error) {
-		return []byte(helmSearchOutput), errors.New("Command failed!")
-	}
-
-	names := cm.GetNamesFromHelmRepo()
-	if names != nil {
-		t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
-	}
-}
-
-func TestApplyConfigMapSuccess(t *testing.T) {
-	cm := ConfigMap{}
-	m := ConfigMetadata{Name: "dummy-xapp", Namespace: "ricxapp"}
-	s := ConfigSample{5, "localhost"}
-
-	KubectlExec = func(args string) (out []byte, err error) {
-		log.Println("TestApplyConfigMapSuccess: ", args)
-		return []byte(`{"logger": {"level": 2}}`), nil
-	}
-
-	err := cm.ApplyConfigMap(XAppConfig{Metadata: m, Configuration: s}, "create")
-	if err != nil {
-		t.Errorf("ApplyConfigMap failed: %v", err)
-	}
-}
-
-func TestRestoreConfigMapSuccess(t *testing.T) {
-	cm := ConfigMap{}
-	m := XappDeploy{Name: "dummy-xapp", Namespace: "ricxapp"}
-	s := ConfigSample{5, "localhost"}
-
-	KubectlExec = func(args string) (out []byte, err error) {
-		log.Println("TestRestoreConfigMapSuccess: ", args)
-		return []byte(`{"logger": {"level": 2}}`), nil
-	}
-
-	err := cm.RestoreConfigMap(m, s)
-	if err != nil {
-		t.Errorf("RestoreConfigMap failed: %v", err)
-	}
-}
-
-func TestDeleteConfigMapSuccess(t *testing.T) {
-	cm := ConfigMap{}
-
-	HelmExec = func(args string) (out []byte, err error) {
-		return []byte("ok"), nil
-	}
-
-	KubectlExec = func(args string) (out []byte, err error) {
-		log.Println("TestDeleteConfigMapSuccess: ", args)
-		return []byte(`{"logger": {"level": 2}}`), nil
-	}
-
-	c, err := cm.DeleteConfigMap(XAppConfig{})
-	if err != nil {
-		t.Errorf("DeleteConfigMap failed: %v -> %v", err, c)
-	}
-}
-
-func TestPurgeConfigMapSuccess(t *testing.T) {
-	cm := ConfigMap{}
-
-	HelmExec = func(args string) (out []byte, err error) {
-		return []byte("ok"), nil
-	}
-
-	KubectlExec = func(args string) (out []byte, err error) {
-		return []byte(`{"logger": {"level": 2}}`), nil
-	}
-
-	c, err := cm.PurgeConfigMap(XappDeploy{})
-	if err != nil {
-		t.Errorf("PurgeConfigMap failed: %v -> %v", err, c)
-	}
-}
-
-func TestCreateConfigMapFails(t *testing.T) {
-	cm := ConfigMap{}
-
-	c, err := cm.CreateConfigMap(XAppConfig{})
-	if err == nil {
-		t.Errorf("CreateConfigMap failed: %v -> %v", err, c)
-	}
-}
-
-func TestUpdateConfigMapFails(t *testing.T) {
-	cm := ConfigMap{}
-
-	c, err := cm.UpdateConfigMap(XAppConfig{})
-	if err == nil {
-		t.Errorf("CreateConfigMap failed: %v -> %v", err, c)
-	}
-}
-
-func TestValidationSuccess(t *testing.T) {
-	cm := ConfigMap{}
-	var d interface{}
-	var cfg map[string]interface{}
-
-	err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": 3}}`), &cfg)
-
-	err = cm.ReadFile("./test/schema.json", &d)
-	if err != nil {
-		t.Errorf("ReadFile failed: %v -> %v", err, d)
-	}
-
-	feedback, err := cm.doValidate(d, cfg)
-	if err != nil {
-		t.Errorf("doValidate failed: %v -> %v", err, feedback)
-	}
-}
-
-func TestValidationFails(t *testing.T) {
-	cm := ConfigMap{}
-	var d interface{}
-	var cfg map[string]interface{}
-
-	err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": "INVALID"}}`), &cfg)
-
-	err = cm.ReadFile("./test/schema.json", &d)
-	if err != nil {
-		t.Errorf("ConfigMetadata failed: %v -> %v", err, d)
-	}
-
-	feedback, err := cm.doValidate(d, cfg)
-	if err == nil {
-		t.Errorf("doValidate should faile but didn't: %v -> %v", err, feedback)
-	}
-
-	log.Println("Feedbacks: ", feedback)
-}
diff --git a/cmd/appmgr/helm.go b/cmd/appmgr/helm.go
deleted file mode 100755
index 8a8154a..0000000
--- a/cmd/appmgr/helm.go
+++ /dev/null
@@ -1,396 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"github.com/spf13/viper"
-	"io/ioutil"
-	"log"
-	"os"
-	"os/exec"
-	"regexp"
-	"strconv"
-	"strings"
-	"time"
-)
-
-var execCommand = exec.Command
-
-func Exec(args string) (out []byte, err error) {
-	cmd := execCommand("/bin/sh", "-c", args)
-
-	var stdout bytes.Buffer
-	var stderr bytes.Buffer
-	cmd.Stdout = &stdout
-	cmd.Stderr = &stderr
-
-	log.Println("Running command: ", cmd)
-	for i := 0; i < viper.GetInt("helm.retry"); i++ {
-		err = cmd.Run()
-		if err != nil {
-			Logger.Error("Command '%s' failed with error: %v, retrying", args, err.Error()+stderr.String())
-			time.Sleep(time.Duration(2) * time.Second)
-			continue
-		}
-		break
-	}
-
-	if err == nil && !strings.HasSuffix(os.Args[0], ".test") {
-		Logger.Info("command success: %s", stdout.String())
-		return stdout.Bytes(), nil
-	}
-
-	return stdout.Bytes(), errors.New(stderr.String())
-}
-
-var HelmExec = func(args string) (out []byte, err error) {
-	return Exec(strings.Join([]string{"helm", args}, " "))
-}
-
-var KubectlExec = func(args string) (out []byte, err error) {
-	return Exec(strings.Join([]string{"kubectl", args}, " "))
-}
-
-func (h *Helm) SetCM(cm ConfigMapper) {
-	h.cm = cm
-}
-
-func (h *Helm) Initialize() {
-	if h.initDone == true {
-		return
-	}
-
-	for {
-		if _, err := h.Init(); err == nil {
-			Logger.Info("Helm init done successfully!")
-			break
-		}
-		Logger.Error("helm init failed, retyring ...")
-		time.Sleep(time.Duration(10) * time.Second)
-	}
-
-	for {
-		if _, err := h.AddRepo(); err == nil {
-			Logger.Info("Helm repo added successfully")
-			break
-		}
-		Logger.Error("Helm repo addition failed, retyring ...")
-		time.Sleep(time.Duration(10) * time.Second)
-	}
-
-	h.initDone = true
-}
-
-func (h *Helm) Run(args string) (out []byte, err error) {
-	return HelmExec(args)
-}
-
-// API functions
-func (h *Helm) Init() (out []byte, err error) {
-	// Add Tiller address as environment variable
-	if err := addTillerEnv(); err != nil {
-		return out, err
-	}
-
-	return HelmExec(strings.Join([]string{"init -c --skip-refresh"}, ""))
-}
-
-func (h *Helm) AddRepo() (out []byte, err error) {
-	// Get helm repo user name and password from files mounted by secret object
-	credFile, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
-	if err != nil {
-		Logger.Error("helm_repo_username ReadFile failed: %v", err.Error())
-		return
-	}
-
-	username := " --username " + string(credFile)
-
-	credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
-	if err != nil {
-		Logger.Error("helm_repo_password ReadFile failed: %v", err.Error())
-		return
-	}
-
-	pwd := " --password " + string(credFile)
-
-	// Get internal helm repo name
-	rname := viper.GetString("helm.repo-name")
-
-	// Get helm repo address from values.yaml
-	repo := viper.GetString("helm.repo")
-
-	return HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
-}
-
-func (h *Helm) Install(m XappDeploy) (xapp Xapp, err error) {
-	out, err := h.Run(strings.Join([]string{"repo update "}, ""))
-	if err != nil {
-		return
-	}
-
-	var cm interface{}
-	m.Namespace = h.cm.GetNamespace(m.Namespace)
-
-	if err = h.cm.GetConfigMap(m, &cm); err != nil {
-		out, err = h.Run(getInstallArgs(m, false))
-		if err != nil {
-			return
-		}
-		return h.ParseStatus(m.Name, string(out))
-	}
-
-	// ConfigMap exists, try to override
-	out, err = h.Run(getInstallArgs(m, true))
-	if err == nil {
-		return h.ParseStatus(m.Name, string(out))
-	}
-
-	cm, cmErr := h.cm.PurgeConfigMap(m)
-	out, err = h.Run(getInstallArgs(m, false))
-	if err != nil {
-		return
-	}
-
-	if cmErr == nil {
-		cmErr = h.cm.RestoreConfigMap(m, cm)
-	}
-	return h.ParseStatus(m.Name, string(out))
-}
-
-func (h *Helm) Status(name string) (xapp Xapp, err error) {
-	out, err := h.Run(strings.Join([]string{"status ", name}, ""))
-	if err != nil {
-		Logger.Error("Getting xapps status: %v", err.Error())
-		return
-	}
-
-	return h.ParseStatus(name, string(out))
-}
-
-func (h *Helm) StatusAll() (xapps []Xapp, err error) {
-	xappNameList, err := h.List()
-	if err != nil {
-		Logger.Error("Helm list failed: %v", err.Error())
-		return
-	}
-
-	return h.parseAllStatus(xappNameList)
-}
-
-func (h *Helm) List() (names []string, err error) {
-	ns := h.cm.GetNamespace("")
-	out, err := h.Run(strings.Join([]string{"list --all --output yaml --namespace=", ns}, ""))
-	if err != nil {
-		Logger.Error("Listing deployed xapps failed: %v", err.Error())
-		return
-	}
-
-	return h.GetNames(string(out))
-}
-
-func (h *Helm) SearchAll() (names []string) {
-	return h.cm.GetNamesFromHelmRepo()
-}
-
-func (h *Helm) Delete(name string) (xapp Xapp, err error) {
-	xapp, err = h.Status(name)
-	if err != nil {
-		Logger.Error("Fetching xapp status failed: %v", err.Error())
-		return
-	}
-
-	_, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
-	return xapp, err
-}
-
-func (h *Helm) Fetch(name, tarDir string) error {
-	if strings.HasSuffix(os.Args[0], ".test") {
-		return nil
-	}
-
-	rname := viper.GetString("helm.repo-name") + "/"
-
-	_, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
-	return err
-}
-
-// Helper functions
-func (h *Helm) GetVersion(name string) (version string) {
-	ns := h.cm.GetNamespace("")
-	out, err := h.Run(strings.Join([]string{"list --output yaml --namespace=", ns, " ", name}, ""))
-	if err != nil {
-		return
-	}
-
-	var re = regexp.MustCompile(`AppVersion: .*`)
-	ver := re.FindStringSubmatch(string(out))
-	if ver != nil {
-		version = strings.Split(ver[0], ": ")[1]
-		version, _ = strconv.Unquote(version)
-	}
-
-	return
-}
-
-func (h *Helm) GetState(out string) (status string) {
-	re := regexp.MustCompile(`STATUS: .*`)
-	result := re.FindStringSubmatch(string(out))
-	if result != nil {
-		status = strings.ToLower(strings.Split(result[0], ": ")[1])
-	}
-
-	return
-}
-
-func (h *Helm) GetAddress(out string) (ip, port string) {
-	var tmp string
-	re := regexp.MustCompile(`ClusterIP.*`)
-	addr := re.FindStringSubmatch(string(out))
-	if addr != nil {
-		fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
-	}
-
-	return
-}
-
-func (h *Helm) GetEndpointInfo(name string) (ip string, port int) {
-	ns := h.cm.GetNamespace("")
-	args := fmt.Sprintf(" get endpoints -o=jsonpath='{.subsets[*].addresses[*].ip}' service-%s-%s-rmr -n %s", ns, name, ns)
-	out, err := KubectlExec(args)
-	if err != nil {
-		return
-	}
-	Logger.Info("Endpoint IP address of %s: %s", name, string(out))
-
-	// "service-<namespace>-<chartname>-rmr.<namespace>"
-	return "service-" + ns + "-" + name + "-rmr." + ns, 4560
-}
-
-func (h *Helm) GetNames(out string) (names []string, err error) {
-	re := regexp.MustCompile(`Name: .*`)
-	result := re.FindAllStringSubmatch(out, -1)
-	if result == nil {
-		return
-	}
-
-	for _, v := range result {
-		xappName := strings.Split(v[0], ": ")[1]
-		if strings.Contains(xappName, "appmgr") == false {
-			names = append(names, xappName)
-		}
-	}
-	return names, nil
-}
-
-func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) {
-	ip, port := h.GetEndpointInfo(name)
-	if ip == "" {
-		Logger.Info("Endpoint IP address not found, using CluserIP")
-		ip, _ = h.GetAddress(out)
-	}
-
-	var tmp string
-	r := regexp.MustCompile(`.*(?s)(Running|Pending|Succeeded|Failed|Unknown).*?\r?\n\r?\n`)
-	result := r.FindStringSubmatch(string(out))
-	if result == nil {
-		return
-	}
-
-	re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
-	resources := re.FindAllStringSubmatch(string(result[0]), -1)
-	if resources != nil {
-		for _, v := range resources {
-			var x XappInstance
-			fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status)
-			x.Status = strings.ToLower(x.Status)
-			x.Ip = ip
-			x.Port = port
-			x.TxMessages = msgs.TxMessages
-			x.RxMessages = msgs.RxMessages
-			xapp.Instances = append(xapp.Instances, x)
-		}
-	}
-}
-
-func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
-	xapp.Name = name
-	xapp.Version = h.GetVersion(name)
-	xapp.Status = h.GetState(out)
-
-	h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
-
-	return
-}
-
-func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) {
-	xapps = []Xapp{}
-
-	for _, name := range names {
-		err := h.cm.ReadSchema(name, &XAppConfig{})
-		if err != nil {
-			continue
-		}
-
-		x, err := h.Status(name)
-		if err == nil {
-			xapps = append(xapps, x)
-		}
-	}
-
-	return
-}
-
-func addTillerEnv() (err error) {
-	service := viper.GetString("helm.tiller-service")
-	namespace := viper.GetString("helm.tiller-namespace")
-	port := viper.GetString("helm.tiller-port")
-
-	if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
-		Logger.Error("Tiller Env Setting Failed: %v", err.Error())
-	}
-
-	return err
-}
-
-func getInstallArgs(x XappDeploy, cmOverride bool) (args string) {
-	args = args + " --namespace=" + x.Namespace
-
-	if x.ImageRepo != "" {
-		args = args + " --set global.repository=" + x.ImageRepo
-	}
-
-	if x.ServiceName != "" {
-		args = args + " --set ricapp.service.name=" + x.ServiceName
-	}
-
-	if x.Hostname != "" {
-		args = args + " --set ricapp.hostname=" + x.Hostname
-	}
-
-	if cmOverride == true {
-		args = args + " --set ricapp.appconfig.override=" + x.Name + "-appconfig"
-	}
-
-	rname := viper.GetString("helm.repo-name")
-	return fmt.Sprintf("install %s/%s --name=%s %s", rname, x.Name, x.Name, args)
-}
diff --git a/cmd/appmgr/helm_test.go b/cmd/appmgr/helm_test.go
deleted file mode 100755
index 6ad10c6..0000000
--- a/cmd/appmgr/helm_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"reflect"
-	"testing"
-)
-
-var helmStatusOutput = `
-LAST DEPLOYED: Sat Mar  9 06:50:45 2019
-NAMESPACE: default
-STATUS: DEPLOYED
-
-RESOURCES:
-==> v1/Pod(related)
-NAME                        READY  STATUS   RESTARTS  AGE
-dummy-xapp-8984fc9fd-bkcbp  1/1    Running  0         55m
-dummy-xapp-8984fc9fd-l6xch  1/1    Running  0         55m
-dummy-xapp-8984fc9fd-pp4hg  1/1    Running  0         55m
-
-==> v1/Service
-NAME                         TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)  AGE
-dummy-xapp-dummy-xapp-chart  ClusterIP  10.102.184.212  <none>       80/TCP   55m
-
-==> v1beta1/Deployment
-NAME        READY  UP-TO-DATE  AVAILABLE  AGE
-dummy-xapp  3/3    3           3          55m
-`
-
-var helListOutput = `Next: ""
-Releases:
-- AppVersion: "1.0"
-  Chart: dummy-xapp-chart-0.1.0
-  Name: dummy-xapp
-  Namespace: default
-  Revision: 1
-  Status: DEPLOYED
-  Updated: Mon Mar 11 06:55:05 2019
-- AppVersion: "2.0"
-  Chart: dummy-xapp-chart-0.1.0
-  Name: dummy-xapp2
-  Namespace: default
-  Revision: 1
-  Status: DEPLOYED
-  Updated: Mon Mar 11 06:55:05 2019
-- AppVersion: "1.0"
-  Chart: appmgr-0.0.1
-  Name: appmgr
-  Namespace: default
-  Revision: 1
-  Status: DEPLOYED
-  Updated: Sun Mar 24 07:17:00 2019`
-
-var h = Helm{}
-
-func TestHelmStatus(t *testing.T) {
-	h.SetCM(&ConfigMap{})
-	KubectlExec = func(args string) (out []byte, err error) {
-		return []byte("10.102.184.212"), nil
-	}
-	xapp, err := h.ParseStatus("dummy-xapp", helmStatusOutput)
-	if err != nil {
-		t.Errorf("Helm install failed: %v", err)
-	}
-
-	x := getXappData()
-	xapp.Version = "1.0"
-
-	if !reflect.DeepEqual(xapp, x) {
-		t.Errorf("\n%v \n%v", xapp, x)
-	}
-}
-
-func TestHelmLists(t *testing.T) {
-	names, err := h.GetNames(helListOutput)
-	if err != nil {
-		t.Errorf("Helm status failed: %v", err)
-	}
-
-	if !reflect.DeepEqual(names, []string{"dummy-xapp", "dummy-xapp2"}) {
-		t.Errorf("Helm status failed: %v", err)
-	}
-}
-
-func TestAddTillerEnv(t *testing.T) {
-	if addTillerEnv() != nil {
-		t.Errorf("TestAddTillerEnv failed!")
-	}
-}
-
-func TestGetInstallArgs(t *testing.T) {
-	x := XappDeploy{Name: "dummy-xapp", Namespace: "ricxapp"}
-
-	expectedArgs := "install helm-repo/dummy-xapp --name=dummy-xapp  --namespace=ricxapp"
-	if args := getInstallArgs(x, false); args != expectedArgs {
-		t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
-	}
-
-	x.ImageRepo = "localhost:5000"
-	expectedArgs = expectedArgs + " --set global.repository=" + "localhost:5000"
-	if args := getInstallArgs(x, false); args != expectedArgs {
-		t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
-	}
-
-	x.ServiceName = "xapp"
-	expectedArgs = expectedArgs + " --set ricapp.service.name=" + "xapp"
-	if args := getInstallArgs(x, false); args != expectedArgs {
-		t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
-	}
-
-	x.ServiceName = "xapp"
-	expectedArgs = expectedArgs + " --set ricapp.appconfig.override=dummy-xapp-appconfig"
-	if args := getInstallArgs(x, true); args != expectedArgs {
-		t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
-	}
-}
-
-func getXappData() (x Xapp) {
-	x = generateXapp("dummy-xapp", "deployed", "1.0", "dummy-xapp-8984fc9fd-bkcbp", "running", "10.102.184.212", "4560")
-	x.Instances = append(x.Instances, x.Instances[0])
-	x.Instances = append(x.Instances, x.Instances[0])
-	x.Instances[1].Name = "dummy-xapp-8984fc9fd-l6xch"
-	x.Instances[2].Name = "dummy-xapp-8984fc9fd-pp4hg"
-
-	return x
-}
diff --git a/cmd/appmgr/subscriptions.go b/cmd/appmgr/subscriptions.go
deleted file mode 100755
index 53bc212..0000000
--- a/cmd/appmgr/subscriptions.go
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"bytes"
-	"encoding/json"
-	"github.com/segmentio/ksuid"
-	"net/http"
-	"time"
-)
-
-func (sd *SubscriptionDispatcher) Initialize() {
-	sd.client = &http.Client{}
-
-	sd.db = &DB{}
-	sd.db.Create()
-	sd.subscriptions = sd.db.RestoreSubscriptions()
-}
-
-func (sd *SubscriptionDispatcher) Add(sr SubscriptionReq) (resp SubscriptionResp) {
-	// Skip duplicates
-	for v := range sd.subscriptions.IterBuffered() {
-		r := v.Val.(Subscription).req
-		if r.TargetUrl == sr.TargetUrl && r.EventType == sr.EventType {
-			Logger.Info("Similar subscription already exists!")
-			return
-		}
-	}
-
-	key := ksuid.New().String()
-	resp = SubscriptionResp{key, 0, sr.EventType}
-	sr.Id = key
-
-	sd.subscriptions.Set(key, Subscription{sr, resp})
-	sd.db.StoreSubscriptions(sd.subscriptions)
-
-	Logger.Info("Sub: New subscription added: key=%s value=%v", key, sr)
-	return
-}
-
-func (sd *SubscriptionDispatcher) GetAll() (hooks []SubscriptionReq) {
-	hooks = []SubscriptionReq{}
-	for v := range sd.subscriptions.IterBuffered() {
-		hooks = append(hooks, v.Val.(Subscription).req)
-	}
-
-	return hooks
-}
-
-func (sd *SubscriptionDispatcher) Get(id string) (SubscriptionReq, bool) {
-	if v, found := sd.subscriptions.Get(id); found {
-		Logger.Info("Subscription id=%s found: %v", id, v.(Subscription).req)
-
-		return v.(Subscription).req, found
-	}
-	return SubscriptionReq{}, false
-}
-
-func (sd *SubscriptionDispatcher) Delete(id string) (SubscriptionReq, bool) {
-	if v, found := sd.subscriptions.Get(id); found {
-		Logger.Info("Subscription id=%s found: %v ... deleting", id, v.(Subscription).req)
-
-		sd.subscriptions.Remove(id)
-		sd.db.StoreSubscriptions(sd.subscriptions)
-
-		return v.(Subscription).req, found
-	}
-	return SubscriptionReq{}, false
-}
-
-func (sd *SubscriptionDispatcher) Update(id string, sr SubscriptionReq) (SubscriptionReq, bool) {
-	if s, found := sd.subscriptions.Get(id); found {
-		Logger.Info("Subscription id=%s found: %v ... updating", id, s.(Subscription).req)
-
-		sr.Id = id
-		sd.subscriptions.Set(id, Subscription{sr, s.(Subscription).resp})
-		sd.db.StoreSubscriptions(sd.subscriptions)
-
-		return sr, found
-	}
-	return SubscriptionReq{}, false
-}
-
-func (sd *SubscriptionDispatcher) Publish(x Xapp, et EventType) {
-	sd.notifyClients([]Xapp{x}, et)
-}
-
-func (sd *SubscriptionDispatcher) notifyClients(xapps []Xapp, et EventType) {
-	if len(xapps) == 0 || len(sd.subscriptions) == 0 {
-		Logger.Info("Nothing to publish [%d:%d]", len(xapps), len(sd.subscriptions))
-		return
-	}
-
-	sd.Seq = sd.Seq + 1
-	for v := range sd.subscriptions.Iter() {
-		go sd.notify(xapps, et, v.Val.(Subscription), sd.Seq)
-	}
-}
-
-func (sd *SubscriptionDispatcher) notify(xapps []Xapp, et EventType, s Subscription, seq int) error {
-	xappData, err := json.Marshal(xapps)
-	if err != nil {
-		Logger.Info("json.Marshal failed: %v", err)
-		return err
-	}
-
-	notif := SubscriptionNotif{Id: s.req.Id, Version: seq, EventType: string(et), XApps: string(xappData)}
-	jsonData, err := json.Marshal(notif)
-	if err != nil {
-		Logger.Info("json.Marshal failed: %v", err)
-		return err
-	}
-
-	// Execute the request with retry policy
-	return sd.retry(s, func() error {
-		Logger.Info("Posting notification to targetUrl=%s: %v", s.req.TargetUrl, notif)
-		resp, err := http.Post(s.req.TargetUrl, "application/json", bytes.NewBuffer(jsonData))
-		if err != nil {
-			Logger.Info("Posting to subscription failed: %v", err)
-			return err
-		}
-
-		if resp.StatusCode != http.StatusOK {
-			Logger.Info("Client returned error code: %d", resp.StatusCode)
-			return err
-		}
-
-		Logger.Info("subscription to '%s' dispatched, response code: %d", s.req.TargetUrl, resp.StatusCode)
-		return nil
-	})
-}
-
-func (sd *SubscriptionDispatcher) retry(s Subscription, fn func() error) error {
-	if err := fn(); err != nil {
-		// Todo: use exponential backoff, or similar mechanism
-		if s.req.MaxRetries--; s.req.MaxRetries > 0 {
-			time.Sleep(time.Duration(s.req.RetryTimer) * time.Second)
-			return sd.retry(s, fn)
-		}
-		sd.subscriptions.Remove(s.req.Id)
-		return err
-	}
-	return nil
-}
diff --git a/cmd/appmgr/subscriptions_test.go b/cmd/appmgr/subscriptions_test.go
deleted file mode 100755
index 5d79d00..0000000
--- a/cmd/appmgr/subscriptions_test.go
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	sdl "gerrit.oran-osc.org/r/ric-plt/sdlgo"
-	"github.com/spf13/viper"
-	"log"
-	"net"
-	"net/http"
-	"net/http/httptest"
-	"testing"
-)
-
-var resp SubscriptionResp
-
-// Test cases
-func TestNoSubscriptionsFound(t *testing.T) {
-	req, _ := http.NewRequest("GET", "/ric/v1/subscriptions", nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusOK, response.Code)
-	if body := response.Body.String(); body != "[]" {
-		t.Errorf("handler returned unexpected body: got %v want []", body)
-	}
-}
-
-func TestAddNewSubscription(t *testing.T) {
-	payload := []byte(`{"maxRetries": 3, "retryTimer": 5, "eventType":"Created", "targetUrl": "http://localhost:8087/xapps_handler"}`)
-	req, _ := http.NewRequest("POST", "/ric/v1/subscriptions", bytes.NewBuffer(payload))
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusCreated, response.Code)
-
-	json.NewDecoder(response.Body).Decode(&resp)
-	if resp.Version != 0 {
-		t.Errorf("Creating new subscription failed: %v", resp)
-	}
-}
-
-func TestGettAllSubscriptions(t *testing.T) {
-	req, _ := http.NewRequest("GET", "/ric/v1/subscriptions", nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusOK, response.Code)
-
-	var subscriptions []SubscriptionReq
-	json.NewDecoder(response.Body).Decode(&subscriptions)
-
-	verifySubscription(t, subscriptions[0], "http://localhost:8087/xapps_handler", 3, 5, "Created")
-}
-
-func TestGetSingleSubscription(t *testing.T) {
-	req, _ := http.NewRequest("GET", "/ric/v1/subscriptions/"+resp.Id, nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusOK, response.Code)
-
-	var subscription SubscriptionReq
-	json.NewDecoder(response.Body).Decode(&subscription)
-
-	verifySubscription(t, subscription, "http://localhost:8087/xapps_handler", 3, 5, "Created")
-}
-
-func TestUpdateSingleSubscription(t *testing.T) {
-	payload := []byte(`{"maxRetries": 11, "retryTimer": 22, "eventType":"Deleted", "targetUrl": "http://localhost:8088/xapps_handler"}`)
-
-	req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/"+resp.Id, bytes.NewBuffer(payload))
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusOK, response.Code)
-
-	var res SubscriptionResp
-	json.NewDecoder(response.Body).Decode(&res)
-	if res.Version != 0 {
-		t.Errorf("handler returned unexpected data: %v", resp)
-	}
-
-	// Check that the subscription is updated properly
-	req, _ = http.NewRequest("GET", "/ric/v1/subscriptions/"+resp.Id, nil)
-	response = executeRequest(req)
-	checkResponseCode(t, http.StatusOK, response.Code)
-
-	var subscription SubscriptionReq
-	json.NewDecoder(response.Body).Decode(&subscription)
-
-	verifySubscription(t, subscription, "http://localhost:8088/xapps_handler", 11, 22, "Deleted")
-}
-
-func TestDeleteSingleSubscription(t *testing.T) {
-	req, _ := http.NewRequest("DELETE", "/ric/v1/subscriptions/"+resp.Id, nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusNoContent, response.Code)
-
-	// Check that the subscription is removed properly
-	req, _ = http.NewRequest("GET", "/ric/v1/subscriptions/"+resp.Id, nil)
-	response = executeRequest(req)
-	checkResponseCode(t, http.StatusNotFound, response.Code)
-}
-
-func TestDeleteSingleSubscriptionFails(t *testing.T) {
-	req, _ := http.NewRequest("DELETE", "/ric/v1/subscriptions/invalidSubscriptionId", nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusNotFound, response.Code)
-}
-
-func TestAddSingleSubscriptionFailsBodyEmpty(t *testing.T) {
-	req, _ := http.NewRequest("POST", "/ric/v1/subscriptions/"+resp.Id, nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusMethodNotAllowed, response.Code)
-}
-
-func TestUpdateeSingleSubscriptionFailsBodyEmpty(t *testing.T) {
-	req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/"+resp.Id, nil)
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusMethodNotAllowed, response.Code)
-}
-
-func TestUpdateeSingleSubscriptionFailsInvalidId(t *testing.T) {
-	payload := []byte(`{"maxRetries": 11, "retryTimer": 22, "eventType":"Deleted", "targetUrl": "http://localhost:8088/xapps_handler"}`)
-
-	req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/invalidSubscriptionId"+resp.Id, bytes.NewBuffer(payload))
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusNotFound, response.Code)
-}
-
-func TestPublishXappAction(t *testing.T) {
-	payload := []byte(`{"maxRetries": 3, "retryTimer": 5, "eventType":"Created", "targetUrl": "http://127.0.0.1:8888"}`)
-	req, _ := http.NewRequest("POST", "/ric/v1/subscriptions", bytes.NewBuffer(payload))
-	response := executeRequest(req)
-
-	checkResponseCode(t, http.StatusCreated, response.Code)
-
-	// Create a RestApi server (simulating RM)
-	ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		fmt.Fprintln(w, "Hello, XM!")
-	}))
-
-	l, err := net.Listen("tcp", "127.0.0.1:8888")
-	if err != nil {
-		log.Fatal(err)
-	}
-	ts.Listener.Close()
-	ts.Listener = l
-	ts.Start()
-
-	defer ts.Close()
-
-	x.sd.Publish(xapp, EventType("created"))
-}
-
-func TestTeardown(t *testing.T) {
-	db := sdl.NewSdlInstance(viper.GetString("db.sessionNamespace"), sdl.NewDatabase())
-	db.RemoveAll()
-}
-
-func verifySubscription(t *testing.T, subscription SubscriptionReq, url string, retries int, timer int, event string) {
-	if subscription.TargetUrl != url {
-		t.Errorf("Unexpected url: got=%s expected=%s", subscription.TargetUrl, url)
-	}
-
-	if subscription.MaxRetries != retries {
-		t.Errorf("Unexpected retries: got=%d expected=%d", subscription.MaxRetries, retries)
-	}
-
-	if subscription.RetryTimer != timer {
-		t.Errorf("Unexpected timer: got=%d expected=%d", subscription.RetryTimer, timer)
-	}
-
-	if subscription.EventType != event {
-		t.Errorf("Unexpected event type: got=%s expected=%s", subscription.EventType, event)
-	}
-}
diff --git a/cmd/appmgr/types.go b/cmd/appmgr/types.go
deleted file mode 100755
index 1c9556d..0000000
--- a/cmd/appmgr/types.go
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
-==================================================================================
-  Copyright (c) 2019 AT&T Intellectual Property.
-  Copyright (c) 2019 Nokia
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-==================================================================================
-*/
-
-package main
-
-import (
-	"github.com/gorilla/mux"
-	cmap "github.com/orcaman/concurrent-map"
-	"net/http"
-)
-
-type CmdOptions struct {
-	hostAddr      *string
-	helmHost      *string
-	helmChartPath *string
-}
-
-type Resource struct {
-	Method      string
-	Url         string
-	HandlerFunc http.HandlerFunc
-}
-
-type Xapp struct {
-	Name      string         `json:"name"`
-	Status    string         `json:"status"`
-	Version   string         `json:"version"`
-	Instances []XappInstance `json:"instances"`
-}
-
-type XappInstance struct {
-	Name       string   `json:"name"`
-	Status     string   `json:"status"`
-	Ip         string   `json:"ip"`
-	Port       int      `json:"port"`
-	TxMessages []string `json:"txMessages"`
-	RxMessages []string `json:"rxMessages"`
-}
-
-type XappDeploy struct {
-	Name        string `json:"name"`
-	ConfigName  string `json:"configName, omitempty"`
-	Namespace   string `json:"namespace, omitempty"`
-	ServiceName string `json:"serviceName, omitempty"`
-	ImageRepo   string `json:"imageRepo, omitempty"`
-	Hostname    string `json:"hostname, omitempty"`
-}
-
-type XappManager struct {
-	router *mux.Router
-	helm   Helmer
-	cm     ConfigMapper
-	sd     SubscriptionDispatcher
-	opts   CmdOptions
-	ready  bool
-}
-
-type ConfigMapper interface {
-	UploadConfig() (cfg []XAppConfig)
-	GetConfigMap(m XappDeploy, c *interface{}) (err error)
-	CreateConfigMap(r XAppConfig) (errList []CMError, err error)
-	UpdateConfigMap(r XAppConfig) (errList []CMError, err error)
-	DeleteConfigMap(r XAppConfig) (cm interface{}, err error)
-	ReadSchema(name string, c *XAppConfig) (err error)
-	PurgeConfigMap(m XappDeploy) (cm interface{}, err error)
-	RestoreConfigMap(m XappDeploy, cm interface{}) (err error)
-	ReadConfigMap(name string, ns string, c *interface{}) (err error)
-	ApplyConfigMap(r XAppConfig, action string) (err error)
-	GetMessages(name string) (msgs MessageTypes)
-	GetNamespace(ns string) string
-	GetNamesFromHelmRepo() (names []string)
-}
-
-type Helmer interface {
-	SetCM(ConfigMapper)
-	Initialize()
-	Install(m XappDeploy) (xapp Xapp, err error)
-	Status(name string) (xapp Xapp, err error)
-	StatusAll() (xapps []Xapp, err error)
-	SearchAll() (xapps []string)
-	List() (xapps []string, err error)
-	Delete(name string) (xapp Xapp, err error)
-}
-
-type Helm struct {
-	host      string
-	chartPath string
-	initDone  bool
-	cm        ConfigMapper
-}
-
-type SubscriptionReq struct {
-	Id         string `json:"id"`
-	TargetUrl  string `json:"targetUrl"`
-	EventType  string `json:"eventType"`
-	MaxRetries int    `json:"maxRetries"`
-	RetryTimer int    `json:"retryTimer"`
-}
-
-type SubscriptionResp struct {
-	Id        string `json:"id"`
-	Version   int    `json:"version"`
-	EventType string `json:"eventType"`
-}
-
-type SubscriptionNotif struct {
-	Id        string `json:"id"`
-	Version   int    `json:"version"`
-	EventType string `json:"eventType"`
-	XApps     string `json:"xApps"`
-}
-
-type Subscription struct {
-	req  SubscriptionReq
-	resp SubscriptionResp
-}
-
-type SubscriptionDispatcher struct {
-	client        *http.Client
-	subscriptions cmap.ConcurrentMap
-	db            *DB
-	Seq           int
-}
-
-type MessageTypes struct {
-	TxMessages []string `json:"txMessages"`
-	RxMessages []string `json:"rxMessages"`
-}
-
-type EventType string
-
-const (
-	Created EventType = "created"
-	Updated EventType = "updated"
-	Deleted EventType = "deleted"
-)
-
-const (
-	MdclogErr   = 1 //! Error level log entry
-	MdclogWarn  = 2 //! Warning level log entry
-	MdclogInfo  = 3 //! Info level log entry
-	MdclogDebug = 4 //! Debug level log entry
-)
diff --git a/container-tag.yaml b/container-tag.yaml
old mode 100644
new mode 100755
index a704888..3724441
--- a/container-tag.yaml
+++ b/container-tag.yaml
@@ -1,4 +1,4 @@
 # The Jenkins job uses this string for the tag in the image name
 # for example nexus3.o-ran-sc.org:10004/my-image-name:my-tag
 ---
-tag: '0.1.10'
+tag: '0.2.0'
diff --git a/go.mod b/go.mod
index 6a22825..253e4a2 100644
--- a/go.mod
+++ b/go.mod
@@ -7,14 +7,25 @@
 	gerrit.oran-osc.org/r/ric-plt/sdlgo v0.0.0
 	github.com/BurntSushi/toml v0.3.1 // indirect
 	github.com/fsnotify/fsnotify v1.4.7
+	github.com/ghodss/yaml v1.0.0
+	github.com/go-openapi/errors v0.19.2
+	github.com/go-openapi/loads v0.19.4
+	github.com/go-openapi/runtime v0.19.7
+	github.com/go-openapi/spec v0.19.4
+	github.com/go-openapi/strfmt v0.19.3
+	github.com/go-openapi/swag v0.19.5
+	github.com/go-openapi/validate v0.19.4
 	github.com/gorilla/mux v1.7.1
+	github.com/jessevdk/go-flags v1.4.0
 	github.com/orcaman/concurrent-map v0.0.0-20190314100340-2693aad1ed75
 	github.com/segmentio/ksuid v1.0.2
 	github.com/spf13/viper v1.3.2
+	github.com/stretchr/testify v1.4.0
 	github.com/valyala/fastjson v1.4.1
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v1.1.0
+	golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
 )
 
 replace gerrit.oran-osc.org/r/ric-plt/sdlgo => gerrit.oran-osc.org/r/ric-plt/sdlgo.git v0.0.1
diff --git a/go.sum b/go.sum
index e0539a4..3ffdc4b 100644
--- a/go.sum
+++ b/go.sum
@@ -5,27 +5,112 @@
 gerrit.oran-osc.org/r/ric-plt/sdlgo.git v0.0.1/go.mod h1:LVhkNS82IofJTBK/VYPKiYed9MG/3OFwvWC6MGSDw1w=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
+github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI=
+github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
+github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY=
+github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
+github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
+github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
+github.com/go-openapi/loads v0.19.4 h1:5I4CCSqoWzT+82bBkNIvmLc0UOsoKKQ4Fz+3VxOB7SY=
+github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
+github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
+github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
+github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
+github.com/go-openapi/runtime v0.19.7 h1:b2zcE9GCjDVtguugU7+S95vkHjwQEjz/lB+8LOuA9Nw=
+github.com/go-openapi/runtime v0.19.7/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
+github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
+github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
+github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA=
+github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
+github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
+github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
+github.com/go-openapi/validate v0.19.4 h1:LGjO87VyXY3bIKjlYpXSFuLRG2mTeuYlZyeNwFFWpyM=
+github.com/go-openapi/validate v0.19.4/go.mod h1:BkJ0ZmXui7yB0bJXWSXgLPNTmbLVeX/3D1xn/N9mMUM=
 github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
 github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU=
 github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -35,6 +120,7 @@
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/orcaman/concurrent-map v0.0.0-20190314100340-2693aad1ed75 h1:IV56VwUb9Ludyr7s53CMuEh4DdTnnQtEPLEgLyJ0kHI=
 github.com/orcaman/concurrent-map v0.0.0-20190314100340-2693aad1ed75/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -53,9 +139,15 @@
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/valyala/fastjson v1.4.1 h1:hrltpHpIpkaxll8QltMU8c3QZ5+qIiCL8yKqPFJI/yE=
 github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7EFWPsvP8o=
@@ -66,18 +158,44 @@
 github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
 github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs=
+go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@@ -85,3 +203,5 @@
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/cmd/appmgr/config.go b/pkg/appmgr/appmgr.go
similarity index 74%
rename from cmd/appmgr/config.go
rename to pkg/appmgr/appmgr.go
index 5e1975e..28e98f9 100755
--- a/cmd/appmgr/config.go
+++ b/pkg/appmgr/appmgr.go
@@ -17,16 +17,27 @@
 ==================================================================================
 */
 
-package main
+package appmgr
 
 import (
 	"flag"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/logger"
 	"github.com/fsnotify/fsnotify"
 	"github.com/spf13/viper"
 	"log"
+	"net/http"
 )
 
-const DEFAULT_CONFIG_FILE = "config/appmgr.yaml"
+const DEFAULT_CONFIG_FILE = "../../config/appmgr.yaml"
+
+var Logger *logger.Log
+
+func LogRestRequests(inner http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		inner.ServeHTTP(w, r)
+		Logger.Info("Logger: method=%s url=%s", r.Method, r.URL.RequestURI())
+	})
+}
 
 func parseCmd() string {
 	var fileName *string
@@ -36,9 +47,15 @@
 	return *fileName
 }
 
+func watch() {
+	viper.WatchConfig()
+	viper.OnConfigChange(func(e fsnotify.Event) {
+		log.Println("config file changed ", e.Name)
+	})
+}
+
 func loadConfig() {
 	viper.SetConfigFile(parseCmd())
-
 	if err := viper.ReadInConfig(); err != nil {
 		log.Fatalf("Error reading config file, %s", err)
 	}
@@ -48,9 +65,8 @@
 	watch()
 }
 
-func watch() {
-	viper.WatchConfig()
-	viper.OnConfigChange(func(e fsnotify.Event) {
-		log.Println("config file changed ", e.Name)
-	})
+func Init() {
+	loadConfig()
+	Logger = logger.NewLogger("appmgr")
+	Logger.SetMdc("xm", "0.2.0")
 }
diff --git a/pkg/appmgr/types.go b/pkg/appmgr/types.go
new file mode 100755
index 0000000..afdb0b4
--- /dev/null
+++ b/pkg/appmgr/types.go
@@ -0,0 +1,90 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package appmgr
+
+import (
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+)
+
+type ConfigMap struct {
+	Kind       string      `json:"kind"`
+	ApiVersion string      `json:"apiVersion"`
+	Data       interface{} `json:"data"`
+	Metadata   CMMetadata  `json:"metadata"`
+}
+
+type CMMetadata struct {
+	Name      string `json:"name"`
+	Namespace string `json:"namespace"`
+}
+
+type ConfigMapper interface {
+	UploadConfig() (cfg []models.XAppConfig)
+	GetConfigMap(m models.XappDescriptor, c *interface{}) (err error)
+	CreateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error)
+	UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error)
+	DeleteConfigMap(r models.XAppConfig) (cm interface{}, err error)
+	ReadSchema(name string, c *models.XAppConfig) (err error)
+	PurgeConfigMap(m models.XappDescriptor) (cm interface{}, err error)
+	RestoreConfigMap(m models.XappDescriptor, cm interface{}) (err error)
+	ReadConfigMap(name string, ns string, c *interface{}) (err error)
+	ApplyConfigMap(r models.XAppConfig, action string) (err error)
+	GetMessages(name string) (msgs MessageTypes)
+	GetNamespace(ns string) string
+	GetNamesFromHelmRepo() (names []string)
+}
+
+type Helmer interface {
+	SetCM(ConfigMapper)
+	Initialize()
+	Install(m models.XappDescriptor) (xapp models.Xapp, err error)
+	Status(name string) (xapp models.Xapp, err error)
+	StatusAll() (xapps models.AllDeployedXapps, err error)
+	SearchAll() (xapps []string)
+	List() (xapps []string, err error)
+	Delete(name string) (xapp models.Xapp, err error)
+}
+
+type Helm struct {
+	host      string
+	chartPath string
+	initDone  bool
+	cm        ConfigMapper
+}
+
+type MessageTypes struct {
+	TxMessages []string `json:"txMessages"`
+	RxMessages []string `json:"rxMessages"`
+}
+
+type EventType string
+
+const (
+	Created EventType = "created"
+	Updated EventType = "updated"
+	Deleted EventType = "deleted"
+)
+
+const (
+	MdclogErr   = 1 //! Error level log entry
+	MdclogWarn  = 2 //! Warning level log entry
+	MdclogInfo  = 3 //! Info level log entry
+	MdclogDebug = 4 //! Debug level log entry
+)
diff --git a/pkg/cm/cm.go b/pkg/cm/cm.go
new file mode 100755
index 0000000..af5372d
--- /dev/null
+++ b/pkg/cm/cm.go
@@ -0,0 +1,306 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package cm
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/spf13/viper"
+	"github.com/valyala/fastjson"
+	"github.com/xeipuuv/gojsonschema"
+	"io/ioutil"
+	"os"
+	"path"
+	"regexp"
+	"strings"
+	"time"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
+)
+
+type CM struct{}
+
+func NewCM() *CM {
+	return &CM{}
+}
+
+func (cm *CM) UploadConfig() (cfg models.AllXappConfig) {
+	ns := cm.GetNamespace("")
+	for _, name := range cm.GetNamesFromHelmRepo() {
+		if name == "appmgr" {
+			continue
+		}
+
+		c := models.XAppConfig{
+			Metadata: &models.ConfigMetadata{Name: &name, Namespace: ns, ConfigName: cm.GetConfigMapName(name, ns)},
+		}
+
+		err := cm.ReadSchema(name, &c)
+		if err != nil {
+			continue
+		}
+
+		err = cm.ReadConfigMap(c.Metadata.ConfigName, ns, &c.Config)
+		if err != nil {
+			appmgr.Logger.Info("No active configMap found, using default!")
+		}
+
+		cfg = append(cfg, &c)
+	}
+	return
+}
+
+func (cm *CM) ReadSchema(name string, c *models.XAppConfig) (err error) {
+	if err = cm.FetchChart(name); err != nil {
+		return
+	}
+
+	tarDir := viper.GetString("xapp.tarDir")
+	err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor)
+	if err != nil {
+		return
+	}
+
+	err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Config)
+	if err != nil {
+		return
+	}
+
+	if err = os.RemoveAll(path.Join(tarDir, name)); err != nil {
+		appmgr.Logger.Info("RemoveAll failed: %v", err)
+	}
+
+	return
+}
+
+func (cm *CM) ReadConfigMap(ConfigName string, ns string, c *interface{}) (err error) {
+	args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, ConfigName)
+	configMapJson, err := util.KubectlExec(args)
+	if err != nil {
+		return
+	}
+
+	err = json.Unmarshal([]byte(configMapJson), &c)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+func (cm *CM) ApplyConfigMap(r models.XAppConfig, action string) (err error) {
+	c := appmgr.ConfigMap{
+		Kind:       "ConfigMap",
+		ApiVersion: "v1",
+		Metadata:   appmgr.CMMetadata{Name: *r.Metadata.Name, Namespace: r.Metadata.Namespace},
+		Data:       r.Config,
+	}
+
+	cmJson, err := json.Marshal(c.Data)
+	if err != nil {
+		appmgr.Logger.Info("Config marshalling failed: %v", err)
+		return
+	}
+
+	cmFile := viper.GetString("xapp.tmpConfig")
+	err = ioutil.WriteFile(cmFile, cmJson, 0644)
+	if err != nil {
+		appmgr.Logger.Info("WriteFile failed: %v", err)
+		return
+	}
+
+	cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl %s -f -"
+	args := fmt.Sprintf(cmd, r.Metadata.Namespace, r.Metadata.ConfigName, cmFile, action)
+	_, err = util.KubectlExec(args)
+	if err != nil {
+		return
+	}
+	appmgr.Logger.Info("Configmap changes done!")
+
+	return
+}
+
+func (cm *CM) GetConfigMap(m models.XappDescriptor, c *interface{}) (err error) {
+	return cm.ReadConfigMap(cm.GetConfigMapName(*m.XappName, m.Namespace), m.Namespace, c)
+}
+
+func (cm *CM) CreateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+	if errList, err = cm.Validate(r); err != nil {
+		return
+	}
+	err = cm.ApplyConfigMap(r, "create")
+	return
+}
+
+func (cm *CM) UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+	if errList, err = cm.Validate(r); err != nil {
+		return
+	}
+
+	// Re-create the configmap with the new parameters
+	err = cm.ApplyConfigMap(r, "apply")
+	return
+}
+
+func (cm *CM) DeleteConfigMap(r models.ConfigMetadata) (c interface{}, err error) {
+	err = cm.ReadConfigMap(r.ConfigName, r.Namespace, &c)
+	if err == nil {
+		args := fmt.Sprintf(" delete configmap --namespace=%s %s", r.Namespace, r.ConfigName)
+		_, err = util.KubectlExec(args)
+	}
+	return
+}
+
+func (cm *CM) PurgeConfigMap(m models.XappDescriptor) (c interface{}, err error) {
+	md := models.ConfigMetadata{Name: m.XappName, Namespace: m.Namespace, ConfigName: cm.GetConfigMapName(*m.XappName, m.Namespace)}
+
+	return cm.DeleteConfigMap(md)
+}
+
+func (cm *CM) RestoreConfigMap(m models.XappDescriptor, c interface{}) (err error) {
+	md := &models.ConfigMetadata{Name: m.XappName, Namespace: m.Namespace, ConfigName: cm.GetConfigMapName(*m.XappName, m.Namespace)}
+	time.Sleep(time.Duration(10 * time.Second))
+
+	return cm.ApplyConfigMap(models.XAppConfig{Metadata: md, Config: c}, "create")
+}
+
+func (cm *CM) GetNamesFromHelmRepo() (names []string) {
+	rname := viper.GetString("helm.repo-name")
+
+	cmdArgs := strings.Join([]string{"search ", rname}, "")
+	out, err := util.HelmExec(cmdArgs)
+	if err != nil {
+		return
+	}
+
+	re := regexp.MustCompile(rname + `/.*`)
+	result := re.FindAllStringSubmatch(string(out), -1)
+	if result != nil {
+		var tmp string
+		for _, v := range result {
+			fmt.Sscanf(v[0], "%s", &tmp)
+			names = append(names, strings.Split(tmp, "/")[1])
+		}
+	}
+	return names
+}
+
+func (cm *CM) Validate(req models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+	c := models.XAppConfig{}
+	err = cm.ReadSchema(*req.Metadata.Name, &c)
+	if err != nil {
+		appmgr.Logger.Info("No schema file found for '%s', aborting ...", *req.Metadata.Name)
+		return
+	}
+	return cm.doValidate(c.Descriptor, req.Config)
+}
+
+func (cm *CM) doValidate(schema, cfg interface{}) (errList models.ConfigValidationErrors, err error) {
+	schemaLoader := gojsonschema.NewGoLoader(schema)
+	documentLoader := gojsonschema.NewGoLoader(cfg)
+
+	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
+	if err != nil {
+		appmgr.Logger.Info("Validation failed: %v", err)
+		return
+	}
+
+	if result.Valid() == false {
+		appmgr.Logger.Info("The document is not valid, Errors: %v", result.Errors())
+		for _, desc := range result.Errors() {
+			field := desc.Field()
+			validationError := desc.Description()
+			errList = append(errList, &models.ConfigValidationError{Field: &field, Error: &validationError})
+		}
+		return errList, errors.New("Validation failed!")
+	}
+	return
+}
+
+func (cm *CM) ReadFile(name string, data interface{}) (err error) {
+	f, err := ioutil.ReadFile(name)
+	if err != nil {
+		appmgr.Logger.Info("Reading '%s' file failed: %v", name, err)
+		return
+	}
+
+	err = json.Unmarshal(f, &data)
+	if err != nil {
+		appmgr.Logger.Info("Unmarshalling '%s' file failed: %v", name, err)
+		return
+	}
+
+	return
+}
+
+func (cm *CM) FetchChart(name string) (err error) {
+	tarDir := viper.GetString("xapp.tarDir")
+	repo := viper.GetString("helm.repo-name")
+	fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
+
+	_, err = util.HelmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
+	return
+}
+
+func (cm *CM) GetMessages(name string) (msgs appmgr.MessageTypes) {
+	appmgr.Logger.Info("Fetching tx/rx messages for: %s", name)
+
+	ns := cm.GetNamespace("")
+	args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
+	out, err := util.KubectlExec(args)
+	if err != nil {
+		return
+	}
+
+	var p fastjson.Parser
+	v, err := p.Parse(string(out))
+	if err != nil {
+		appmgr.Logger.Info("fastjson.Parser for '%s' failed: %v", name, err)
+		return
+	}
+
+	for _, m := range v.GetArray("rmr", "txMessages") {
+		msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
+	}
+	for _, m := range v.GetArray("rmr", "rxMessages") {
+		msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
+	}
+
+	return
+}
+
+func (cm *CM) GetConfigMapName(xappName, namespace string) string {
+	return " configmap-" + namespace + "-" + xappName + "-appconfig"
+}
+
+func (cm *CM) GetNamespace(ns string) string {
+	if ns != "" {
+		return ns
+	}
+
+	ns = viper.GetString("xapp.namespace")
+	if ns == "" {
+		ns = "ricxapp"
+	}
+	return ns
+}
diff --git a/pkg/cm/cm_test.go b/pkg/cm/cm_test.go
new file mode 100755
index 0000000..be600bb
--- /dev/null
+++ b/pkg/cm/cm_test.go
@@ -0,0 +1,312 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package cm
+
+import (
+	"encoding/json"
+	"errors"
+	"os"
+	"reflect"
+	"testing"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
+)
+
+var helmSearchOutput = `
+helm-repo/anr           0.0.1           1.0             Helm Chart for Nokia ANR (Automatic Neighbour Relation) xAPP
+helm-repo/appmgr        0.0.2           1.0             Helm Chart for xAppManager
+helm-repo/dualco        0.0.1           1.0             Helm Chart for Nokia dualco xAPP
+helm-repo/reporter      0.0.1           1.0             Helm Chart for Reporting xAPP
+helm-repo/uemgr         0.0.1           1.0             Helm Chart for Nokia uemgr xAPP
+`
+
+var kubectlConfigmapOutput = `
+{
+    "local": {
+        "host": ":8080"
+    },
+    "logger": {
+        "level": 3
+    },
+    "rmr": {
+       "protPort": "tcp:4560",
+       "maxSize": 2072,
+       "numWorkers": 1,
+       "txMessages": ["RIC_X2_LOAD_INFORMATION"],
+       "rxMessages": ["RIC_X2_LOAD_INFORMATION"]
+    },
+    "db": {
+        "namespace": "ricxapp",
+        "host": "dbaas",
+        "port": 6379
+    }
+}
+`
+
+type ConfigSample struct {
+	Level int
+	Host  string
+}
+
+type MockedConfigMapper struct {
+}
+
+func (cm *MockedConfigMapper) ReadSchema(name string, c *models.XAppConfig) (err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) UploadConfig() (cfg []models.XAppConfig) {
+	return
+}
+
+func (cm *MockedConfigMapper) CreateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) GetConfigMap(m models.XappDescriptor, c *interface{}) (err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) DeleteConfigMap(r models.XAppConfig) (c interface{}, err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) PurgeConfigMap(m models.XappDescriptor) (c interface{}, err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) RestoreConfigMap(m models.XappDescriptor, c interface{}) (err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) ReadConfigMap(name string, ns string, c *interface{}) (err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) ApplyConfigMap(r models.XAppConfig, action string) (err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) FetchChart(name string) (err error) {
+	return
+}
+
+func (cm *MockedConfigMapper) GetMessages(name string) (msgs appmgr.MessageTypes) {
+	return
+}
+
+func (cm *MockedConfigMapper) GetNamespace(ns string) (n string) {
+	return
+}
+
+func (cm *MockedConfigMapper) GetNamesFromHelmRepo() (names []string) {
+	return
+}
+
+// Test cases
+func TestMain(m *testing.M) {
+	appmgr.Init()
+	appmgr.Logger.SetLevel(0)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestGetMessages(t *testing.T) {
+	expectedMsgs := appmgr.MessageTypes{
+		TxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
+		RxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
+	}
+
+	util.KubectlExec = func(args string) (out []byte, err error) {
+		return []byte(kubectlConfigmapOutput), nil
+	}
+
+	result := NewCM().GetMessages("dummy-xapp")
+	if !reflect.DeepEqual(result, expectedMsgs) {
+		t.Errorf("TestGetMessages failed: expected: %v, got: %v", expectedMsgs, result)
+	}
+}
+
+func TestHelmNamespace(t *testing.T) {
+	if NewCM().GetNamespace("pltxapp") != "pltxapp" {
+		t.Errorf("getNamespace failed!")
+	}
+
+	if NewCM().GetNamespace("") != "ricxapp" {
+		t.Errorf("getNamespace failed!")
+	}
+}
+
+func TestFetchChartFails(t *testing.T) {
+	if NewCM().FetchChart("dummy-xapp") == nil {
+		t.Errorf("TestFetchChart failed!")
+	}
+}
+
+func TestFetchChartSuccess(t *testing.T) {
+	util.HelmExec = func(args string) (out []byte, err error) {
+		return
+	}
+
+	if NewCM().FetchChart("dummy-xapp") != nil {
+		t.Errorf("TestFetchChart failed!")
+	}
+}
+
+func TestGetNamesFromHelmRepoSuccess(t *testing.T) {
+	expectedResult := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
+	util.HelmExec = func(args string) (out []byte, err error) {
+		return []byte(helmSearchOutput), nil
+	}
+
+	names := NewCM().GetNamesFromHelmRepo()
+	if !reflect.DeepEqual(names, expectedResult) {
+		t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
+	}
+}
+
+func TestGetNamesFromHelmRepoFailure(t *testing.T) {
+	expectedResult := []string{}
+	util.HelmExec = func(args string) (out []byte, err error) {
+		return []byte(helmSearchOutput), errors.New("Command failed!")
+	}
+
+	names := NewCM().GetNamesFromHelmRepo()
+	if names != nil {
+		t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
+	}
+}
+
+func TestApplyConfigMapSuccess(t *testing.T) {
+	name := "dummy-xapp"
+	m := models.ConfigMetadata{Name: &name, Namespace: "ricxapp"}
+	s := ConfigSample{5, "localhost"}
+
+	util.KubectlExec = func(args string) (out []byte, err error) {
+		return []byte(`{"logger": {"level": 2}}`), nil
+	}
+
+	err := NewCM().ApplyConfigMap(models.XAppConfig{Metadata: &m, Config: s}, "create")
+	if err != nil {
+		t.Errorf("ApplyConfigMap failed: %v", err)
+	}
+}
+
+func TestRestoreConfigMapSuccess(t *testing.T) {
+	name := "dummy-xapp"
+	m := models.XappDescriptor{XappName: &name, Namespace: "ricxapp"}
+	s := ConfigSample{5, "localhost"}
+
+	util.KubectlExec = func(args string) (out []byte, err error) {
+		return []byte(`{"logger": {"level": 2}}`), nil
+	}
+
+	err := NewCM().RestoreConfigMap(m, s)
+	if err != nil {
+		t.Errorf("RestoreConfigMap failed: %v", err)
+	}
+}
+
+func TestDeleteConfigMapSuccess(t *testing.T) {
+	util.HelmExec = func(args string) (out []byte, err error) {
+		return []byte("ok"), nil
+	}
+
+	util.KubectlExec = func(args string) (out []byte, err error) {
+		return []byte(`{"logger": {"level": 2}}`), nil
+	}
+
+	validationErrors, err := NewCM().DeleteConfigMap(models.ConfigMetadata{})
+	if err != nil {
+		t.Errorf("DeleteConfigMap failed: %v -> %v", err, validationErrors)
+	}
+}
+
+func TestPurgeConfigMapSuccess(t *testing.T) {
+	util.HelmExec = func(args string) (out []byte, err error) {
+		return []byte("ok"), nil
+	}
+
+	util.KubectlExec = func(args string) (out []byte, err error) {
+		return []byte(`{"logger": {"level": 2}}`), nil
+	}
+
+	name := "dummy-xapp"
+	validationErrors, err := NewCM().PurgeConfigMap(models.XappDescriptor{XappName: &name})
+	if err != nil {
+		t.Errorf("PurgeConfigMap failed: %v -> %v", err, validationErrors)
+	}
+}
+
+func TestCreateConfigMapFails(t *testing.T) {
+	name := "dummy-xapp"
+	validationErrors, err := NewCM().CreateConfigMap(models.XAppConfig{Metadata: &models.ConfigMetadata{Name: &name}})
+	if err == nil {
+		t.Errorf("CreateConfigMap failed: %v -> %v", err, validationErrors)
+	}
+}
+
+func TestUpdateConfigMapFails(t *testing.T) {
+	name := "dummy-xapp"
+	validationErrors, err := NewCM().UpdateConfigMap(models.XAppConfig{Metadata: &models.ConfigMetadata{Name: &name}})
+	if err == nil {
+		t.Errorf("CreateConfigMap failed: %v -> %v", err, validationErrors)
+	}
+}
+
+func TestValidationSuccess(t *testing.T) {
+	var d interface{}
+	var cfg map[string]interface{}
+	err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": 3}}`), &cfg)
+
+	err = NewCM().ReadFile("../../test/schema.json", &d)
+	if err != nil {
+		t.Errorf("ReadFile failed: %v -> %v", err, d)
+	}
+
+	feedback, err := NewCM().doValidate(d, cfg)
+	if err != nil {
+		t.Errorf("doValidate failed: %v -> %v", err, feedback)
+	}
+}
+
+func TestValidationFails(t *testing.T) {
+	var d interface{}
+	var cfg map[string]interface{}
+	err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": "INVALID"}}`), &cfg)
+
+	err = NewCM().ReadFile("../../test/schema.json", &d)
+	if err != nil {
+		t.Errorf("ConfigMetadata failed: %v -> %v", err, d)
+	}
+
+	feedback, err := NewCM().doValidate(d, cfg)
+	if err == nil {
+		t.Errorf("doValidate should faile but didn't: %v -> %v", err, feedback)
+	}
+	appmgr.Logger.Debug("Feedbacks: %v", feedback)
+}
diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go
new file mode 100755
index 0000000..a3d7b18
--- /dev/null
+++ b/pkg/helm/helm.go
@@ -0,0 +1,368 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package helm
+
+import (
+	"fmt"
+	"github.com/ghodss/yaml"
+	"github.com/spf13/viper"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/cm"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
+)
+
+type Helm struct {
+	initDone bool
+	cm       *cm.CM
+}
+
+func NewHelm() *Helm {
+	return &Helm{initDone: false, cm: cm.NewCM()}
+}
+
+func (h *Helm) Initialize() {
+	if h.initDone == true {
+		return
+	}
+
+	for {
+		if _, err := h.Init(); err == nil {
+			appmgr.Logger.Info("Helm init done successfully!")
+			break
+		}
+		appmgr.Logger.Info("helm init failed, retyring ...")
+		time.Sleep(time.Duration(10) * time.Second)
+	}
+
+	for {
+		if _, err := h.AddRepo(); err == nil {
+			appmgr.Logger.Info("Helm repo added successfully")
+			break
+		}
+		appmgr.Logger.Info("Helm repo addition failed, retyring ...")
+		time.Sleep(time.Duration(10) * time.Second)
+	}
+	h.initDone = true
+}
+
+func (h *Helm) Run(args string) (out []byte, err error) {
+	return util.HelmExec(args)
+}
+
+// API functions
+func (h *Helm) Init() (out []byte, err error) {
+	if err := h.AddTillerEnv(); err != nil {
+		return out, err
+	}
+
+	return util.HelmExec(strings.Join([]string{"init -c --skip-refresh"}, ""))
+}
+
+func (h *Helm) AddRepo() (out []byte, err error) {
+	// Get helm repo user name and password from files mounted by secret object
+	credFile, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
+	if err != nil {
+		appmgr.Logger.Info("helm_repo_username ReadFile failed: %v", err.Error())
+		return
+	}
+	username := " --username " + string(credFile)
+
+	credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
+	if err != nil {
+		appmgr.Logger.Info("helm_repo_password ReadFile failed: %v", err.Error())
+		return
+	}
+	pwd := " --password " + string(credFile)
+
+	rname := viper.GetString("helm.repo-name")
+	repo := viper.GetString("helm.repo")
+
+	return util.HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
+}
+
+func (h *Helm) Install(m models.XappDescriptor) (xapp models.Xapp, err error) {
+	var c interface{}
+	m.Namespace = h.cm.GetNamespace(m.Namespace)
+
+	out, err := h.Run(strings.Join([]string{"repo update "}, ""))
+	if err != nil {
+		return
+	}
+
+	if err = h.cm.GetConfigMap(m, &c); err != nil {
+		out, err = h.Run(h.GetInstallArgs(m, false))
+		if err != nil {
+			return
+		}
+		return h.ParseStatus(*m.XappName, string(out))
+	}
+
+	// ConfigMap exists, try to override
+	out, err = h.Run(h.GetInstallArgs(m, true))
+	if err == nil {
+		return h.ParseStatus(*m.XappName, string(out))
+	}
+
+	c, cmErr := h.cm.PurgeConfigMap(m)
+	out, err = h.Run(h.GetInstallArgs(m, false))
+	if err != nil {
+		return
+	}
+
+	if cmErr == nil {
+		cmErr = h.cm.RestoreConfigMap(m, c)
+	}
+	return h.ParseStatus(*m.XappName, string(out))
+}
+
+func (h *Helm) Status(name string) (xapp models.Xapp, err error) {
+	out, err := h.Run(strings.Join([]string{"status ", name}, ""))
+	if err != nil {
+		appmgr.Logger.Info("Getting xapps status: %v", err.Error())
+		return
+	}
+
+	return h.ParseStatus(name, string(out))
+}
+
+func (h *Helm) StatusAll() (xapps models.AllDeployedXapps, err error) {
+	xappNameList, err := h.List()
+	if err != nil {
+		appmgr.Logger.Info("Helm list failed: %v", err.Error())
+		return
+	}
+
+	return h.parseAllStatus(xappNameList)
+}
+
+func (h *Helm) List() (names []string, err error) {
+	ns := h.cm.GetNamespace("")
+	out, err := h.Run(strings.Join([]string{"list --all --deployed --output yaml --namespace=", ns}, ""))
+	if err != nil {
+		appmgr.Logger.Info("Listing deployed xapps failed: %v", err.Error())
+		return
+	}
+
+	return h.GetNames(string(out))
+}
+
+func (h *Helm) SearchAll() models.AllDeployableXapps {
+	return h.cm.GetNamesFromHelmRepo()
+}
+
+func (h *Helm) Delete(name string) (xapp models.Xapp, err error) {
+	xapp, err = h.Status(name)
+	if err != nil {
+		appmgr.Logger.Info("Fetching xapp status failed: %v", err.Error())
+		return
+	}
+
+	_, err = h.Run(strings.Join([]string{"del --purge ", name}, ""))
+	return xapp, err
+}
+
+func (h *Helm) Fetch(name, tarDir string) error {
+	if strings.HasSuffix(os.Args[0], ".test") {
+		return nil
+	}
+
+	rname := viper.GetString("helm.repo-name") + "/"
+
+	_, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, ""))
+	return err
+}
+
+// Helper functions
+func (h *Helm) GetVersion(name string) (version string) {
+	ns := h.cm.GetNamespace("")
+	out, err := h.Run(strings.Join([]string{"list --deployed --output yaml --namespace=", ns, " ", name}, ""))
+	if err != nil {
+		return
+	}
+
+	var re = regexp.MustCompile(`AppVersion: .*`)
+	ver := re.FindStringSubmatch(string(out))
+	if ver != nil {
+		version = strings.Split(ver[0], ": ")[1]
+		version, _ = strconv.Unquote(version)
+	}
+
+	return
+}
+
+func (h *Helm) GetState(out string) (status string) {
+	re := regexp.MustCompile(`STATUS: .*`)
+	result := re.FindStringSubmatch(string(out))
+	if result != nil {
+		status = strings.ToLower(strings.Split(result[0], ": ")[1])
+	}
+
+	return
+}
+
+func (h *Helm) GetAddress(out string) (ip, port string) {
+	var tmp string
+	re := regexp.MustCompile(`ClusterIP.*`)
+	addr := re.FindStringSubmatch(string(out))
+	if addr != nil {
+		fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port)
+	}
+
+	return
+}
+
+func (h *Helm) GetEndpointInfo(name string) (ip string, port int) {
+	ns := h.cm.GetNamespace("")
+	args := fmt.Sprintf(" get endpoints -o=jsonpath='{.subsets[*].addresses[*].ip}' service-%s-%s-rmr -n %s", ns, name, ns)
+	out, err := util.KubectlExec(args)
+	if err != nil {
+		return
+	}
+	appmgr.Logger.Info("Endpoint IP address of %s: %s", name, string(out))
+	return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), 4560
+}
+
+func (h *Helm) GetNames(out string) (names []string, err error) {
+	re := regexp.MustCompile(`Name: .*`)
+	result := re.FindAllStringSubmatch(out, -1)
+	if result == nil {
+		return
+	}
+
+	for _, v := range result {
+		xappName := strings.Split(v[0], ": ")[1]
+		if strings.Contains(xappName, "appmgr") == false {
+			names = append(names, xappName)
+		}
+	}
+	return names, nil
+}
+
+func (h *Helm) FillInstanceData(name string, out string, xapp *models.Xapp, msgs appmgr.MessageTypes) {
+	ip, port := h.GetEndpointInfo(name)
+	if ip == "" {
+		appmgr.Logger.Info("Endpoint IP address not found, using CluserIP")
+		ip, _ = h.GetAddress(out)
+	}
+
+	var tmp string
+	r := regexp.MustCompile(`.*(?s)(Running|Pending|Succeeded|Failed|Unknown).*?\r?\n\r?\n`)
+	result := r.FindStringSubmatch(string(out))
+	if result == nil {
+		return
+	}
+
+	re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
+	resources := re.FindAllStringSubmatch(string(result[0]), -1)
+	if resources != nil {
+		for _, v := range resources {
+			var x models.XappInstance
+			var name string
+			fmt.Sscanf(v[0], "%s %s %s", &name, &tmp, &x.Status)
+			x.Name = &name
+			x.Status = strings.ToLower(x.Status)
+			x.IP = ip
+			x.Port = int64(port)
+			x.TxMessages = msgs.TxMessages
+			x.RxMessages = msgs.RxMessages
+			xapp.Instances = append(xapp.Instances, &x)
+		}
+	}
+}
+
+func (h *Helm) ParseStatus(name string, out string) (xapp models.Xapp, err error) {
+	xapp.Name = &name
+	xapp.Version = h.GetVersion(name)
+	xapp.Status = h.GetState(out)
+
+	h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
+	return
+}
+
+func (h *Helm) parseAllStatus(names []string) (xapps models.AllDeployedXapps, err error) {
+	xapps = models.AllDeployedXapps{}
+	for _, name := range names {
+		err := h.cm.ReadSchema(name, &models.XAppConfig{})
+		if err != nil {
+			continue
+		}
+
+		x, err := h.Status(name)
+		if err == nil {
+			xapps = append(xapps, &x)
+		}
+	}
+	return
+}
+
+func (h *Helm) AddTillerEnv() (err error) {
+	service := viper.GetString("helm.tiller-service")
+	namespace := viper.GetString("helm.tiller-namespace")
+	port := viper.GetString("helm.tiller-port")
+
+	if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil {
+		appmgr.Logger.Info("Tiller Env Setting Failed: %v", err.Error())
+	}
+	return err
+}
+
+func (h *Helm) GetInstallArgs(x models.XappDescriptor, cmOverride bool) (args string) {
+	args = args + " --namespace=" + x.Namespace
+	if x.HelmVersion != "" {
+		args = args + " --version=" + x.HelmVersion
+	}
+
+	if x.ReleaseName != "" {
+		args = args + " --name=" + x.ReleaseName
+	} else {
+		args = args + " --name=" + *x.XappName
+	}
+
+	if cmOverride == true {
+		args = args + " --set ricapp.appconfig.override=" + *x.XappName + "-appconfig"
+	}
+
+	if x.OverrideFile != nil {
+		if overrideYaml, err := yaml.JSONToYAML([]byte(x.OverrideFile.(string))); err == nil {
+			err = ioutil.WriteFile("/tmp/appmgr_override.yaml", overrideYaml, 0644)
+			if err != nil {
+				appmgr.Logger.Info("ioutil.WriteFile(/tmp/appmgr_override.yaml) failed: %v", err)
+			} else {
+				args = args + " -f=/tmp/appmgr_override.yaml"
+			}
+		} else {
+			appmgr.Logger.Info("yaml.JSONToYAML failed: %v", err)
+		}
+	}
+
+	repoName := viper.GetString("helm.repo-name")
+	if repoName == "" {
+		repoName = "helm-repo"
+	}
+	return fmt.Sprintf("install %s/%s %s", repoName, *x.XappName, args)
+}
diff --git a/pkg/helm/helm_test.go b/pkg/helm/helm_test.go
new file mode 100755
index 0000000..fb72358
--- /dev/null
+++ b/pkg/helm/helm_test.go
@@ -0,0 +1,181 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package helm
+
+import (
+	"os"
+	"reflect"
+	"strconv"
+	"testing"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
+)
+
+var helmStatusOutput = `
+LAST DEPLOYED: Sat Mar  9 06:50:45 2019
+NAMESPACE: default
+STATUS: DEPLOYED
+
+RESOURCES:
+==> v1/Pod(related)
+NAME                        READY  STATUS   RESTARTS  AGE
+dummy-xapp-8984fc9fd-bkcbp  1/1    Running  0         55m
+dummy-xapp-8984fc9fd-l6xch  1/1    Running  0         55m
+dummy-xapp-8984fc9fd-pp4hg  1/1    Running  0         55m
+
+==> v1/Service
+NAME                         TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)  AGE
+dummy-xapp-dummy-xapp-chart  ClusterIP  10.102.184.212  <none>       80/TCP   55m
+
+==> v1beta1/Deployment
+NAME        READY  UP-TO-DATE  AVAILABLE  AGE
+dummy-xapp  3/3    3           3          55m
+`
+
+var helListOutput = `Next: ""
+Releases:
+- AppVersion: "1.0"
+  Chart: dummy-xapp-chart-0.1.0
+  Name: dummy-xapp
+  Namespace: default
+  Revision: 1
+  Status: DEPLOYED
+  Updated: Mon Mar 11 06:55:05 2019
+- AppVersion: "2.0"
+  Chart: dummy-xapp-chart-0.1.0
+  Name: dummy-xapp2
+  Namespace: default
+  Revision: 1
+  Status: DEPLOYED
+  Updated: Mon Mar 11 06:55:05 2019
+- AppVersion: "1.0"
+  Chart: appmgr-0.0.1
+  Name: appmgr
+  Namespace: default
+  Revision: 1
+  Status: DEPLOYED
+  Updated: Sun Mar 24 07:17:00 2019`
+
+// Test cases
+func TestMain(m *testing.M) {
+	appmgr.Init()
+	appmgr.Logger.SetLevel(0)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmStatus(t *testing.T) {
+	//NewHelm().SetCM(&ConfigMap{})
+	util.KubectlExec = func(args string) (out []byte, err error) {
+		return []byte("10.102.184.212"), nil
+	}
+	xapp, err := NewHelm().ParseStatus("dummy-xapp", helmStatusOutput)
+	if err != nil {
+		t.Errorf("Helm install failed: %v", err)
+	}
+	x := getXappData()
+	xapp.Version = "1.0"
+
+	if *x.Name != *xapp.Name || x.Status != xapp.Status || x.Version != xapp.Version {
+		t.Errorf("\n%v \n%v", *xapp.Name, *x.Name)
+	}
+
+	if *x.Instances[0].Name != *xapp.Instances[0].Name || x.Instances[0].Status != xapp.Instances[0].Status {
+		t.Errorf("\n1:%v 2:%v", *x.Instances[0].Name, *xapp.Instances[0].Name)
+	}
+
+	if x.Instances[0].IP != xapp.Instances[0].IP || x.Instances[0].Port != xapp.Instances[0].Port {
+		t.Errorf("\n1:%v 2:%v", x.Instances[0].IP, xapp.Instances[0].IP)
+	}
+}
+
+func TestHelmLists(t *testing.T) {
+	names, err := NewHelm().GetNames(helListOutput)
+	if err != nil {
+		t.Errorf("Helm status failed: %v", err)
+	}
+
+	if !reflect.DeepEqual(names, []string{"dummy-xapp", "dummy-xapp2"}) {
+		t.Errorf("Helm status failed: %v", err)
+	}
+}
+
+func TestAddTillerEnv(t *testing.T) {
+	if NewHelm().AddTillerEnv() != nil {
+		t.Errorf("TestAddTillerEnv failed!")
+	}
+}
+
+func TestGetInstallArgs(t *testing.T) {
+	name := "dummy-xapp"
+	x := models.XappDescriptor{XappName: &name, Namespace: "ricxapp"}
+
+	expectedArgs := "install helm-repo/dummy-xapp  --namespace=ricxapp --name=dummy-xapp"
+	if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
+		t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+	}
+
+	x.HelmVersion = "1.2.3"
+	expectedArgs = "install helm-repo/dummy-xapp  --namespace=ricxapp --version=1.2.3 --name=dummy-xapp"
+	if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
+		t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+	}
+
+	x.ReleaseName = "ueec-xapp"
+	expectedArgs = "install helm-repo/dummy-xapp  --namespace=ricxapp --version=1.2.3 --name=ueec-xapp"
+	if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
+		t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+	}
+}
+
+func getXappData() (x models.Xapp) {
+	//name1 := "dummy-xapp-8984fc9fd-l6xch"
+	//name2 := "dummy-xapp-8984fc9fd-pp4hg"
+	x = generateXapp("dummy-xapp", "deployed", "1.0", "dummy-xapp-8984fc9fd-bkcbp", "running", "service-ricxapp-dummy-xapp-rmr.ricxapp", "4560")
+	//x.Instances = append(x.Instances, x.Instances[0])
+	//x.Instances = append(x.Instances, x.Instances[0])
+	//x.Instances[1].Name = &name1
+	//x.Instances[2].Name = &name2
+
+	return x
+}
+
+func generateXapp(name, status, ver, iname, istatus, ip, port string) (x models.Xapp) {
+	x.Name = &name
+	x.Status = status
+	x.Version = ver
+	p, _ := strconv.Atoi(port)
+	var msgs appmgr.MessageTypes
+
+	instance := &models.XappInstance{
+		Name:       &iname,
+		Status:     istatus,
+		IP:         ip,
+		Port:       int64(p),
+		TxMessages: msgs.TxMessages,
+		RxMessages: msgs.RxMessages,
+	}
+	x.Instances = append(x.Instances, instance)
+
+	return
+}
diff --git a/cmd/appmgr/logger.go b/pkg/logger/logger.go
similarity index 86%
rename from cmd/appmgr/logger.go
rename to pkg/logger/logger.go
index d1bcf15..251e90f 100755
--- a/cmd/appmgr/logger.go
+++ b/pkg/logger/logger.go
@@ -17,11 +17,10 @@
 ==================================================================================
 */
 
-package main
+package logger
 
 import (
 	mdclog "gerrit.o-ran-sc.org/r/com/golog"
-	"net/http"
 	"time"
 )
 
@@ -63,10 +62,3 @@
 	l.SetMdc("time", time.Now().Format(time.RFC3339))
 	l.logger.Debug(pattern, args...)
 }
-
-func LogRestRequests(inner http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		inner.ServeHTTP(w, r)
-		Logger.Info("Logger: method=%s url=%s", r.Method, r.URL.RequestURI())
-	})
-}
diff --git a/pkg/restful/restful.go b/pkg/restful/restful.go
new file mode 100755
index 0000000..c0ef580
--- /dev/null
+++ b/pkg/restful/restful.go
@@ -0,0 +1,245 @@
+/*
+	==================================================================================
+	Copyright (c) 2019 AT&T Intellectual Property.
+	Copyright (c) 2019 Nokia
+
+	Licensed under the Apache License, Version 2.0 (the "License");
+	you may not use this file except in compliance with the License.
+	You may obtain a copy of the License at
+
+		http://www.apache.org/licenses/LICENSE-2.0
+
+	Unless required by applicable law or agreed to in writing, software
+	distributed under the License is distributed on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	See the License for the specific language governing permissions and
+	limitations under the License.
+	==================================================================================
+*/
+
+package restful
+
+import (
+	//"github.com/spf13/viper"
+	"log"
+	"os"
+	"time"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi/operations"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi/operations/health"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi/operations/xapp"
+	"github.com/go-openapi/loads"
+	"github.com/go-openapi/runtime/middleware"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/cm"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/helm"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/resthooks"
+)
+
+func NewRestful() *Restful {
+	r := &Restful{
+		helm:  helm.NewHelm(),
+		cm:    cm.NewCM(),
+		rh:    resthooks.NewResthook(),
+		ready: false,
+	}
+	r.api = r.SetupHandler()
+	return r
+}
+
+func (r *Restful) Run() {
+	server := restapi.NewServer(r.api)
+	defer server.Shutdown()
+	server.Port = 8080      //viper.GetInt("local.port")
+	server.Host = "0.0.0.0" //viper.GetString("local.host")
+
+	appmgr.Logger.Info("Xapp manager started ... serving on %s:%d\n", server.Host, server.Port)
+
+	go r.NotifyClients()
+	if err := server.Serve(); err != nil {
+		log.Fatal(err.Error())
+	}
+}
+
+func (r *Restful) SetupHandler() *operations.AppManagerAPI {
+	swaggerSpec, err := loads.Embedded(restapi.SwaggerJSON, restapi.FlatSwaggerJSON)
+	if err != nil {
+		appmgr.Logger.Error(err.Error())
+		os.Exit(1)
+	}
+	api := operations.NewAppManagerAPI(swaggerSpec)
+
+	// URL: /ric/v1/health
+	api.HealthGetHealthAliveHandler = health.GetHealthAliveHandlerFunc(
+		func(params health.GetHealthAliveParams) middleware.Responder {
+			return health.NewGetHealthAliveOK()
+		})
+	api.HealthGetHealthReadyHandler = health.GetHealthReadyHandlerFunc(
+		func(params health.GetHealthReadyParams) middleware.Responder {
+			return health.NewGetHealthReadyOK()
+		})
+
+	// URL: /ric/v1/subscriptions
+	api.GetSubscriptionsHandler = operations.GetSubscriptionsHandlerFunc(
+		func(params operations.GetSubscriptionsParams) middleware.Responder {
+			return operations.NewGetSubscriptionsOK().WithPayload(r.rh.GetAllSubscriptions())
+		})
+	api.GetSubscriptionByIDHandler = operations.GetSubscriptionByIDHandlerFunc(
+		func(params operations.GetSubscriptionByIDParams) middleware.Responder {
+			if result, found := r.rh.GetSubscriptionById(params.SubscriptionID); found {
+				return operations.NewGetSubscriptionByIDOK().WithPayload(&result)
+			}
+			return operations.NewGetSubscriptionByIDNotFound()
+		})
+	api.AddSubscriptionHandler = operations.AddSubscriptionHandlerFunc(
+		func(params operations.AddSubscriptionParams) middleware.Responder {
+			return operations.NewAddSubscriptionCreated().WithPayload(r.rh.AddSubscription(*params.SubscriptionRequest))
+		})
+	api.ModifySubscriptionHandler = operations.ModifySubscriptionHandlerFunc(
+		func(params operations.ModifySubscriptionParams) middleware.Responder {
+			if _, ok := r.rh.ModifySubscription(params.SubscriptionID, *params.SubscriptionRequest); ok {
+				return operations.NewModifySubscriptionOK()
+			}
+			return operations.NewModifySubscriptionBadRequest()
+		})
+	api.DeleteSubscriptionHandler = operations.DeleteSubscriptionHandlerFunc(
+		func(params operations.DeleteSubscriptionParams) middleware.Responder {
+			if _, ok := r.rh.DeleteSubscription(params.SubscriptionID); ok {
+				return operations.NewDeleteSubscriptionNoContent()
+			}
+			return operations.NewDeleteSubscriptionBadRequest()
+		})
+
+	// URL: /ric/v1/xapp
+	api.XappGetAllXappsHandler = xapp.GetAllXappsHandlerFunc(
+		func(params xapp.GetAllXappsParams) middleware.Responder {
+			if result, err := r.helm.StatusAll(); err == nil {
+				return xapp.NewGetAllXappsOK().WithPayload(result)
+			}
+			return xapp.NewGetAllXappsInternalServerError()
+		})
+	api.XappListAllXappsHandler = xapp.ListAllXappsHandlerFunc(
+		func(params xapp.ListAllXappsParams) middleware.Responder {
+			if result := r.helm.SearchAll(); err == nil {
+				return xapp.NewListAllXappsOK().WithPayload(result)
+			}
+			return xapp.NewListAllXappsInternalServerError()
+		})
+	api.XappGetXappByNameHandler = xapp.GetXappByNameHandlerFunc(
+		func(params xapp.GetXappByNameParams) middleware.Responder {
+			if result, err := r.helm.Status(params.XAppName); err == nil {
+				return xapp.NewGetXappByNameOK().WithPayload(&result)
+			}
+			return xapp.NewGetXappByNameNotFound()
+		})
+	api.XappGetXappInstanceByNameHandler = xapp.GetXappInstanceByNameHandlerFunc(
+		func(params xapp.GetXappInstanceByNameParams) middleware.Responder {
+			if result, err := r.helm.Status(params.XAppName); err == nil {
+				for _, v := range result.Instances {
+					if *v.Name == params.XAppInstanceName {
+						return xapp.NewGetXappInstanceByNameOK().WithPayload(v)
+					}
+				}
+			}
+			return xapp.NewGetXappInstanceByNameNotFound()
+		})
+	api.XappDeployXappHandler = xapp.DeployXappHandlerFunc(
+		func(params xapp.DeployXappParams) middleware.Responder {
+			if result, err := r.helm.Install(*params.XappDescriptor); err == nil {
+				go r.PublishXappCreateEvent(params)
+				return xapp.NewDeployXappCreated().WithPayload(&result)
+			}
+			return xapp.NewUndeployXappInternalServerError()
+		})
+	api.XappUndeployXappHandler = xapp.UndeployXappHandlerFunc(
+		func(params xapp.UndeployXappParams) middleware.Responder {
+			if result, err := r.helm.Delete(params.XAppName); err == nil {
+				go r.PublishXappDeleteEvent(result)
+				return xapp.NewUndeployXappNoContent()
+			}
+			return xapp.NewUndeployXappInternalServerError()
+		})
+
+	// URL: /ric/v1/config
+	api.XappGetAllXappConfigHandler = xapp.GetAllXappConfigHandlerFunc(
+		func(params xapp.GetAllXappConfigParams) middleware.Responder {
+			return xapp.NewGetAllXappConfigOK().WithPayload(r.cm.UploadConfig())
+		})
+	api.XappCreateXappConfigHandler = xapp.CreateXappConfigHandlerFunc(
+		func(params xapp.CreateXappConfigParams) middleware.Responder {
+			result, err := r.cm.CreateConfigMap(*params.XAppConfig)
+			if err == nil {
+				if err.Error() != "Validation failed!" {
+					return xapp.NewCreateXappConfigInternalServerError()
+				} else {
+					return xapp.NewCreateXappConfigUnprocessableEntity()
+				}
+			}
+			r.rh.PublishSubscription(models.Xapp{}, models.EventTypeCreated)
+			return xapp.NewCreateXappConfigCreated().WithPayload(result)
+		})
+	api.XappModifyXappConfigHandler = xapp.ModifyXappConfigHandlerFunc(
+		func(params xapp.ModifyXappConfigParams) middleware.Responder {
+			result, err := r.cm.UpdateConfigMap(*params.XAppConfig)
+			if err == nil {
+				if err.Error() != "Validation failed!" {
+					return xapp.NewModifyXappConfigInternalServerError()
+				} else {
+					return xapp.NewModifyXappConfigUnprocessableEntity()
+				}
+			}
+			r.rh.PublishSubscription(models.Xapp{}, models.EventTypeModified)
+			return xapp.NewModifyXappConfigOK().WithPayload(result)
+		})
+	api.XappDeleteXappConfigHandler = xapp.DeleteXappConfigHandlerFunc(
+		func(params xapp.DeleteXappConfigParams) middleware.Responder {
+			_, err := r.cm.DeleteConfigMap(*params.ConfigMetadata)
+			if err == nil {
+				return xapp.NewDeleteXappConfigInternalServerError()
+			}
+			r.rh.PublishSubscription(models.Xapp{}, models.EventTypeDeleted)
+			return xapp.NewDeleteXappConfigNoContent()
+		})
+
+	// LCM: /xapps/{xAppName}/instances/{xAppInstanceName}/stop/start
+	api.XappStartXappInstanceByNameHandler = xapp.StartXappInstanceByNameHandlerFunc(
+		func(params xapp.StartXappInstanceByNameParams) middleware.Responder {
+			return xapp.NewStartXappInstanceByNameOK()
+		})
+	api.XappStopXappInstanceByNameHandler = xapp.StopXappInstanceByNameHandlerFunc(
+		func(params xapp.StopXappInstanceByNameParams) middleware.Responder {
+			return xapp.NewStopXappInstanceByNameOK()
+		})
+
+	return api
+}
+
+func (r *Restful) NotifyClients() {
+	r.helm.Initialize()
+	if xapps, err := r.helm.StatusAll(); err == nil {
+		r.rh.NotifyClients(xapps, models.EventTypeRestarted)
+		r.ready = true
+	}
+}
+
+func (r *Restful) PublishXappCreateEvent(params xapp.DeployXappParams) {
+	name := *params.XappDescriptor.XappName
+	if params.XappDescriptor.ReleaseName != "" {
+		name = params.XappDescriptor.ReleaseName
+	}
+
+	for i := 0; i < 5; i++ {
+		if result, _ := r.helm.Status(name); result.Instances != nil {
+			r.rh.PublishSubscription(result, models.EventTypeDeployed)
+			break
+		}
+		time.Sleep(time.Duration(5) * time.Second)
+	}
+}
+
+func (r *Restful) PublishXappDeleteEvent(xapp models.Xapp) {
+	r.rh.PublishSubscription(xapp, models.EventTypeUndeployed)
+}
diff --git a/pkg/restful/types.go b/pkg/restful/types.go
new file mode 100755
index 0000000..189962d
--- /dev/null
+++ b/pkg/restful/types.go
@@ -0,0 +1,49 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package restful
+
+import (
+	"net/http"
+
+	cfgmap "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/cm"
+	helmer "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/helm"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi/operations"
+	resthook "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/resthooks"
+)
+
+type CmdOptions struct {
+	hostAddr      *string
+	helmHost      *string
+	helmChartPath *string
+}
+
+type Resource struct {
+	Method      string
+	Url         string
+	HandlerFunc http.HandlerFunc
+}
+
+type Restful struct {
+	api   *operations.AppManagerAPI
+	helm  *helmer.Helm
+	cm    *cfgmap.CM
+	rh    *resthook.Resthook
+	ready bool
+}
diff --git a/pkg/resthooks/resthooks.go b/pkg/resthooks/resthooks.go
new file mode 100755
index 0000000..5076b06
--- /dev/null
+++ b/pkg/resthooks/resthooks.go
@@ -0,0 +1,224 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package resthooks
+
+import (
+	"bytes"
+	"encoding/json"
+	sdl "gerrit.oran-osc.org/r/ric-plt/sdlgo"
+	cmap "github.com/orcaman/concurrent-map"
+	"github.com/segmentio/ksuid"
+	"net/http"
+	"time"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+)
+
+func NewResthook() *Resthook {
+	rh := &Resthook{
+		client: &http.Client{},
+		db:     sdl.NewSdlInstance("appmgr", sdl.NewDatabase()),
+	}
+
+	rh.subscriptions = rh.RestoreSubscriptions()
+	return rh
+}
+
+func (rh *Resthook) AddSubscription(sr models.SubscriptionRequest) *models.SubscriptionResponse {
+	for v := range rh.subscriptions.IterBuffered() {
+		r := v.Val.(SubscriptionInfo).req
+		if *r.Data.TargetURL == *sr.Data.TargetURL && r.Data.EventType == sr.Data.EventType {
+			appmgr.Logger.Info("Similar subscription already exists!")
+			return &models.SubscriptionResponse{}
+		}
+	}
+
+	key := ksuid.New().String()
+	resp := models.SubscriptionResponse{ID: key, Version: 0, EventType: sr.Data.EventType}
+	rh.subscriptions.Set(key, SubscriptionInfo{key, sr, resp})
+	rh.StoreSubscriptions(rh.subscriptions)
+
+	appmgr.Logger.Info("Sub: New subscription added: key=%s targetUl=%s eventType=%s", key, *sr.Data.TargetURL, sr.Data.EventType)
+	return &resp
+}
+
+func (rh *Resthook) DeleteSubscription(id string) (*models.SubscriptionResponse, bool) {
+	if v, found := rh.subscriptions.Get(id); found {
+		appmgr.Logger.Info("Subscription id=%s found: %v ... deleting", id, v.(SubscriptionInfo).req)
+
+		rh.subscriptions.Remove(id)
+		rh.StoreSubscriptions(rh.subscriptions)
+		resp := v.(SubscriptionInfo).resp
+		return &resp, found
+	}
+	return &models.SubscriptionResponse{}, false
+}
+
+func (rh *Resthook) ModifySubscription(id string, req models.SubscriptionRequest) (*models.SubscriptionResponse, bool) {
+	if s, found := rh.subscriptions.Get(id); found {
+		appmgr.Logger.Info("Subscription id=%s found: %v ... updating", id, s.(SubscriptionInfo).req)
+
+		resp := models.SubscriptionResponse{ID: id, Version: 0, EventType: req.Data.EventType}
+		rh.subscriptions.Set(id, SubscriptionInfo{id, req, resp})
+		rh.StoreSubscriptions(rh.subscriptions)
+
+		return &resp, found
+	}
+	return &models.SubscriptionResponse{}, false
+}
+
+func (rh *Resthook) GetAllSubscriptions() (hooks models.AllSubscriptions) {
+	hooks = models.AllSubscriptions{}
+	for v := range rh.subscriptions.IterBuffered() {
+		s := v.Val.(SubscriptionInfo)
+		r := v.Val.(SubscriptionInfo).req
+		hooks = append(hooks, &models.Subscription{&models.SubscriptionData{r.Data.EventType, r.Data.MaxRetries, r.Data.RetryTimer, r.Data.TargetURL}, s.Id})
+	}
+
+	return hooks
+}
+
+func (rh *Resthook) GetSubscriptionById(id string) (models.Subscription, bool) {
+	if v, found := rh.subscriptions.Get(id); found {
+		appmgr.Logger.Info("Subscription id=%s found: %v", id, v.(SubscriptionInfo).req)
+		r := v.(SubscriptionInfo).req
+		return models.Subscription{&models.SubscriptionData{r.Data.EventType, r.Data.MaxRetries, r.Data.RetryTimer, r.Data.TargetURL}, id}, found
+	}
+	return models.Subscription{}, false
+}
+
+func (rh *Resthook) PublishSubscription(x models.Xapp, et models.EventType) {
+	rh.NotifyClients(models.AllDeployedXapps{&x}, et)
+}
+
+func (rh *Resthook) NotifyClients(xapps models.AllDeployedXapps, et models.EventType) {
+	if len(xapps) == 0 || len(rh.subscriptions) == 0 {
+		appmgr.Logger.Info("Nothing to publish [%d:%d]", len(xapps), len(rh.subscriptions))
+		return
+	}
+
+	rh.Seq = rh.Seq + 1
+	for v := range rh.subscriptions.Iter() {
+		go rh.notify(xapps, et, v.Val.(SubscriptionInfo), rh.Seq)
+	}
+}
+
+func (rh *Resthook) notify(xapps models.AllDeployedXapps, et models.EventType, s SubscriptionInfo, seq int64) error {
+	notif := models.SubscriptionNotification{ID: s.Id, Version: seq, EventType: et, XApps: xapps}
+	jsonData, err := json.Marshal(notif)
+	if err != nil {
+		appmgr.Logger.Info("json.Marshal failed: %v", err)
+		return err
+	}
+
+	// Execute the request with retry policy
+	return rh.retry(s, func() error {
+		appmgr.Logger.Info("Posting notification to TargetURL=%s: %v", *s.req.Data.TargetURL, notif)
+		resp, err := http.Post(*s.req.Data.TargetURL, "application/json", bytes.NewBuffer(jsonData))
+		if err != nil {
+			appmgr.Logger.Info("Posting to subscription failed: %v", err)
+			return err
+		}
+
+		if resp.StatusCode != http.StatusOK {
+			appmgr.Logger.Info("Client returned error code: %d", resp.StatusCode)
+			return err
+		}
+
+		appmgr.Logger.Info("subscription to '%s' dispatched, response code: %d", *s.req.Data.TargetURL, resp.StatusCode)
+		return nil
+	})
+}
+
+func (rh *Resthook) retry(s SubscriptionInfo, fn func() error) error {
+	if err := fn(); err != nil {
+		// Todo: use exponential backoff, or similar mechanism
+		if *s.req.Data.MaxRetries--; *s.req.Data.MaxRetries > 0 {
+			time.Sleep(time.Duration(*s.req.Data.RetryTimer) * time.Second)
+			return rh.retry(s, fn)
+		}
+		rh.subscriptions.Remove(s.Id)
+		return err
+	}
+	return nil
+}
+
+func (rh *Resthook) StoreSubscriptions(m cmap.ConcurrentMap) {
+	for v := range m.Iter() {
+		s := v.Val.(SubscriptionInfo)
+
+		data, err := json.Marshal(s.req)
+		if err != nil {
+			appmgr.Logger.Error("json.marshal failed: %v ", err.Error())
+			return
+		}
+
+		if err := rh.db.Set(s.Id, data); err != nil {
+			appmgr.Logger.Error("DB.session.Set failed: %v ", err.Error())
+		}
+	}
+}
+
+func (rh *Resthook) RestoreSubscriptions() (m cmap.ConcurrentMap) {
+	rh.VerifyDBConnection()
+
+	m = cmap.New()
+	keys, err := rh.db.GetAll()
+	if err != nil {
+		appmgr.Logger.Error("DB.session.GetAll failed: %v ", err.Error())
+		return
+	}
+
+	for _, key := range keys {
+		value, err := rh.db.Get([]string{key})
+		if err != nil {
+			appmgr.Logger.Error("DB.session.Get failed: %v ", err.Error())
+			return
+		}
+
+		var item models.SubscriptionRequest
+		if err = json.Unmarshal([]byte(value[key].(string)), &item); err != nil {
+			appmgr.Logger.Error("json.Unmarshal failed: %v ", err.Error())
+			return
+		}
+
+		resp := models.SubscriptionResponse{ID: key, Version: 0, EventType: item.Data.EventType}
+		m.Set(key, SubscriptionInfo{key, item, resp})
+	}
+
+	return m
+}
+
+func (rh *Resthook) VerifyDBConnection() {
+	// Test DB connection, and wait until ready!
+	for {
+		if _, err := rh.db.GetAll(); err == nil {
+			return
+		}
+		appmgr.Logger.Error("Database connection not ready, waiting ...")
+		time.Sleep(time.Duration(5 * time.Second))
+	}
+}
+
+func (rh *Resthook) FlushSubscriptions() {
+	rh.db.RemoveAll()
+	rh.subscriptions = cmap.New()
+}
diff --git a/pkg/resthooks/resthooks_test.go b/pkg/resthooks/resthooks_test.go
new file mode 100755
index 0000000..018dee8
--- /dev/null
+++ b/pkg/resthooks/resthooks_test.go
@@ -0,0 +1,124 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package resthooks
+
+import (
+	"github.com/stretchr/testify/assert"
+	"os"
+	"testing"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+)
+
+var resp models.SubscriptionResponse
+
+// Test cases
+func TestMain(m *testing.M) {
+	appmgr.Init()
+	appmgr.Logger.SetLevel(0)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestAddSubscriptionSuccess(t *testing.T) {
+	resp := NewResthook().AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+	assert.Equal(t, resp.Version, int64(0))
+	assert.Equal(t, resp.EventType, models.EventTypeCreated)
+}
+
+func TestAddSubscriptionExists(t *testing.T) {
+	resp := NewResthook().AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+	assert.Equal(t, resp.Version, int64(0))
+	assert.Equal(t, resp.EventType, models.EventType(""))
+}
+
+func TestDeletesubscriptionSuccess(t *testing.T) {
+	resp := NewResthook().AddSubscription(CreateSubscription(models.EventTypeDeleted, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+	assert.Equal(t, resp.Version, int64(0))
+	assert.Equal(t, resp.EventType, models.EventTypeDeleted)
+
+	resp, ok := NewResthook().DeleteSubscription(resp.ID)
+	assert.Equal(t, ok, true)
+	assert.Equal(t, resp.Version, int64(0))
+	assert.Equal(t, resp.EventType, models.EventTypeDeleted)
+}
+
+func TestDeletesubscriptionInvalid(t *testing.T) {
+	resp, ok := NewResthook().DeleteSubscription("Non-existent-ID")
+	assert.Equal(t, ok, false)
+	assert.Equal(t, resp.Version, int64(0))
+	assert.Equal(t, resp.EventType, models.EventType(""))
+}
+
+func TestModifySubscriptionSuccess(t *testing.T) {
+	resp := NewResthook().AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+	assert.Equal(t, resp.Version, int64(0))
+	assert.Equal(t, resp.EventType, models.EventTypeCreated)
+
+	resp, ok := NewResthook().ModifySubscription(resp.ID, CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+	assert.Equal(t, ok, true)
+	assert.Equal(t, resp.Version, int64(0))
+	assert.Equal(t, resp.EventType, models.EventTypeModified)
+}
+
+func TestModifysubscriptionInvalid(t *testing.T) {
+	resp, ok := NewResthook().DeleteSubscription("Non-existent-ID")
+	assert.Equal(t, ok, false)
+	assert.Equal(t, resp.Version, int64(0))
+	assert.Equal(t, resp.EventType, models.EventType(""))
+}
+
+func TestGetAllSubscriptionSuccess(t *testing.T) {
+	NewResthook().FlushSubscriptions()
+	subscriptions := NewResthook().GetAllSubscriptions()
+	assert.Equal(t, len(subscriptions), 0)
+
+	NewResthook().AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+	NewResthook().AddSubscription(CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+
+	subscriptions = NewResthook().GetAllSubscriptions()
+	assert.Equal(t, len(subscriptions), 2)
+}
+
+func TestGetSubscriptionByIdSuccess(t *testing.T) {
+	NewResthook().FlushSubscriptions()
+	sub1 := CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
+	sub2 := CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2")
+	r1 := NewResthook().AddSubscription(sub1)
+	r2 := NewResthook().AddSubscription(sub2)
+
+	resp1, ok := NewResthook().GetSubscriptionById(r1.ID)
+	assert.Equal(t, ok, true)
+	assert.Equal(t, resp1.Data, sub1.Data)
+
+	resp2, ok := NewResthook().GetSubscriptionById(r2.ID)
+	assert.Equal(t, ok, true)
+	assert.Equal(t, resp2.Data, sub2.Data)
+}
+
+func TestTeardown(t *testing.T) {
+	NewResthook().FlushSubscriptions()
+}
+
+func CreateSubscription(et models.EventType, maxRetries, retryTimer int64, targetUrl string) models.SubscriptionRequest {
+	return models.SubscriptionRequest{&models.SubscriptionData{et, &maxRetries, &retryTimer, &targetUrl}}
+}
diff --git a/cmd/appmgr/main.go b/pkg/resthooks/types.go
similarity index 64%
copy from cmd/appmgr/main.go
copy to pkg/resthooks/types.go
index 8788405..830576b 100755
--- a/cmd/appmgr/main.go
+++ b/pkg/resthooks/types.go
@@ -17,17 +17,25 @@
 ==================================================================================
 */
 
-package main
+package resthooks
 
-var Logger *Log
+import (
+	sdl "gerrit.oran-osc.org/r/ric-plt/sdlgo"
+	cmap "github.com/orcaman/concurrent-map"
+	"net/http"
 
-func main() {
-	Logger = NewLogger("xapp-manager")
-	Logger.SetMdc("appmgr", "0.1.9")
-	loadConfig()
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+)
 
-	m := XappManager{}
-	m.Initialize(&Helm{}, &ConfigMap{})
+type SubscriptionInfo struct {
+	Id   string
+	req  models.SubscriptionRequest
+	resp models.SubscriptionResponse
+}
 
-	m.Run()
+type Resthook struct {
+	client        *http.Client
+	subscriptions cmap.ConcurrentMap
+	db            *sdl.SdlInstance
+	Seq           int64
 }
diff --git a/pkg/util/util.go b/pkg/util/util.go
new file mode 100755
index 0000000..8ea4d40
--- /dev/null
+++ b/pkg/util/util.go
@@ -0,0 +1,68 @@
+/*
+==================================================================================
+  Copyright (c) 2019 AT&T Intellectual Property.
+  Copyright (c) 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+package util
+
+import (
+	"bytes"
+	"errors"
+	"github.com/spf13/viper"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+
+	"gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+)
+
+var execCommand = exec.Command
+
+func Exec(args string) (out []byte, err error) {
+	cmd := execCommand("/bin/sh", "-c", args)
+
+	var stdout bytes.Buffer
+	var stderr bytes.Buffer
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+
+	appmgr.Logger.Info("Running command: %s ", cmd.Args)
+	for i := 0; i < viper.GetInt("helm.retry"); i++ {
+		if err = cmd.Run(); err != nil {
+			appmgr.Logger.Error("Command failed: %v - %s, retrying", err.Error(), stderr.String())
+			time.Sleep(time.Duration(2) * time.Second)
+			continue
+		}
+		break
+	}
+
+	if err == nil && !strings.HasSuffix(os.Args[0], ".test") {
+		appmgr.Logger.Info("command success: %s", stdout.String())
+		return stdout.Bytes(), nil
+	}
+
+	return stdout.Bytes(), errors.New(stderr.String())
+}
+
+var HelmExec = func(args string) (out []byte, err error) {
+	return Exec(strings.Join([]string{"helm", args}, " "))
+}
+
+var KubectlExec = func(args string) (out []byte, err error) {
+	return Exec(strings.Join([]string{"kubectl", args}, " "))
+}
diff --git a/scripts/appmgrcli b/scripts/appmgrcli
index 22f0677..4b9637d 100755
--- a/scripts/appmgrcli
+++ b/scripts/appmgrcli
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/usr/bin/perl -w
 #
 # Copyright (c) 2019 AT&T Intellectual Property.
 # Copyright (c) 2019 Nokia.
@@ -19,26 +19,34 @@
 #############################
 # Simple cli for xapp manager
 #
-# In addition to standard shell tools, requires packages "curl" and
+# In addition to standard shell tools, requires basic Perl installation
+# (Ubuntu package "perl-base", installed by default), packages "curl" and
 # "yajl-tools" (the second provides json_reformat on Ubuntu; on Red Hat-style
 # distributions install "yajl" instead).
 #
-myname=appmgrcli
+use strict;
+use Getopt::Long;
+use Fcntl;
 
-usage() {
-  cat <<EOF1
-usage: $myname [-h host] [-p port] [-v] command params...
-- command is one of deploy, undeploy, status, subscriptions, health, help
+my $myname="appmgrcli";
+
+sub usage {
+    print <<"EOF1";
+usage: $myname [-h host] [-p port] [-v] [-c curlprog] command params...
+- command is deploy, undeploy, status, subscriptions, health, config, help
 - (abbreviations dep, undep, stat, subs, heal allowed)
 - Parameters of the commands that may have parameters:
 -- deploy: name of the xapp to deploy
+---- Deployment parameters come from the Help chart but the following can be
+---- overridden by option with the same name (for example --configName=cname):
+----- helmVersion ReleaseName namespace podHost (host of pods).
 -- undeploy: name of the xapp to undeploy
 -- status:
 ---- No parameters: Lists information about all deployed xapps
 ---- xapp name as parameter: Prints information about the given xapp
 ---- xapp name and instance: Lists information about the given instance only
 -- subscriptions is followed by sub-command list, add, delete, or modify
----(abbreviations del and mod for delete and modify are allowed):
+--- (abbreviations del and mod for delete and modify are allowed):
 ---- list without parameters lists all subscriptions
 ---- list with subscription id prints that subscription
 ---- add URL eventType maxRetry retryTimer
@@ -50,356 +58,997 @@
 --------the rest of the parameters are like in add
 ---- delete id
 ------- id is the subscription id to delete (find out with the list command)
+-- config is followed by sub-command list, add, delete, or modify
+--- (abbreviations del and mod for delete and modify are allowed):
+---- list (no pars)
+------ lists the configuration of all xapps
+---- add jsonfile
+------ Creates xapp configuration. the jsonfile must contain all data (see API)
+---- add name configName namespace configSchemaFile configDataFile
+------ Creates xapp configuration, but unlike in the 1-parameter form,
+------ Xapp name, config map name and namespace are separate parameters.
+------ The other data come from JSON files.
+---- modify
+------ Modifies existing configuration. Same parameters (1 or 5) as in add.
+---- delete name configName namespace
+------ Deletes the configuration identified by the parameters.
+--------------------------------------------------------------
 - Default values for host and port can be set in environment
 - variables APPMGR_HOST and APPMGR_PORT
 - Option -v sets verbose mode.
+- Option -c overrides the used curl program name ("curl" by default).
+- Exit code is 0 for success, 1 for any kind of failure.
 EOF1
+    exit 0;
+}
+
+sub helphint {
+    print "run $myname help (or --help) for instructions\n";
 }
 
 # Defaults
 
-host=localhost
-port=8080
-verbose=0
+my $host="localhost";
+my $port=8080;
+my $verbose=0;
+my $showhelp = 0;
+
+# API URLs
+
+my $base="/ric/v1";
+my $base_xapps="$base/xapps";
+my $base_health="$base/health";
+my $base_subs="$base/subscriptions";
+my $base_config="$base/config";
 
 # Check for environment override
-if [ "x$APPMGR_HOST" != "x" ]; then
-    host="$APPMGR_HOST"
-fi
-if [ "x$APPMGR_PORT" != "x" ]; then
-    port="$APPMGR_PORT"
-fi
+if (exists $ENV{"APPMGR_HOST"}) {
+   $host=$ENV{"APPMGR_HOST"};
+}
+if (exists $ENV{"APPMGR_PORT"}) {
+    $port=$ENV{"APPMGR_PORT"};
+}
 
-# Proper shell option parsing:
-while getopts  "h:p:v" flag
-do
-  # Curiously, getopts does not guard against an argument-requiring option
-  # eating the next option. It also does not handle the -- convention.
-  # Here is how to fix that.
-  if [ "X$OPTARG" = 'X--' ]; then
-    break # Explicit end of options
-  fi
-  if expr -- "$OPTARG" : '-.*' > /dev/null ; then
-    echo $myname: Option -$flag has no required value, or value begins with -,
-    echo - which is disallowed.
-    usage
-    exit 1
-  fi
-  case $flag in
-  (h) host="$OPTARG"
-      ;;
-  (p) port="$OPTARG"
-      ;;
-  (v) verbose=1
-      ;;
-  (*)
-      echo $myname: Bad option letter or required option argument missing.
-      usage
-      exit 1
-      ;;
-  esac
-done
-# Get rid of the option part
-shift $((OPTIND-1))
+# Overrides for some deploy parameters
 
-if [ $verbose = 1 ]; then
-  echo "host = $host"
-  echo "port = $port"
-fi
+my $configName = "";
+my $namespace = "ricxapp";
+my $releaseName = "";
+my $helmVersion = "0.0.1";
+my $overrideFile = "";
+my $podHost = "";
 
-# Verify command
+# The curl command can be overridden for testing with a dummy.
 
-case $1 in
-  (deploy|dep)
-    cmd=deploy
-    ;;
-  (undeploy|undep)
-    cmd=undeploy
-    ;;
-  (status|stat)
-    cmd=status
-    ;;
-  (subscriptions|subs)
-    cmd=subscriptions
-    ;;
-  (health|heal)
-    cmd=health
-    ;;
-  (config|upload)
-    cmd=config
-    ;;
-  (help)
-    usage
-    exit 0
-    ;;
-  (*)
-    if [ "x$1" = "x" ]; then
-     echo "$myname: Missing command"
-    else
-     echo "$myname: Unrecognized command $1"
-    fi
-    usage
-    exit 1
-    ;;
-esac
+my $curl = "curl";
 
-if [ $verbose = 1 ]; then
-  echo "Command $cmd params=$2"
-fi
+Getopt::Long::Configure("no_auto_abbrev", "permute");
+if (! GetOptions("h=s" => \$host,
+                 "p=i" => \$port,
+                 "c=s" => \$curl,
+                 "ConfigName=s" => \$configName,
+                 "Namespace=s" => \$namespace,
+                 "ReleaseName=s" => \$releaseName,
+                 "HelmVersion=s" => \$helmVersion,
+                 "OverrideFile=s" => \$overrideFile,
+                 "podHost=s" => \$podHost,
+                 "help" => \$showhelp,
+                 "v" => \$verbose)) {
+    print "$myname: Error in options\n";
+    helphint();
+    exit 1;
+}
 
-errfile=`mktemp /tmp/appmgr_e.XXXXXXXXXX`
-resultfile=`mktemp /tmp/appmgr_r.XXXXXXXXXX`
+if ($showhelp) {
+    usage();
+    exit 0;
+}
+
+if ($verbose) {
+    print "host = $host\n";
+    print "port = $port\n";
+    print "ConfigName = $configName\n";
+    print "Namespace = $namespace\n";
+    print "ReleaseName = $releaseName\n";
+    print "HelmVersion = $helmVersion\n";
+     print "OverrideFile = $overrideFile\n";
+    print "podHost = $podHost\n";
+    for (my $idx = 0; $idx <= $#ARGV; ++$idx) {
+        print "\$ARGV[$idx] = $ARGV[$idx]\n";
+    }
+}
+
+# Verify command and call handler function
+
+my %commands = (
+    "deploy" => \&do_deploy,
+    "dep" => \&do_deploy,
+    "undeploy" => \&do_undeploy,
+    "undep" => \&do_undeploy,
+    "status" => \&do_status,
+    "stat" =>  \&do_status,
+    "subscriptions" => \&do_subscriptions,
+    "subs" =>  \&do_subscriptions,
+    "health" => \&do_health,
+    "heal" => \&do_health,
+    "config" => \&do_config,
+    "help" => \&usage
+);
+
+if ($#ARGV < 0) {
+    print "$myname: Missing command\n";
+    helphint();
+    exit 1;
+}
+
 # Variable status used for the return value of the whole script.
-status=0
+my $status = 0;
+
+my $command = $ARGV[0];
+shift;
+if (exists $commands{$command}) {
+    # Call the handler function with the rest of the command line
+    $commands{$command}(@ARGV);
+    exit $status; # Default exit. A handler can exit also if more convenient
+}
+print "$myname: Unrecognised command $command\n";
+helphint();
+exit 1;
+
+my $errfile;
+my $resultfile;
+
+
+sub make_temp_name($) {
+    my $tmpsuffix = "${$}${^T}";
+    return "$_[0].$tmpsuffix";
+}
+
+sub make_temps {
+    $errfile = make_temp_name("/tmp/appmgr_e");
+    $resultfile = make_temp_name("/tmp/appmgr_r");
+}
+
+sub remove_temps {
+    unlink ($errfile, $resultfile);
+}
+
+sub print_file($$) {
+    my $outputhandle = $_[0];
+    my $filename = $_[1];
+    my $buffer;
+    my $inhandle;
+    if (!open($inhandle, "<", $filename)) {
+        print $outputhandle "$myname print_file: cannot open $filename: $!\n";
+        return;
+    }
+    while (read($inhandle, $buffer, 4000) > 0) {
+        print $outputhandle $buffer;
+    }
+    close($inhandle);
+}
+
+# The HTTP protocol result code, filled in by rest().
+
+my $http_code = "";
+
+# Helper: Given a curl output file, extract the number from ##code line.
+# return ERROR if file cannot be opened, or "" if no code found.
+
+sub find_http_code($) {
+    my ($fh, $line, $code);
+    open($fh, "<", $_[0]) or return "ERROR";
+    while ($line = <$fh>) {
+        if ($line =~ /^##([0-9]+)/) {
+            return $1;
+        }
+    }
+    return "";
+}
 
 # Helper for command execution:
 # Do a rest call with "curl": $1 = method, $2 = path (without host and port
 # which come from variables), $3 data to POST if needed
-# returns 0 if OK, and any returned data is in $resultfile
-# else 1, and error message from curl is in $errfile, which is printed
-# before returning the 1.
-# Also sets $status to the return value.
+# returns true (1) if OK, and any returned data is in $resultfile
+# else 0, and error message from curl is in $errfile, which is printed
+# before returning the 0.
 #
 # On curl options: --silent --show-error disables progress bar, but allows
 # error messages. --connect-timeout 20 limits waiting for connection to
 # 20 seconds. In practice connection will succeed almost immediately,
 # or in the case of wrong address not at all.
+# To get the http code, using -w with format. The result comes at the end
+# of the output, so "decorating" it for easier filtering.
+# The code is put to global $http_code.
 #
-rest() {
-  local data
-  if [ "x$3" != "x" ]; then
-    data="--data $3"
-  fi
+sub rest($$_) {
+    my $method = $_[0];
+    my $path = $_[1];
+    my $data = $_[2] || "";
+    my $retval = 1;
+    my $http_status_file = make_temp_name("/tmp/appmgr_h");
 
-  if curl --silent --show-error --connect-timeout 20 --header "Content-Type: application/json" -X $1 -o $resultfile "http://${host}:${port}$2" $data 2> $errfile ;then
-    status=0
-  else
-    cat $errfile
-    status=1
-  fi
-  return $status
+    # This redirects stderr (fd 2) to $errfile, but saving normal stderr
+    # so that if can be restored.
+    open(OLDERR, ">&", \*STDERR) or die "Can't dup STDERR: $!";
+    open(ERRFILE, ">", $errfile) or die "open errorfile failed";
+    open(STDERR, ">&", \*ERRFILE) or die "Can't dup ERRFILE: $!";
+
+    # This redirects stdout (fd 1) to $http_status_file, but saving original
+    # so that if can be restored.
+    open(OLDSTDOUT, ">&", \*STDOUT) or die "Can't dup STDOUT: $!";
+    open(HTTP_STATUS_FILE, ">", $http_status_file) or die "open http status file failed";
+    open(STDOUT, ">&", \*HTTP_STATUS_FILE) or die "Can't dup HTTP_STATUS_FILE: $!";
+
+    my @args = ($curl, "--silent", "--show-error", "--connect-timeout", "20",
+                "--header", "Content-Type: application/json", "-X", $method,
+                "-o", $resultfile, "-w", '\n##%{http_code}\n',
+                "http://${host}:${port}${path}");
+    if ($data ne "") {
+        push(@args, "--data");
+        push(@args, $data);
+    }
+    if ($verbose) {
+        print OLDSTDOUT "Running: " . join(" ", @args) . "\n";
+    }
+    if (system(@args) == -1) {
+        print OLDSTDOUT "$myname: failed to execute @args\n";
+        $retval = 0;
+    }
+    elsif ($? & 127) {
+         printf OLDSTDOUT "$myname: child died with signal %d, %s coredump\n",
+             ($? & 127),  ($? & 128) ? 'with' : 'without';
+         $retval = 0;
+    }
+    else {
+        my $curl_exit_code = $? >> 8;
+        if ($curl_exit_code == 0) {
+            seek HTTP_STATUS_FILE, 0, 0; # Ensures flushing
+            $http_code = find_http_code($http_status_file);
+            if ($http_code eq "ERROR") {
+                print OLDSTDOUT "$myname: failed to open temp file $http_status_file\n";
+                $retval = 0;
+            }
+            elsif ($http_code eq "") {
+                print OLDSTDOUT "$myname: curl failed to provide HTTP code\n";
+                $retval = 0;
+            }
+            else {
+                if ($verbose) {
+                    print OLDSTDOUT "HTTP status code = $http_code\n";
+                }
+                $retval = 1; # Interaction OK from REST point of view
+            }
+        }
+        else {
+            print_file(\*OLDSTDOUT, $errfile);
+            $retval = 0;
+        }
+    }
+    open(STDOUT, ">&", \*OLDSTDOUT) or die "Can't dup OLDSTDOUT: $!";
+    open(STDERR, ">&", \*OLDERR) or die "Can't dup OLDERR: $!";
+    unlink($http_status_file);
+    return $retval;
 }
 
-remove_temps () {
-  rm -f $errfile $resultfile
+# Pretty-print a JSON file to stdout.
+# (currently uses json_reformat command)
+# Skips the ##httpcode line we make "curl"
+# add in order to get access to the HTTP status.
+
+sub print_json($) {
+    my $filename = $_[0];
+    my ($line, $inhandle, $outhandle);
+    if (!open($inhandle, "<", $filename)) {
+        print "$myname print_json: cannot open $filename: $!\n";
+        return;
+    }
+    if (!open($outhandle, "|json_reformat")) {
+        print "$myname print_json: cannot pipe to json_reformat: $!\n";
+        return;
+    }
+    while ($line = <$inhandle>) {
+        if (! ($line =~ /^##[0-9]+/)) {
+            print $outhandle $line;
+        }
+    }
+    close($outhandle);
+    close($inhandle);
 }
 
-# Execute command ($cmd guaranteed to be valid)
+# Append an entry like ","name":"value" to the first parameter, if "name"
+# names a variable with non-empty value.
+# Else returns the unmodified first parameter.
+
+sub append_option($$) {
+    my $result = $_[0];
+    my $var = $_[1];
+    my $val = eval("\$$var");
+    if ($val ne "") {
+        $result = "$result,\"$var\":\"$val\"";
+    }
+    return $result;
+}
+
+# Command handlers
 # Assumes the API currently implemented.
-# Functions for each command below (except health which is so simple).
+# Functions for each command below
 
-base=/ric/v1
-base_xapps=$base/xapps
-base_health=$base/health
-base_subs=$base/subscriptions
-base_config=$base/config
+# Deploy:
+# The deploy command has one mandatory parameter "name" in the API,
+# and several optional ones. Used mainly internally for testing, because
+# they all override Helm chart values:
+# "helmVersion": Helm chart version to be used
+# "releaseName": The releas name of xApp visible in K8s
+# "namespace":  Name of the namespace to which xApp is deployed.
+# "overrideFile":  The file content used to override values.yaml file
+# this host from the host the xapp manager is running in, we use the term
+# and variable name "podHost" here.
+# The options come from options (see GetOptions() call).
 
-do_deploy() {
-  if [ "x$1" != "x" ]; then
-    if rest POST $base_xapps \{\"name\":\"$1\"\} ; then
-      json_reformat < $resultfile
-    fi
-  else
-    echo Error: expected the name of xapp to deploy
-    status=1
-  fi
+sub do_deploy(@) {
+    my $name = $_[0] || "";
+    if ($name ne "") {
+        my $data = "{\"XappName\":\"$name\"";
+        $data = append_option($data, "helmVersion");
+        $data = append_option($data, "releaseName");
+        $data = append_option($data, "namespace");
+        $data = append_option($data, "overrideFile");
+        $data = $data . "}";
+        make_temps();
+        if (rest("POST", $base_xapps, $data)) {
+            if ($http_code eq "201") {
+                print_json $resultfile;
+                $status = 0;
+            }
+            else {
+                my $error;
+                if ($http_code eq "400") {
+                    $error = "INVALID PARAMETERS SUPPLIED";
+                }
+                elsif ($http_code eq "500") {
+                    $error = "INTERNAL ERROR";
+                }
+                else {
+                    $error = "UNKNOWN STATUS $http_code";
+                }
+                print "$error\n";
+                $status = 1;
+            }
+        }
+        else {
+            $status=1;
+        }
+        remove_temps();
+    }
+    else {
+        print "$myname: Error: expected the name of xapp to deploy\n";
+        $status = 1;
+    }
 }
 
-do_undeploy() {
-  local urlpath
-
-  urlpath=$base_xapps
-  if [ "x$1" != "x" ]; then
-    urlpath="$urlpath/$1"
-    if rest DELETE $urlpath; then
-      # Currently appmgr returns an empty result if
-      # undeploy is succesfull. Don't reformat file if empty.
-      if [ -s $resultfile ]; then
-        json_reformat < $resultfile
-      else
-        echo "$1 undeployed"
-      fi
-    fi
-  else
-    echo Error: expected the name of xapp to undeploy
-    status=1
-  fi
+sub do_undeploy(@) {
+    my $name = $_[0] || "";
+    my $urlpath = $base_xapps;
+    if ($name ne "") {
+        make_temps();
+        $urlpath = "$urlpath/$name";
+        if (rest("DELETE", $urlpath)) {
+            if ($http_code eq "204") {
+                print "SUCCESSFUL DELETION\n";
+                $status = 0;
+            }
+            else {
+                my $error;
+                if ($http_code eq "400") {
+                    $error = "INVALID XAPP NAME SUPPLIED";
+                }
+                elsif ($http_code eq "500") {
+                    $error = "INTERNAL ERROR";
+                }
+                else {
+                    $error = "UNKNOWN STATUS $http_code";
+                }
+                print "$error\n";
+                $status = 1;
+            }
+        }
+        else {
+            $status = 1;
+        }
+        remove_temps();
+    }
+    else {
+        print "$myname: Error: expected the name of xapp to undeploy\n";
+        $status = 1;
+    }
 }
 
-do_status() {
-  local urlpath
+sub do_status(@) {
+    my $name = $_[0] || "";
+    my $instance = $_[1] || "";
+    my $urlpath = $base_xapps;
 
-  urlpath=$base_xapps
-  if [ "x$1" != "x" ]; then
-    urlpath="$urlpath/$1"
-  fi
-  if [ "x$2" != "x" ]; then
-    urlpath="$urlpath/instances/$2"
-  fi
-  if rest GET $urlpath; then
-    json_reformat < $resultfile
-  fi
+    if ($name ne "") {
+        $urlpath = "$urlpath/$name";
+    }
+    if ($instance ne "") {
+        $urlpath = "$urlpath/instances/$instance"
+    }
+    make_temps();
+    if (rest("GET", $urlpath)) {
+        if ($http_code eq "200") {
+            print_json $resultfile;
+            $status = 0;
+        }
+        else {
+            my $error;
+            if ($http_code eq "400") {
+                $error = "INVALID XAPP NAME SUPPLIED";
+            }
+            if ($http_code eq "404") {
+                $error = "XAPP NOT FOUND";
+            }
+            elsif ($http_code eq "500") {
+                $error = "INTERNAL ERROR";
+            }
+            else {
+                $error = "UNKNOWN STATUS $http_code";
+            }
+            print "$error\n";
+            $status = 1;
+        }
+    }
+    else {
+        $status = 1;
+    }
+    remove_temps();
 }
 
-# This is a bit more complex. $1 is sub-command: list, add, delete, modify
-
+# Helpers for subscription:
 # Validate the subscription data that follows a subscription add or modify
 # subcommand. $1=URL, $2=eventType, $3=maxRetries, $4=retryTimer
 # URL must look like URL, event type must be one of created deleted all,
 # maxRetries and retryTimer must be non-negative numbers.
-# If errors, sets variable status=1 and prints errors, else leaves
-# status unchanged.
+# If errors, returns false (0) and prints errors, else returns 1.
 #
-validate_subscription() {
-   if ! expr "$1" : "^http://.*" \| "$1" : "^https://.*" >/dev/null; then
-     echo "$myname: bad URL $1"
-     status=1
-   fi
-   if ! [ "$2" = created -o "$2" = deleted -o "$2" = all ]; then
-     echo "$myname: unrecognized event $2"
-     status=1
-   fi
-   if ! expr "$3" : "^[0-9][0-9]*$" >/dev/null; then
-     echo "$myname: invalid maximum retries count $3"
-     status=1	
-   fi
-   if ! expr "$4" : "^[0-9][0-9]*$" >/dev/null; then
-     echo "$myname: invalid retry time $4"
-     status=1	
-   fi
+sub validate_subscription(@) {
+    # Using the API parameter names
+    my $targetUrl = $_[0] || "";
+    my $eventType = $_[1] || "";
+    my $maxRetries = $_[2] || "";
+    my $retryTimer = $_[3] || "";
+    my $retval = 1;
+
+    if (! ($targetUrl =~ /^http:\/\/.*/ or $targetUrl =~ /^https:\/\/.*/)) {
+        print "$myname: bad URL $targetUrl\n";
+        $retval = 0;
+    }
+    if ($eventType ne "created" and $eventType ne "deleted" and
+        $eventType ne "all") {
+        print "$myname: unrecognized event $eventType\n";
+        $retval = 0;
+    }
+    if (! ($maxRetries =~ /^[0-9]+$/)) {
+        print "$myname: invalid maximum retries count $maxRetries\n";
+        $retval = 0;
+    }
+    if (! ($retryTimer =~ /^[0-9]+$/)) {
+        print "$myname: invalid retry time $retryTimer\n";
+        $retval = 0;
+    }
+    return $retval;
 }
 
-do_subscriptions() {
-  local urlpath
-  urlpath=$base_subs
-  case $1 in
-    (list)
-      if [ "x$2" != "x" ]; then
-        urlpath="$urlpath/$2"
-      fi
-      if rest GET $urlpath; then
-        json_reformat < $resultfile
-      else
-        status=1
-      fi
-    ;;
-    (add)
-      validate_subscription "$2" "$3" "$4" "$5"
-      if [ $status = 0 ]; then
-        if rest POST $urlpath \{\"targetUrl\":\"$2\",\"eventType\":\"$3\",\"maxRetries\":$4,\"retryTimer\":$5\} ; then
-          json_reformat < $resultfile
-        else
-          status=1
-	fi
-      fi
-    ;;
-    (delete|del)
-      if [ "x$2" != "x" ]; then
-        urlpath="$urlpath/$2"
-      else
-	echo "$myname: Subscription id required"
-	status=1
-      fi
-      if [ $status = 0 ]; then
-        if rest DELETE $urlpath; then
-          # Currently appmgr returns an empty result if
-          # delete is succesfull. Don't reformat file if empty.
-          if [ -s $resultfile ]; then
-            json_reformat < $resultfile
-          else
-            echo "Subscription $2 deleted"
-          fi
-        else
-          status=1
-	fi        
-      fi
-    ;;
-    (modify|mod)
-      if [ "x$2" != "x" ]; then
-        urlpath="$urlpath/$2"
-      else
-	echo "$myname: Subscription id required"
-	status=1
-      fi
-      if [ $status = 0 ]; then
-        validate_subscription "$3" "$4" "$5" "$6"
-        if [ $status = 0 ]; then
-          if rest PUT $urlpath \{\"targetUrl\":\"$3\",\"eventType\":\"$4\",\"maxRetries\":$5,\"retryTimer\":$6\} ; then
-            json_reformat < $resultfile
-          else
-            status=1
-          fi
-        fi
-      fi
-    ;;
-    (*)
-      echo "$myname: unrecognized subscriptions subcommand $1"
-      status=1
-  esac
+# Format a subscriptionRequest JSON object
+
+sub make_subscriptionRequest(@) {
+    my $targetUrl = $_[0];
+    my $eventType = $_[1];
+    my $maxRetries = $_[2];
+    my $retryTimer = $_[3];
+    return "{\"Data\": {\"TargetUrl\":\"$targetUrl\",\"EventType\":\"$eventType\",\"MaxRetries\":$maxRetries,\"RetryTimer\":$retryTimer}}";
 }
 
-do_config() {
-  local urlpath
-  urlpath=$base_config
-  case $1 in
-    (get|list)
-      if [ "x$2" != "x" ]; then
-        urlpath="$urlpath/$2"
-      fi
-      if rest GET $urlpath; then
-        json_reformat < $resultfile
-      else
-        status=1
-      fi
-    ;;
-    (add|update)
-      if rest POST $urlpath "@$2" ; then
-        cat $resultfile
-      else
-        status=1
-    fi
-    ;;
-    (del|delete|remove|rem)
-      if rest DELETE $urlpath "@$2" ; then
-        cat $resultfile
-      else
-        status=1
-    fi
-    ;;
-    (*)
-      echo "$myname: unrecognized config subcommand $1"
-      status=1
-  esac
+# Subscriptions:
+# $1 is sub-command: list, add, delete, modify
+
+sub do_subscriptions(@) {
+    my $subcommand = $_[0] || "";
+    shift;
+
+    my %subcommands = (
+        "list" => \&do_subscription_list,
+        "add" => \&do_subscription_add,
+        "delete" => \&do_subscription_delete,
+        "del" => \&do_subscription_delete,
+        "modify" => \&do_subscription_modify,
+        "mod" => \&do_subscription_modify
+    );
+    if (exists $subcommands{$subcommand}) {
+        $subcommands{$subcommand}(@_);
+    }
+    else {
+        print "$myname: unrecognized subscriptions subcommand $subcommand\n";
+        helphint();
+        $status=1
+    }
 }
 
-case $cmd in
-  (deploy)
-    do_deploy "$2"
-    ;;
-  (undeploy)
-    do_undeploy "$2"
-    ;;
-  (status)
-    do_status "$2" "$3"
-    ;;
-  (subscriptions)
-    do_subscriptions "$2" "$3" "$4" "$5" "$6" "$7"
-    ;;
-  (config)
-    do_config "$2" "$3"
-    ;;
-  (health)
-    if rest GET $base_health ; then
-      echo OK
-    else
-      echo NOT OK
-    fi
-    ;;
-esac
-remove_temps
-exit $status
+# list: With empty parameter, list all, else the parameter is
+# a subscriptionId
 
-# An Emacs hack to set the indentation style of this file
-# Local Variables:
-# sh-indentation:2
-# End:
+sub do_subscription_list(@) {
+    my $urlpath=$base_subs;
+    my $subscriptionId = $_[0] || "";
+    if ($subscriptionId ne "") {
+        $urlpath = "$urlpath/$subscriptionId";
+    }
+    make_temps();
+    if (rest("GET", $urlpath)) {
+        if ($http_code eq "200") {
+            print_json $resultfile;
+            $status = 0;
+        }
+        else {
+            my $error;
+            if ($http_code eq "400") {
+                $error = "INVALID SUBSCRIPTION ID $subscriptionId";
+            }
+            elsif ($http_code eq "404") {
+                $error = "SUBSCRIPTION $subscriptionId NOT FOUND";
+            }
+            elsif ($http_code eq "500") {
+                $error = "INTERNAL ERROR";
+            }
+            else {
+                $error = "UNKNOWN STATUS $http_code";
+            }
+            print "$error\n";
+            $status = 1;
+        }
+    }
+    else {
+        $status=1;
+    }
+    remove_temps();
+}
+
+sub do_subscription_add(@) {
+    my $urlpath=$base_subs;
+
+    if (validate_subscription(@_)) {
+        make_temps();
+        if (rest("POST", $urlpath, make_subscriptionRequest(@_))) {
+            if ($http_code eq "201") {
+                print_json $resultfile;
+                $status = 0;
+            }
+            else {
+                my $error;
+                if ($http_code eq "400") {
+                    $error = "INVALID INPUT";
+                }
+                elsif ($http_code eq "500") {
+                    $error = "INTERNAL ERROR";
+                }
+                else {
+                    $error = "UNKNOWN STATUS $http_code";
+                }
+                print "$error\n";
+                $status = 1;
+            }
+        }
+        else {
+            $status=1;
+        }
+        remove_temps();
+    }
+    else {
+        $status = 1;
+    }
+}
+
+sub do_subscription_delete(@) {
+    my $urlpath=$base_subs;
+    my $subscriptionId = $_[0] || "";
+    if ($subscriptionId ne "") {
+        $urlpath = "$urlpath/$subscriptionId";
+    }
+    else {
+        print "$myname: delete: Subscription id required\n";
+        $status=1;
+        return;
+    }
+    make_temps();
+    if (rest("DELETE", $urlpath)) {
+        if ($http_code eq "204") {
+            print "SUBSCRIPTION $subscriptionId DELETED\n";
+            $status = 0;
+        }
+        else {
+            my $error;
+            if ($http_code eq "400") {
+                $error = "INVALID SUBSCRIPTION ID $subscriptionId";
+            }
+            elsif ($http_code eq "500") {
+                $error = "INTERNAL ERROR";
+            }
+            else {
+                $error = "UNKNOWN STATUS $http_code";
+            }
+            print "$error\n";
+            $status = 1;
+        }
+    }
+    else {
+        $status = 1;
+    }
+    remove_temps();
+}
+
+sub do_subscription_modify(@) {
+    my $urlpath=$base_subs;
+    if (defined $_[0]) {
+        $urlpath = "$urlpath/$_[0]";
+    }
+    else {
+        print "$myname: modify: Subscription id required\n";
+        $status=1;
+        return;
+    }
+    shift;
+    if (validate_subscription(@_)) {
+        make_temps();
+        if (rest("PUT", $urlpath, make_subscriptionRequest(@_))) {
+            if ($http_code eq "200") {
+                print_json $resultfile;
+                $status = 0;
+            }
+            else {
+                my $error;
+                if ($http_code eq "400") {
+                    $error = "INVALID INPUT";
+                }
+                elsif ($http_code eq "500") {
+                    $error = "INTERNAL ERROR";
+                }
+                else {
+                    $error = "UNKNOWN STATUS $http_code";
+                }
+                print "$error\n";
+                $status = 1;
+            }
+        }
+        else {
+            $status=1;
+        }
+        remove_temps();
+    }
+    else {
+        $status = 1;
+    }
+}
+
+sub do_health(@) {
+    my $urlpath=$base_health;
+    my $check = $_[0] || "";
+    # API now defines two types of checks, either of
+    # which must be specified.
+    if ($check ne "alive" and $check ne "ready") {
+        print "$myname: health check type required (alive or ready)\n";
+        $status=1;
+        return;
+    }
+    $urlpath = "$urlpath/$check";
+    make_temps();
+    if (rest("GET", $urlpath)) {
+        my $res;
+        if ($check eq "alive") {
+            # If GET succeeds at all, the xapp manager is alive, no
+            # need to check the HTTP code.
+            $res = "ALIVE";
+        }
+        else {
+            if ($http_code eq "200") {
+                $res = "READY";
+            }
+            elsif ($http_code eq "503") {
+                $res = "NOT READY";
+            }
+            elsif ($http_code eq "500") {
+                $res = "INTERNAL ERROR";
+            }
+            else {
+                $res = "UNKNOWN STATUS $http_code";
+            }
+        }
+        print "$res\n";
+    }
+    else {
+        $status = 1;
+        print "$myname: health check failed to contact appmgr\n";
+    }
+    remove_temps();
+}
+
+sub do_config(@) {
+    my $subcommand = $_[0] || "";
+    shift;
+
+    my %subcommands = (
+        "list" => \&do_config_list,
+        "add" => \&do_config_add,
+        "delete" => \&do_config_delete,
+        "del" => \&do_config_delete,
+        "modify" => \&do_config_modify,
+        "mod" => \&do_config_modify
+    );
+    if (exists $subcommands{$subcommand}) {
+        $subcommands{$subcommand}(@_);
+    }
+    else {
+        print "$myname: unrecognized config subcommand $subcommand\n";
+        helphint();
+        $status=1
+    }
+}
+
+sub do_config_list(@) {
+    if (defined $_[0]) {
+        print "$myname: \"config list\" has no parameters\n";
+        $status = 1;
+        return;
+    }
+    make_temps();
+    if (rest("GET", $base_config)) {
+        if ($http_code eq "200") {
+            print_json $resultfile;
+            $status = 0;
+        }
+        else {
+            my $error;
+            if ($http_code eq "500") {
+                $error = "INTERNAL ERROR";
+            }
+            else {
+                $error = "UNKNOWN STATUS $http_code";
+            }
+            print "$error\n";
+            $status = 1;
+        }
+    }
+    else {
+        $status=1;
+    }
+    remove_temps();
+}
+
+# validate_config() checks configuration commmand line.
+# "config add" and "config modify" expect either single parameter which
+# must be a JSON file that contains the whole thing to send (see API),
+# or 5 parameters, where the first three are
+# $_[0] = name
+# $_[1] = configName (name of the configMap)
+# $_[2] = namespace
+# Followed by two file names:
+# $_[3] = file containing configSchema
+# $_[4] = file containing data for configMap
+# Giving the last two literally on the command line does not make much sense,
+# since they are arbitrary JSON data.
+# On success, returns parameter count (1 or 5), depending on which kind of
+# command line found.
+# 0 if errors.
+
+# Check only the 3 names at the beginning of config add/modify/delete
+sub validate_config_names(@) {
+    my $retval = 1;
+    # Names in the Kubernetes world consist of lowercase alphanumerics
+    # and - and . as specified in
+    # https://kubernetes.io/docs/concepts/overview/working-with-objects/name
+    for (my $idx = 0; $idx <= 2; ++$idx) {
+        if (! ($_[$idx] =~ /^[a-z][-a-z0-9.]*$/)) {
+            print "$myname: invalid characters in name $_[$idx]\n";
+            $retval = 0;
+        }
+    }
+    return $retval;
+}
+
+sub validate_config(@) {
+    my $retval = 1;
+    print "validate_config args @_\n";
+    if ($#_ == 0) {
+        if (! -r $_[0]) {
+            print "$myname: config file $_[0] cannot be read: $!\n";
+            $retval = 0;
+        }
+    }
+    elsif ($#_ == 4) {
+        $retval = 5;
+        if (! validate_config_names(@_)) {
+            $retval = 0;
+        }
+        for (my $idx = 3; $idx <= 4; ++$idx) {
+            if (! -r $_[$idx]) {
+                print "$myname: cannot read file $_[$idx]\n";
+                $retval = 0;
+            }
+        }
+    }
+    else {
+        print "$myname: config add: 1 or 5 parameter expected\n";
+        $retval = 0;
+    }
+    return $retval;
+}
+
+# Generate JSON for the xAppConfig element (see API).
+
+sub make_xAppConfigInfo($$$) {
+    return "{\"xAppName\":\"$_[0]\",\"configMapName\":\"$_[1]\",\"namespace\":\"$_[2]\"}";
+}
+
+sub make_xAppConfig(@) {
+    my $retval =  "{\"xAppConfigInfo\":" . make_xAppConfigInfo($_[0],$_[1],$_[2]);
+    my $fh;
+    open($fh, "<", $_[3]) or die "failed to open $_[3]";
+    my @obj = <$fh>;
+    close($fh);
+    $retval = $retval . ",\"configSchema\":" . join("", @obj);
+    open($fh, "<", $_[4]) or die "failed to open $_[4]";
+    @obj = <$fh>;
+    close($fh);
+    $retval = $retval . ",\"configMap\":" . join("", @obj) . "}";
+}
+
+sub do_config_add(@) {
+    my $paramCount;
+
+    $paramCount = validate_config(@_);
+    if ($paramCount > 0) {
+        my $xAppConfig;
+        if ($paramCount == 1) {
+            $xAppConfig = "\@$_[0]";
+        }
+        else {
+            $xAppConfig = make_xAppConfig(@_);
+        }
+        make_temps();
+        if (rest("POST", $base_config, $xAppConfig)) {
+            if ($http_code eq "201") {
+                print_json $resultfile;
+                $status = 0;
+            }
+            elsif ($http_code eq "422") { # Validation failed, details in result
+                print_json $resultfile;
+                $status = 1;
+	    }
+            else {
+                my $error;
+                if ($http_code eq "400") {
+                    $error = "INVALID INPUT";
+                }
+                elsif ($http_code eq "500") {
+                    $error = "INTERNAL ERROR";
+                }
+                else {
+                    $error = "UNKNOWN STATUS $http_code";
+                }
+                print "$error\n";
+                $status = 1;
+            }
+        }
+        else {
+            $status=1;
+        }
+        remove_temps();
+    }
+    else {
+        $status = 1;
+    }
+}
+
+sub do_config_modify(@) {
+    my $paramCount;
+
+    $paramCount = validate_config(@_);
+    if ($paramCount > 0) {
+        my $xAppConfig;
+        if ($paramCount == 1) {
+            $xAppConfig = "\@$_[0]";
+        }
+        else {
+            $xAppConfig = make_xAppConfig(@_);
+        }
+        make_temps();
+        if (rest("PUT", $base_config, $xAppConfig)) {
+            if ($http_code eq "200") {
+                print_json $resultfile;
+                $status = 0;
+            }
+            elsif ($http_code eq "422") { # Validation failed, details in result
+                print_json $resultfile;
+                $status = 1;
+	    }
+            else {
+                my $error;
+                if ($http_code eq "400") {
+                    $error = "INVALID INPUT";
+                }
+                elsif ($http_code eq "500") {
+                    $error = "INTERNAL ERROR";
+                }
+                else {
+                    $error = "UNKNOWN STATUS $http_code";
+                }
+                print "$error\n";
+                $status = 1;
+            }
+        }
+        else {
+            $status=1;
+        }
+        remove_temps();
+    }
+    else {
+        $status = 1;
+    }
+}
+
+# In config delete, allow either 1 parameter naming a file that contains
+# a JSON xAppConfigInfo object, or 3 parameters giving the
+# components (xAppName, configMapName, namespace), same as
+# in add and modify operations.
+
+sub do_config_delete(@) {
+    my $xAppConfigInfo = "";
+
+    if ($#_ != 0 and $#_ != 2) {
+        print "$myname: wrong number of parameters for config delete\n";
+        $status = 1;
+    }
+    elsif ($#_ == 0) {
+        if (-r $_[0]) {
+            $xAppConfigInfo = "\@$_[0]";
+        }
+        else {
+            print "$myname: config file $_[0] cannot be read: $!\n";
+            $status = 1;
+        }
+    }
+    elsif (($#_ == 2) && validate_config_names(@_)) {
+        $xAppConfigInfo = make_xAppConfigInfo($_[0],$_[1],$_[2]);
+    }
+    else {
+        print "$myname: bad parameters for config delete\n";
+        $status = 1;
+    }
+    if ($xAppConfigInfo ne "") {
+        make_temps();
+        if (rest("DELETE", $base_config, $xAppConfigInfo)) {
+            if ($http_code eq "204") {
+                print "SUCCESFUL DELETION OF CONFIG\n";
+                $status = 0;
+            }
+            else {
+                my $error;
+                if ($http_code eq "400") {
+                    $error = "INVALID PARAMETERS SUPPLIED";
+                }
+                elsif ($http_code eq "500") {
+                    $error = "INTERNAL ERROR";
+                }
+                else {
+                    $error = "UNKNOWN STATUS $http_code";
+                }
+                print "$error\n";
+                $status = 1;
+            }
+        }
+        else {
+            $status=1;
+        }
+        remove_temps();
+    }
+}