aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSrivahni Chivukula <srivahni.chivukula@intel.com>2020-02-21 10:08:04 -0800
committerSrivahni Chivukula <srivahni.chivukula@intel.com>2020-03-12 19:01:25 +0000
commitdef02dfebada38bde91dc98db85eff3d8923a923 (patch)
tree8888dc51acadb7c42fcfd0dedc2dbd6674802446 /src
parentaf34fbc0e67668723eb69a800165f6bfb5100dbf (diff)
Add apps under composite app API
Implemented create, get and delete handlers for the apps under composite app. Added unit tests Added remove function to mockdb Handled multipart POST request to upload file along with app data. Issue-ID: MULTICLOUD-998 Signed-off-by: Srivahni Chivukula <srivahni.chivukula@intel.com> Change-Id: I25c1faba1212c0cc881c2cd599e8e66a7b93033e
Diffstat (limited to 'src')
-rw-r--r--src/orchestrator/api/api.go13
-rw-r--r--src/orchestrator/api/apphandler.go224
-rw-r--r--src/orchestrator/api/clusterhandler_test.go34
-rw-r--r--src/orchestrator/api/composite_app_handler.go (renamed from src/orchestrator/api/compositeapphandler.go)1
-rw-r--r--src/orchestrator/api/composite_profilehandler_test.go2
-rw-r--r--src/orchestrator/api/controllerhandler_test.go6
-rw-r--r--src/orchestrator/api/projecthandler_test.go6
-rw-r--r--src/orchestrator/cmd/main.go2
-rw-r--r--src/orchestrator/pkg/infra/db/mock.go4
-rw-r--r--src/orchestrator/pkg/module/app.go202
-rw-r--r--src/orchestrator/pkg/module/app_test.go327
-rw-r--r--src/orchestrator/pkg/module/compositeapp.go8
-rw-r--r--src/orchestrator/pkg/module/module.go2
13 files changed, 801 insertions, 30 deletions
diff --git a/src/orchestrator/api/api.go b/src/orchestrator/api/api.go
index 9b33daf2..70b40d96 100644
--- a/src/orchestrator/api/api.go
+++ b/src/orchestrator/api/api.go
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package api
import (
@@ -25,6 +26,7 @@ var moduleClient *moduleLib.Client
// NewRouter creates a router that registers the various urls that are supported
func NewRouter(projectClient moduleLib.ProjectManager,
compositeAppClient moduleLib.CompositeAppManager,
+ appClient moduleLib.AppManager,
ControllerClient moduleLib.ControllerManager,
clusterClient moduleLib.ClusterManager,
genericPlacementIntentClient moduleLib.GenericPlacementIntentManager,
@@ -73,6 +75,17 @@ func NewRouter(projectClient moduleLib.ProjectManager,
router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}", compAppHandler.getHandler).Methods("GET")
router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}", compAppHandler.deleteHandler).Methods("DELETE")
+ if appClient == nil {
+ appClient = moduleClient.App
+ }
+ appHandler := appHandler{
+ client: appClient,
+ }
+
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/apps", appHandler.createAppHandler).Methods("POST")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/apps/{app-name}", appHandler.getAppHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/apps/{app-name}", appHandler.deleteAppHandler).Methods("DELETE")
+
if compositeProfileClient == nil {
compositeProfileClient = moduleClient.CompositeProfile
}
diff --git a/src/orchestrator/api/apphandler.go b/src/orchestrator/api/apphandler.go
new file mode 100644
index 00000000..3cd2dbc8
--- /dev/null
+++ b/src/orchestrator/api/apphandler.go
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2020 Intel Corporation, Inc
+ *
+ * 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 api
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "io"
+ "io/ioutil"
+ "mime"
+ "mime/multipart"
+ "net/http"
+ "net/textproto"
+
+ moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
+ "github.com/onap/multicloud-k8s/src/orchestrator/utils"
+
+ "github.com/gorilla/mux"
+)
+
+// appHandler to store backend implementations objects
+// Also simplifies mocking for unit testing purposes
+type appHandler struct {
+ // Interface that implements App operations
+ // We will set this variable with a mock interface for testing
+ client moduleLib.AppManager
+}
+
+// createAppHandler handles creation of the App entry in the database
+// This is a multipart handler. See following example curl request
+// curl -X POST http://localhost:9015/v2/projects/sampleProject/composite-apps/sampleCompositeApp/v1/apps \
+// -F "metadata={\"metadata\":{\"name\":\"app\",\"description\":\"sample app\",\"UserData1\":\"data1\",\"UserData2\":\"data2\"}};type=application/json" \
+// -F file=@/pathToFile
+
+func (h appHandler) createAppHandler(w http.ResponseWriter, r *http.Request) {
+ var a moduleLib.App
+ var ac moduleLib.AppContent
+
+ // Implemenation using multipart form
+ // Set Max size to 16mb here
+ err := r.ParseMultipartForm(16777216)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ jsn := bytes.NewBuffer([]byte(r.FormValue("metadata")))
+ err = json.NewDecoder(jsn).Decode(&a)
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Name is required.
+ if a.Metadata.Name == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ //Read the file section and ignore the header
+ file, _, err := r.FormFile("file")
+ if err != nil {
+ http.Error(w, "Unable to process file", http.StatusUnprocessableEntity)
+ return
+ }
+
+ defer file.Close()
+
+ //Convert the file content to base64 for storage
+ content, err := ioutil.ReadAll(file)
+ if err != nil {
+ http.Error(w, "Unable to read file", http.StatusUnprocessableEntity)
+ return
+ }
+
+ err = utils.IsTarGz(bytes.NewBuffer(content))
+ if err != nil {
+ http.Error(w, "Error in file format", http.StatusUnprocessableEntity)
+ return
+ }
+
+ ac.FileContent = base64.StdEncoding.EncodeToString(content)
+
+ vars := mux.Vars(r)
+ projectName := vars["project-name"]
+ compositeAppName := vars["composite-app-name"]
+ compositeAppVersion := vars["version"]
+
+ ret, err := h.client.CreateApp(a, ac, projectName, compositeAppName, compositeAppVersion)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// getAppHandler handles GET operations on a particular App Name
+// Returns an app
+func (h appHandler) getAppHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ projectName := vars["project-name"]
+ compositeAppName := vars["composite-app-name"]
+ compositeAppVersion := vars["version"]
+ name := vars["app-name"]
+
+ accepted, _, err := mime.ParseMediaType(r.Header.Get("Accept"))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusNotAcceptable)
+ return
+ }
+
+ var retApp moduleLib.App
+ var retAppContent moduleLib.AppContent
+
+ retApp, err = h.client.GetApp(name, projectName, compositeAppName, compositeAppVersion)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ retAppContent, err = h.client.GetAppContent(name, projectName, compositeAppName, compositeAppVersion)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ switch accepted {
+ case "multipart/form-data":
+ mpw := multipart.NewWriter(w)
+ w.Header().Set("Content-Type", mpw.FormDataContentType())
+ w.WriteHeader(http.StatusOK)
+ pw, err := mpw.CreatePart(textproto.MIMEHeader{"Content-Type": {"application/json"}, "Content-Disposition": {"form-data; name=metadata"}})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := json.NewEncoder(pw).Encode(retApp); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ pw, err = mpw.CreatePart(textproto.MIMEHeader{"Content-Type": {"application/octet-stream"}, "Content-Disposition": {"form-data; name=file; filename=fileContent"}})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ fcBytes, err := base64.StdEncoding.DecodeString(retAppContent.FileContent)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = pw.Write(fcBytes)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ case "application/json":
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ err = json.NewEncoder(w).Encode(retApp)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ case "application/octet-stream":
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.WriteHeader(http.StatusOK)
+ fcBytes, err := base64.StdEncoding.DecodeString(retAppContent.FileContent)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(fcBytes)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ default:
+ http.Error(w, "set Accept: multipart/form-data, application/json or application/octet-stream", http.StatusMultipleChoices)
+ return
+ }
+}
+
+// deleteAppHandler handles DELETE operations on a particular App Name
+func (h appHandler) deleteAppHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ projectName := vars["project-name"]
+ compositeAppName := vars["composite-app-name"]
+ compositeAppVersion := vars["version"]
+ name := vars["app-name"]
+
+ err := h.client.DeleteApp(name, projectName, compositeAppName, compositeAppVersion)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/orchestrator/api/clusterhandler_test.go b/src/orchestrator/api/clusterhandler_test.go
index 71afdd1b..e5161a42 100644
--- a/src/orchestrator/api/clusterhandler_test.go
+++ b/src/orchestrator/api/clusterhandler_test.go
@@ -229,7 +229,7 @@ func TestClusterProviderCreateHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v2/cluster-providers", testCase.reader)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -307,7 +307,7 @@ func TestClusterProviderGetAllHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/cluster-providers", nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -377,7 +377,7 @@ func TestClusterProviderGetHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/cluster-providers/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -426,7 +426,7 @@ func TestClusterProviderDeleteHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v2/cluster-providers/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -538,7 +538,7 @@ of clusterTest
request := httptest.NewRequest("POST", "/v2/cluster-providers/clusterProvider1/clusters", bytes.NewBuffer(body.Bytes()))
request.Header.Set("Content-Type", multiwr.FormDataContentType())
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -625,7 +625,7 @@ func TestClusterGetAllHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/cluster-providers/clusterProvder1/clusters", nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -706,7 +706,7 @@ func TestClusterGetHandler(t *testing.T) {
if len(testCase.accept) > 0 {
request.Header.Set("Accept", testCase.accept)
}
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -784,7 +784,7 @@ of clusterTest
if len(testCase.accept) > 0 {
request.Header.Set("Accept", testCase.accept)
}
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -834,7 +834,7 @@ func TestClusterDeleteHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v2/cluster-providers/clusterProvider1/clusters/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -880,7 +880,7 @@ func TestClusterLabelCreateHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v2/cluster-providers/cp1/clusters/cl1/labels", testCase.reader)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -944,7 +944,7 @@ func TestClusterLabelsGetHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/cluster-providers/cp1/clusters/cl1/labels", nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -1004,7 +1004,7 @@ func TestClusterLabelGetHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/cluster-providers/clusterProvider1/clusters/cl1/labels/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -1053,7 +1053,7 @@ func TestClusterLabelDeleteHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v2/cluster-providers/cp1/clusters/cl1/labels/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -1144,7 +1144,7 @@ func TestClusterKvPairsCreateHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v2/cluster-providers/cp1/clusters/cl1/kv-pairs", testCase.reader)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -1262,7 +1262,7 @@ func TestClusterKvPairsGetAllHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/cluster-providers/cp1/clusters/cl1/kv-pairs", nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -1352,7 +1352,7 @@ func TestClusterKvPairsGetHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/cluster-providers/clusterProvider1/clusters/cl1/kv-pairs/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -1401,7 +1401,7 @@ func TestClusterKvPairsDeleteHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v2/cluster-providers/cp1/clusters/cl1/kv-pairs/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
diff --git a/src/orchestrator/api/compositeapphandler.go b/src/orchestrator/api/composite_app_handler.go
index 42c72cdb..b54c488e 100644
--- a/src/orchestrator/api/compositeapphandler.go
+++ b/src/orchestrator/api/composite_app_handler.go
@@ -35,7 +35,6 @@ type compositeAppHandler struct {
}
// createHandler handles creation of the CompositeApp entry in the database
-// This is a multipart handler
func (h compositeAppHandler) createHandler(w http.ResponseWriter, r *http.Request) {
var c moduleLib.CompositeApp
diff --git a/src/orchestrator/api/composite_profilehandler_test.go b/src/orchestrator/api/composite_profilehandler_test.go
index 360653c7..7c84f12a 100644
--- a/src/orchestrator/api/composite_profilehandler_test.go
+++ b/src/orchestrator/api/composite_profilehandler_test.go
@@ -128,7 +128,7 @@ func Test_compositeProfileHandler_createHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v2/projects/{project-name}/composite-apps/{composite-app-name}/{version}/composite-profiles", testCase.reader)
- resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil, nil, nil, testCase.cProfClient, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil, nil, nil, nil, testCase.cProfClient, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
diff --git a/src/orchestrator/api/controllerhandler_test.go b/src/orchestrator/api/controllerhandler_test.go
index ab0aeed8..3c543cb8 100644
--- a/src/orchestrator/api/controllerhandler_test.go
+++ b/src/orchestrator/api/controllerhandler_test.go
@@ -110,7 +110,7 @@ func TestControllerCreateHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v2/controllers", testCase.reader)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -173,7 +173,7 @@ func TestControllerGetHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/controllers/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -222,7 +222,7 @@ func TestControllerDeleteHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v2/controllers/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
diff --git a/src/orchestrator/api/projecthandler_test.go b/src/orchestrator/api/projecthandler_test.go
index af40f961..5e820aa2 100644
--- a/src/orchestrator/api/projecthandler_test.go
+++ b/src/orchestrator/api/projecthandler_test.go
@@ -119,7 +119,7 @@ func TestProjectCreateHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v2/projects", testCase.reader)
- resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -188,7 +188,7 @@ func TestProjectGetHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/projects/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -237,7 +237,7 @@ func TestProjectDeleteHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v2/projects/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
diff --git a/src/orchestrator/cmd/main.go b/src/orchestrator/cmd/main.go
index f95c057e..001903a7 100644
--- a/src/orchestrator/cmd/main.go
+++ b/src/orchestrator/cmd/main.go
@@ -47,7 +47,7 @@ func main() {
log.Fatalln("Exiting...")
}
- httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
+ httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter)
log.Println("Starting Kubernetes Multicloud API")
diff --git a/src/orchestrator/pkg/infra/db/mock.go b/src/orchestrator/pkg/infra/db/mock.go
index 79366d10..e43be8fb 100644
--- a/src/orchestrator/pkg/infra/db/mock.go
+++ b/src/orchestrator/pkg/infra/db/mock.go
@@ -96,3 +96,7 @@ func (m *MockDB) Find(table string, key Key, tag string) ([][]byte, error) {
func (m *MockDB) Delete(table string, key Key, tag string) error {
return m.Err
}
+
+func (m *MockDB) Remove(table string, key Key) error {
+ return m.Err
+}
diff --git a/src/orchestrator/pkg/module/app.go b/src/orchestrator/pkg/module/app.go
new file mode 100644
index 00000000..c25a1b51
--- /dev/null
+++ b/src/orchestrator/pkg/module/app.go
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2020 Intel Corporation, Inc
+ *
+ * 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 governinog permissions and
+ * limitations under the License.
+ */
+
+package module
+
+import (
+ "encoding/json"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// App contains metadata for Apps
+type App struct {
+ Metadata AppMetaData `json:"metadata"`
+}
+
+//AppMetaData contains the parameters needed for Apps
+type AppMetaData struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ UserData1 string `json:"userData1"`
+ UserData2 string `json:"userData2"`
+}
+
+//AppContent contains fileContent
+type AppContent struct {
+ FileContent string
+}
+
+// AppKey is the key structure that is used in the database
+type AppKey struct {
+ App string `json:"app"`
+ Project string `json:"project"`
+ CompositeApp string `json:"compositeapp"`
+ CompositeAppVersion string `json:"compositeappversion"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (aK AppKey) String() string {
+ out, err := json.Marshal(aK)
+ if err != nil {
+ return ""
+ }
+ return string(out)
+}
+
+// AppManager is an interface exposes the App functionality
+type AppManager interface {
+ CreateApp(a App, ac AppContent, p string, cN string, cV string) (App, error)
+ GetApp(name string, p string, cN string, cV string) (App, error)
+ GetAppContent(name string, p string, cN string, cV string) (AppContent, error)
+ DeleteApp(name string, p string, cN string, cV string) error
+}
+
+// AppClient implements the AppManager
+// It will also be used to maintain some localized state
+type AppClient struct {
+ storeName string
+ tagMeta, tagContent string
+}
+
+// NewAppClient returns an instance of the AppClient
+// which implements the AppManager
+func NewAppClient() *AppClient {
+ return &AppClient{
+ storeName: "orchestrator",
+ tagMeta: "appmetadata",
+ tagContent: "appcontent",
+ }
+}
+
+// CreateApp creates a new collection based on the App
+func (v *AppClient) CreateApp(a App, ac AppContent, p string, cN string, cV string) (App, error) {
+
+ //Construct the composite key to select the entry
+ key := AppKey{
+ App: a.Metadata.Name,
+ Project: p,
+ CompositeApp: cN,
+ CompositeAppVersion: cV,
+ }
+
+ //Check if this App already exists
+ _, err := v.GetApp(a.Metadata.Name, p, cN, cV)
+ if err == nil {
+ return App{}, pkgerrors.New("App already exists")
+ }
+
+ //Check if Project exists
+ _, err = NewProjectClient().GetProject(p)
+ if err != nil {
+ return App{}, pkgerrors.New("Unable to find the project")
+ }
+
+ //check if CompositeApp with version exists
+ _, err = NewCompositeAppClient().GetCompositeApp(cN, cV, p)
+ if err != nil {
+ return App{}, pkgerrors.New("Unable to find the composite app with version")
+ }
+
+ err = db.DBconn.Insert(v.storeName, key, nil, v.tagMeta, a)
+ if err != nil {
+ return App{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ err = db.DBconn.Insert(v.storeName, key, nil, v.tagContent, ac)
+ if err != nil {
+ return App{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return a, nil
+}
+
+// GetApp returns the App for corresponding name
+func (v *AppClient) GetApp(name string, p string, cN string, cV string) (App, error) {
+
+ //Construct the composite key to select the entry
+ key := AppKey{
+ App: name,
+ Project: p,
+ CompositeApp: cN,
+ CompositeAppVersion: cV,
+ }
+ value, err := db.DBconn.Find(v.storeName, key, v.tagMeta)
+ if err != nil {
+ return App{}, pkgerrors.Wrap(err, "Get app")
+ }
+
+ //value is a byte array
+ if value != nil {
+ app := App{}
+ err = db.DBconn.Unmarshal(value[0], &app)
+ if err != nil {
+ return App{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ return app, nil
+ }
+
+ return App{}, pkgerrors.New("Error getting app")
+}
+
+// GetAppContent returns content for corresponding app
+func (v *AppClient) GetAppContent(name string, p string, cN string, cV string) (AppContent, error) {
+
+ //Construct the composite key to select the entry
+ key := AppKey{
+ App: name,
+ Project: p,
+ CompositeApp: cN,
+ CompositeAppVersion: cV,
+ }
+ value, err := db.DBconn.Find(v.storeName, key, v.tagContent)
+ if err != nil {
+ return AppContent{}, pkgerrors.Wrap(err, "Get app content")
+ }
+
+ //value is a byte array
+ if value != nil {
+ ac := AppContent{}
+ err = db.DBconn.Unmarshal(value[0], &ac)
+ if err != nil {
+ return AppContent{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ return ac, nil
+ }
+
+ return AppContent{}, pkgerrors.New("Error getting app content")
+}
+
+// DeleteApp deletes the App from database
+func (v *AppClient) DeleteApp(name string, p string, cN string, cV string) error {
+
+ //Construct the composite key to select the entry
+ key := AppKey{
+ App: name,
+ Project: p,
+ CompositeApp: cN,
+ CompositeAppVersion: cV,
+ }
+ err := db.DBconn.Remove(v.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete App Entry;")
+ }
+
+ return nil
+}
diff --git a/src/orchestrator/pkg/module/app_test.go b/src/orchestrator/pkg/module/app_test.go
new file mode 100644
index 00000000..42c08ef6
--- /dev/null
+++ b/src/orchestrator/pkg/module/app_test.go
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2020 Intel Corporation, Inc
+ *
+ * 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 module
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+ pkgerrors "github.com/pkg/errors"
+ // pkgerrors "github.com/pkg/errors"
+)
+
+func TestCreateApp(t *testing.T) {
+ testCases := []struct {
+ label string
+ inpApp App
+ inpAppContent AppContent
+ inpProject string
+ inpCompositeAppName string
+ inpCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ expected App
+ }{
+ {
+ label: "Create App",
+ inpApp: App{
+ Metadata: AppMetaData{
+ Name: "testApp",
+ Description: "A sample app used for unit testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ },
+
+ inpAppContent: AppContent{
+ FileContent: "Sample file content",
+ },
+ inpProject: "testProject",
+ inpCompositeAppName: "testCompositeApp",
+ inpCompositeAppVersion: "v1",
+ expected: App{
+ Metadata: AppMetaData{
+ Name: "testApp",
+ Description: "A sample app used for unit testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ ProjectKey{ProjectName: "testProject"}.String(): {
+ "projectmetadata": []byte(
+ "{" +
+ "\"metadata\": {" +
+ "\"Name\": \"testProject\"," +
+ "\"Description\": \"Test project for unit testing\"," +
+ "\"UserData1\": \"userData1\"," +
+ "\"UserData2\": \"userData2\"}" +
+ "}"),
+ },
+ CompositeAppKey{CompositeAppName: "testCompositeApp", Version: "v1", Project: "testProject"}.String(): {
+ "compositeapp": []byte(
+ "{" +
+ "\"metadata\":{" +
+ "\"Name\":\"testCompositeApp\"," +
+ "\"Description\":\"Test CompositeApp for unit testing\"," +
+ "\"UserData1\":\"userData1\"," +
+ "\"UserData2\":\"userData2\"}," +
+ "\"spec\":{" +
+ "\"Version\":\"v1\"}" +
+ "}"),
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewAppClient()
+ got, err := impl.CreateApp(testCase.inpApp, testCase.inpAppContent, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Create returned an unexpected error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Create returned an unexpected error %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Create returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestGetApp(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpApp string
+ inpProject string
+ inpCompositeAppName string
+ inpCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ expected App
+ }{
+ {
+ label: "Get Composite App",
+ inpApp: "testApp",
+ inpProject: "testProject",
+ inpCompositeAppName: "testCompositeApp",
+ inpCompositeAppVersion: "v1",
+ expected: App{
+ Metadata: AppMetaData{
+ Name: "testApp",
+ Description: "Test App for unit testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): {
+ "appmetadata": []byte(
+ "{" +
+ "\"metadata\": {" +
+ "\"Name\": \"testApp\"," +
+ "\"Description\": \"Test App for unit testing\"," +
+ "\"UserData1\": \"userData1\"," +
+ "\"UserData2\": \"userData2\"}" +
+ "}"),
+ "appcontent": []byte(
+ "{" +
+ "\"FileContent\": \"sample file content\"" +
+ "}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Get Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewAppClient()
+ got, err := impl.GetApp(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Get returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestGetAppContent(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpApp string
+ inpProject string
+ inpCompositeAppName string
+ inpCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ expected AppContent
+ }{
+ {
+ label: "Get App content",
+ inpApp: "testApp",
+ inpProject: "testProject",
+ inpCompositeAppName: "testCompositeApp",
+ inpCompositeAppVersion: "v1",
+ expected: AppContent{
+ FileContent: "Samplefilecontent",
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): {
+ "appmetadata": []byte(
+ "{" +
+ "\"metadata\": {" +
+ "\"Name\": \"testApp\"," +
+ "\"Description\": \"Test App for unit testing\"," +
+ "\"UserData1\": \"userData1\"," +
+ "\"UserData2\": \"userData2\"}" +
+ "}"),
+ "appcontent": []byte(
+ "{" +
+ "\"FileContent\": \"Samplefilecontent\"" +
+ "}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Get Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewAppClient()
+ got, err := impl.GetAppContent(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Get returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestDeleteApp(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpApp string
+ inpProject string
+ inpCompositeAppName string
+ inpCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ }{
+ {
+ label: "Delete App",
+ inpApp: "testApp",
+ inpProject: "testProject",
+ inpCompositeAppName: "testCompositeApp",
+ inpCompositeAppVersion: "v1",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): {
+ "appmetadata": []byte(
+ "{" +
+ "\"metadata\": {" +
+ "\"Name\": \"testApp\"," +
+ "\"Description\": \"Test App for unit testing\"," +
+ "\"UserData1\": \"userData1\"," +
+ "\"UserData2\": \"userData2\"}" +
+ "}"),
+ "appcontent": []byte(
+ "{" +
+ "\"FileContent\": \"Samplefilecontent\"" +
+ "}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Delete Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewAppClient()
+ err := impl.DeleteApp(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Delete returned an unexpected error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Delete returned an unexpected error %s", err)
+ }
+ }
+ })
+ }
+}
diff --git a/src/orchestrator/pkg/module/compositeapp.go b/src/orchestrator/pkg/module/compositeapp.go
index 74fbe0d5..59fbbab5 100644
--- a/src/orchestrator/pkg/module/compositeapp.go
+++ b/src/orchestrator/pkg/module/compositeapp.go
@@ -105,7 +105,7 @@ func (v *CompositeAppClient) CreateCompositeApp(c CompositeApp, p string) (Compo
return CompositeApp{}, pkgerrors.New("Unable to find the project")
}
- err = db.DBconn.Create(v.storeName, key, v.tagMeta, c)
+ err = db.DBconn.Insert(v.storeName, key, nil, v.tagMeta, c)
if err != nil {
return CompositeApp{}, pkgerrors.Wrap(err, "Creating DB Entry")
}
@@ -122,7 +122,7 @@ func (v *CompositeAppClient) GetCompositeApp(name string, version string, p stri
Version: version,
Project: p,
}
- value, err := db.DBconn.Read(v.storeName, key, v.tagMeta)
+ value, err := db.DBconn.Find(v.storeName, key, v.tagMeta)
if err != nil {
return CompositeApp{}, pkgerrors.Wrap(err, "Get composite application")
}
@@ -130,7 +130,7 @@ func (v *CompositeAppClient) GetCompositeApp(name string, version string, p stri
//value is a byte array
if value != nil {
compApp := CompositeApp{}
- err = db.DBconn.Unmarshal(value, &compApp)
+ err = db.DBconn.Unmarshal(value[0], &compApp)
if err != nil {
return CompositeApp{}, pkgerrors.Wrap(err, "Unmarshaling Value")
}
@@ -149,7 +149,7 @@ func (v *CompositeAppClient) DeleteCompositeApp(name string, version string, p s
Version: version,
Project: p,
}
- err := db.DBconn.Delete(v.storeName, key, v.tagMeta)
+ err := db.DBconn.Remove(v.storeName, key)
if err != nil {
return pkgerrors.Wrap(err, "Delete CompositeApp Entry;")
}
diff --git a/src/orchestrator/pkg/module/module.go b/src/orchestrator/pkg/module/module.go
index 8f2948dd..77a2f5bf 100644
--- a/src/orchestrator/pkg/module/module.go
+++ b/src/orchestrator/pkg/module/module.go
@@ -20,6 +20,7 @@ package module
type Client struct {
Project *ProjectClient
CompositeApp *CompositeAppClient
+ App *AppClient
Controller *ControllerClient
Cluster *ClusterClient
GenericPlacementIntent *GenericPlacementIntentClient
@@ -36,6 +37,7 @@ func NewClient() *Client {
c := &Client{}
c.Project = NewProjectClient()
c.CompositeApp = NewCompositeAppClient()
+ c.App = NewAppClient()
c.Controller = NewControllerClient()
c.Cluster = NewClusterClient()
c.GenericPlacementIntent = NewGenericPlacementIntentClient()