Add API support to handle App Profiles 01/102901/5
authorEric Multanen <eric.w.multanen@intel.com>
Thu, 20 Feb 2020 00:19:18 +0000 (16:19 -0800)
committerEric Multanen <eric.w.multanen@intel.com>
Fri, 6 Mar 2020 00:29:23 +0000 (16:29 -0800)
Add support for profiles per App under
composite profiles.  See:
https://wiki.onap.org/display/DW/V2+API+Specification#V2APISpecification-Addingprofileperapplication

Issue-ID: MULTICLOUD-997
Signed-off-by: Eric Multanen <eric.w.multanen@intel.com>
Change-Id: I8569066d903327d640e315d0de835605ca1779c0

src/orchestrator/api/api.go
src/orchestrator/api/app_profilehandler.go [new file with mode: 0644]
src/orchestrator/api/clusterhandler_test.go
src/orchestrator/api/composite_profilehandler_test.go
src/orchestrator/api/controllerhandler_test.go
src/orchestrator/api/projecthandler_test.go
src/orchestrator/cmd/main.go
src/orchestrator/pkg/module/app_profile.go [new file with mode: 0644]
src/orchestrator/pkg/module/module.go
src/orchestrator/utils/utils.go [new file with mode: 0644]
src/orchestrator/utils/utils_test.go [new file with mode: 0644]

index 7c540dd..9b33daf 100644 (file)
@@ -31,7 +31,8 @@ func NewRouter(projectClient moduleLib.ProjectManager,
        appIntentClient moduleLib.AppIntentManager,
        deploymentIntentGrpClient moduleLib.DeploymentIntentGroupManager,
        intentClient moduleLib.IntentManager,
-       compositeProfileClient moduleLib.CompositeProfileManager) *mux.Router {
+       compositeProfileClient moduleLib.CompositeProfileManager,
+       appProfileClient moduleLib.AppProfileManager) *mux.Router {
 
        router := mux.NewRouter().PathPrefix("/v2").Subrouter()
 
@@ -78,11 +79,28 @@ func NewRouter(projectClient moduleLib.ProjectManager,
        compProfilepHandler := compositeProfileHandler{
                client: compositeProfileClient,
        }
+       if appProfileClient == nil {
+               appProfileClient = moduleClient.AppProfile
+       }
+       appProfileHandler := appProfileHandler{
+               client: appProfileClient,
+       }
 
        router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles", compProfilepHandler.createHandler).Methods("POST")
        router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles", compProfilepHandler.getHandler).Methods("GET")
        router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}", compProfilepHandler.getHandler).Methods("GET")
        router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}", compProfilepHandler.deleteHandler).Methods("DELETE")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.createAppProfileHandler).Methods("POST")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.getAppProfileHandler).Methods("GET")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.getAppProfileHandler).Queries("app-name", "{app-name}")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.getAppProfileHandler).Methods("GET")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.deleteAppProfileHandler).Methods("DELETE")
+
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.createAppProfileHandler).Methods("POST")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.getAppProfileHandler).Methods("GET")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.getAppProfileHandler).Queries("app-name", "{app-name}")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.getAppProfileHandler).Methods("GET")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.deleteAppProfileHandler).Methods("DELETE")
 
        router.HandleFunc("/controllers", controlHandler.createHandler).Methods("POST")
        router.HandleFunc("/controllers", controlHandler.createHandler).Methods("PUT")
diff --git a/src/orchestrator/api/app_profilehandler.go b/src/orchestrator/api/app_profilehandler.go
new file mode 100644 (file)
index 0000000..1642348
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * 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"
+       pkgerrors "github.com/pkg/errors"
+)
+
+/* Used to store backend implementation objects
+Also simplifies mocking for unit testing purposes
+*/
+type appProfileHandler struct {
+       client moduleLib.AppProfileManager
+}
+
+// createAppProfileHandler handles the create operation
+func (h appProfileHandler) createAppProfileHandler(w http.ResponseWriter, r *http.Request) {
+
+       vars := mux.Vars(r)
+       project := vars["project-name"]
+       compositeApp := vars["composite-app-name"]
+       compositeAppVersion := vars["composite-app-version"]
+       compositeProfile := vars["composite-profile-name"]
+
+       var ap moduleLib.AppProfile
+       var ac moduleLib.AppProfileContent
+
+       // Implemenation using multipart form
+       // Review and enable/remove at a later date
+       // 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(&ap)
+       switch {
+       case err == io.EOF:
+               http.Error(w, "Empty body", http.StatusBadRequest)
+               return
+       case err != nil:
+               http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+               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.Profile = base64.StdEncoding.EncodeToString(content)
+
+       // Name is required.
+       if ap.Metadata.Name == "" {
+               http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+               return
+       }
+
+       ret, err := h.client.CreateAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, ap, ac)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       w.WriteHeader(http.StatusCreated)
+       err = json.NewEncoder(w).Encode(ret)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
+// getHandler handles the GET operations on AppProfile
+func (h appProfileHandler) getAppProfileHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       project := vars["project-name"]
+       compositeApp := vars["composite-app-name"]
+       compositeAppVersion := vars["composite-app-version"]
+       compositeProfile := vars["composite-profile-name"]
+       name := vars["app-profile"]
+       appName := r.URL.Query().Get("app-name")
+
+       if len(name) != 0 && len(appName) != 0 {
+               http.Error(w, pkgerrors.New("Invalid query").Error(), http.StatusInternalServerError)
+               return
+       }
+
+       // handle the get all app profiles case - return a list of only the json parts
+       if len(name) == 0 && len(appName) == 0 {
+               var retList []moduleLib.AppProfile
+
+               ret, err := h.client.GetAppProfiles(project, compositeApp, compositeAppVersion, compositeProfile)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+
+               for _, ap := range ret {
+                       retList = append(retList, ap)
+               }
+
+               w.Header().Set("Content-Type", "application/json")
+               w.WriteHeader(http.StatusOK)
+               err = json.NewEncoder(w).Encode(retList)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+               return
+       }
+
+       accepted, _, err := mime.ParseMediaType(r.Header.Get("Accept"))
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusNotAcceptable)
+               return
+       }
+
+       var retAppProfile moduleLib.AppProfile
+       var retAppProfileContent moduleLib.AppProfileContent
+
+       if len(appName) != 0 {
+               retAppProfile, err = h.client.GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+
+               retAppProfileContent, err = h.client.GetAppProfileContentByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+       } else {
+               retAppProfile, err = h.client.GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, name)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+
+               retAppProfileContent, err = h.client.GetAppProfileContent(project, compositeApp, compositeAppVersion, compositeProfile, name)
+               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(retAppProfile); 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"}})
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+               kc_bytes, err := base64.StdEncoding.DecodeString(retAppProfileContent.Profile)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+               _, err = pw.Write(kc_bytes)
+               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(retAppProfile)
+               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)
+               kc_bytes, err := base64.StdEncoding.DecodeString(retAppProfileContent.Profile)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+               _, err = w.Write(kc_bytes)
+               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
+       }
+}
+
+// deleteHandler handles the delete operations on AppProfile
+func (h appProfileHandler) deleteAppProfileHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       project := vars["project-name"]
+       compositeApp := vars["composite-app-name"]
+       compositeAppVersion := vars["composite-app-version"]
+       compositeProfile := vars["composite-profile-name"]
+       name := vars["app-profile"]
+
+       err := h.client.DeleteAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, name)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       w.WriteHeader(http.StatusNoContent)
+}
index 58db636..71afdd1 100644 (file)
@@ -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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
index e98ed96..360653c 100644 (file)
@@ -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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil, nil, nil, testCase.cProfClient, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
index a195cb8..ab0aeed 100644 (file)
@@ -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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(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))
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
index 547420b..af40f96 100644 (file)
@@ -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))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, 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))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, 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))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
index 0385416..aa93b9c 100644 (file)
@@ -47,7 +47,7 @@ func main() {
                log.Fatalln("Exiting...")
        }
 
-       httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil, nil, nil, nil)
+       httpRouter := api.NewRouter(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/module/app_profile.go b/src/orchestrator/pkg/module/app_profile.go
new file mode 100644 (file)
index 0000000..77835fb
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ * 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 (
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+
+       pkgerrors "github.com/pkg/errors"
+)
+
+// AppProfile contains the parameters needed for AppProfiles
+// It implements the interface for managing the AppProfiles
+type AppProfile struct {
+       Metadata AppProfileMetadata `json:"metadata"`
+       Spec     AppProfileSpec     `json:"spec"`
+}
+
+type AppProfileContent struct {
+       Profile string `json:"profile"`
+}
+
+// AppProfileMetadata contains the metadata for AppProfiles
+type AppProfileMetadata struct {
+       Name        string `json:"name"`
+       Description string `json:"description"`
+       UserData1   string `json:"userData1"`
+       UserData2   string `json:"userData2"`
+}
+
+// AppProfileSpec contains the Spec for AppProfiles
+type AppProfileSpec struct {
+       AppName string `json:"app-name"`
+}
+
+// AppProfileKey is the key structure that is used in the database
+type AppProfileKey struct {
+       Project             string `json:"project"`
+       CompositeApp        string `json:"compositeapp"`
+       CompositeAppVersion string `json:"compositeappversion"`
+       CompositeProfile    string `json:"compositeprofile"`
+       Profile             string `json:"profile"`
+}
+
+type AppProfileQueryKey struct {
+       AppName string `json:"app-name"`
+}
+
+type AppProfileFindByAppKey struct {
+       Project             string `json:"project"`
+       CompositeApp        string `json:"compositeapp"`
+       CompositeAppVersion string `json:"compositeappversion"`
+       CompositeProfile    string `json:"compositeprofile"`
+       AppName             string `json:"app-name"`
+}
+
+// AppProfileManager exposes the AppProfile functionality
+type AppProfileManager interface {
+       CreateAppProfile(provider, compositeApp, compositeAppVersion, compositeProfile string, ap AppProfile, ac AppProfileContent) (AppProfile, error)
+       GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfile, error)
+       GetAppProfiles(project, compositeApp, compositeAppVersion, compositeProfile string) ([]AppProfile, error)
+       GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfile, error)
+       GetAppProfileContent(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfileContent, error)
+       GetAppProfileContentByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfileContent, error)
+       DeleteAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) error
+}
+
+// AppProfileClient implements the Manager
+// It will also be used to maintain some localized state
+type AppProfileClient struct {
+       storeName  string
+       tagMeta    string
+       tagContent string
+}
+
+// NewAppProfileClient returns an instance of the AppProfileClient
+// which implements the Manager
+func NewAppProfileClient() *AppProfileClient {
+       return &AppProfileClient{
+               storeName:  "orchestrator",
+               tagMeta:    "profilemetadata",
+               tagContent: "profilecontent",
+       }
+}
+
+// CreateAppProfile creates an entry for AppProfile in the database.
+func (c *AppProfileClient) CreateAppProfile(project, compositeApp, compositeAppVersion, compositeProfile string, ap AppProfile, ac AppProfileContent) (AppProfile, error) {
+       key := AppProfileKey{
+               Project:             project,
+               CompositeApp:        compositeApp,
+               CompositeAppVersion: compositeAppVersion,
+               CompositeProfile:    compositeProfile,
+               Profile:             ap.Metadata.Name,
+       }
+       qkey := AppProfileQueryKey{
+               AppName: ap.Spec.AppName,
+       }
+
+       res, err := c.GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, ap.Metadata.Name)
+       if res != (AppProfile{}) {
+               return AppProfile{}, pkgerrors.New("AppProfile already exists")
+       }
+
+       res, err = c.GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, ap.Spec.AppName)
+       if res != (AppProfile{}) {
+               return AppProfile{}, pkgerrors.New("App already has an AppProfile")
+       }
+
+       //Check if composite profile exists (success assumes existance of all higher level 'parent' objects)
+       _, err = NewCompositeProfileClient().GetCompositeProfile(compositeProfile, project, compositeApp, compositeAppVersion)
+       if err != nil {
+               return AppProfile{}, pkgerrors.New("Unable to find the project")
+       }
+
+       // TODO: (after app api is ready) check that the app Spec.AppName exists as part of the composite app
+
+       err = db.DBconn.Insert(c.storeName, key, qkey, c.tagMeta, ap)
+       if err != nil {
+               return AppProfile{}, pkgerrors.Wrap(err, "Creating DB Entry")
+       }
+       err = db.DBconn.Insert(c.storeName, key, qkey, c.tagContent, ac)
+       if err != nil {
+               return AppProfile{}, pkgerrors.Wrap(err, "Creating DB Entry")
+       }
+
+       return ap, nil
+}
+
+// GetAppProfile - return specified App Profile
+func (c *AppProfileClient) GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfile, error) {
+       key := AppProfileKey{
+               Project:             project,
+               CompositeApp:        compositeApp,
+               CompositeAppVersion: compositeAppVersion,
+               CompositeProfile:    compositeProfile,
+               Profile:             profile,
+       }
+
+       value, err := db.DBconn.Find(c.storeName, key, c.tagMeta)
+       if err != nil {
+               return AppProfile{}, pkgerrors.Wrap(err, "Get App Profile error")
+       }
+
+       if value != nil {
+               ap := AppProfile{}
+               err = db.DBconn.Unmarshal(value[0], &ap)
+               if err != nil {
+                       return AppProfile{}, pkgerrors.Wrap(err, "Unmarshalling AppProfile")
+               }
+               return ap, nil
+       }
+
+       return AppProfile{}, pkgerrors.New("Error getting AppProfile")
+
+}
+
+// GetAppProfile - return all App Profiles for given composite profile
+func (c *AppProfileClient) GetAppProfiles(project, compositeApp, compositeAppVersion, compositeProfile string) ([]AppProfile, error) {
+
+       key := AppProfileKey{
+               Project:             project,
+               CompositeApp:        compositeApp,
+               CompositeAppVersion: compositeAppVersion,
+               CompositeProfile:    compositeProfile,
+               Profile:             "",
+       }
+
+       var resp []AppProfile
+       values, err := db.DBconn.Find(c.storeName, key, c.tagMeta)
+       if err != nil {
+               return []AppProfile{}, pkgerrors.Wrap(err, "Get AppProfiles")
+       }
+
+       for _, value := range values {
+               ap := AppProfile{}
+               err = db.DBconn.Unmarshal(value, &ap)
+               if err != nil {
+                       return []AppProfile{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+               }
+               resp = append(resp, ap)
+       }
+
+       return resp, nil
+}
+
+// GetAppProfileByApp - return all App Profiles for given composite profile
+func (c *AppProfileClient) GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfile, error) {
+
+       key := AppProfileFindByAppKey{
+               Project:             project,
+               CompositeApp:        compositeApp,
+               CompositeAppVersion: compositeAppVersion,
+               CompositeProfile:    compositeProfile,
+               AppName:             appName,
+       }
+
+       value, err := db.DBconn.Find(c.storeName, key, c.tagMeta)
+       if err != nil {
+               return AppProfile{}, pkgerrors.Wrap(err, "Get AppProfile by App")
+       }
+
+       if value != nil {
+               ap := AppProfile{}
+               err = db.DBconn.Unmarshal(value[0], &ap)
+               if err != nil {
+                       return AppProfile{}, pkgerrors.Wrap(err, "Unmarshalling AppProfile")
+               }
+               return ap, nil
+       }
+
+       return AppProfile{}, pkgerrors.New("Error getting AppProfile by App")
+}
+
+func (c *AppProfileClient) GetAppProfileContent(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfileContent, error) {
+       key := AppProfileKey{
+               Project:             project,
+               CompositeApp:        compositeApp,
+               CompositeAppVersion: compositeAppVersion,
+               CompositeProfile:    compositeProfile,
+               Profile:             profile,
+       }
+
+       value, err := db.DBconn.Find(c.storeName, key, c.tagContent)
+       if err != nil {
+               return AppProfileContent{}, pkgerrors.Wrap(err, "Get Cluster Content")
+       }
+
+       //value is a byte array
+       if value != nil {
+               ac := AppProfileContent{}
+               err = db.DBconn.Unmarshal(value[0], &ac)
+               if err != nil {
+                       return AppProfileContent{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+               }
+               return ac, nil
+       }
+
+       return AppProfileContent{}, pkgerrors.New("Error getting App Profile Content")
+}
+
+func (c *AppProfileClient) GetAppProfileContentByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfileContent, error) {
+       key := AppProfileFindByAppKey{
+               Project:             project,
+               CompositeApp:        compositeApp,
+               CompositeAppVersion: compositeAppVersion,
+               CompositeProfile:    compositeProfile,
+               AppName:             appName,
+       }
+
+       value, err := db.DBconn.Find(c.storeName, key, c.tagContent)
+       if err != nil {
+               return AppProfileContent{}, pkgerrors.Wrap(err, "Get Cluster Content")
+       }
+
+       //value is a byte array
+       if value != nil {
+               ac := AppProfileContent{}
+               err = db.DBconn.Unmarshal(value[0], &ac)
+               if err != nil {
+                       return AppProfileContent{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+               }
+               return ac, nil
+       }
+
+       return AppProfileContent{}, pkgerrors.New("Error getting App Profile Content")
+}
+
+// Delete AppProfile from the database
+func (c *AppProfileClient) DeleteAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) error {
+       key := AppProfileKey{
+               Project:             project,
+               CompositeApp:        compositeApp,
+               CompositeAppVersion: compositeAppVersion,
+               CompositeProfile:    compositeProfile,
+               Profile:             profile,
+       }
+
+       err := db.DBconn.Remove(c.storeName, key)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Delete AppProfile entry;")
+       }
+       return nil
+}
index fb46b89..8f2948d 100644 (file)
@@ -27,6 +27,7 @@ type Client struct {
        DeploymentIntentGroup  *DeploymentIntentGroupClient
        Intent                 *IntentClient
        CompositeProfile       *CompositeProfileClient
+       AppProfile             *AppProfileClient
        // Add Clients for API's here
 }
 
@@ -42,6 +43,7 @@ func NewClient() *Client {
        c.DeploymentIntentGroup = NewDeploymentIntentGroupClient()
        c.Intent = NewIntentClient()
        c.CompositeProfile = NewCompositeProfileClient()
+       c.AppProfile = NewAppProfileClient()
        // Add Client API handlers here
        return c
 }
diff --git a/src/orchestrator/utils/utils.go b/src/orchestrator/utils/utils.go
new file mode 100644 (file)
index 0000000..44cf512
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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 utils
+
+import (
+       "archive/tar"
+       "compress/gzip"
+       "io"
+
+       pkgerrors "github.com/pkg/errors"
+)
+
+func IsTarGz(r io.Reader) error {
+       //Check if it is a valid gz
+       gzf, err := gzip.NewReader(r)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Invalid gzip format")
+       }
+
+       //Check if it is a valid tar file
+       //Unfortunately this can only be done by inspecting all the tar contents
+       tarR := tar.NewReader(gzf)
+       first := true
+
+       for true {
+               header, err := tarR.Next()
+
+               if err == io.EOF {
+                       //Check if we have just a gzip file without a tar archive inside
+                       if first {
+                               return pkgerrors.New("Empty or non-existant Tar file found")
+                       }
+                       //End of archive
+                       break
+               }
+
+               if err != nil {
+                       return pkgerrors.Errorf("Error reading tar file %s", err.Error())
+               }
+
+               //Check if files are of type directory and regular file
+               if header.Typeflag != tar.TypeDir &&
+                       header.Typeflag != tar.TypeReg {
+                       return pkgerrors.Errorf("Unknown header in tar %s, %s",
+                               header.Name, string(header.Typeflag))
+               }
+
+               first = false
+       }
+
+       return nil
+}
diff --git a/src/orchestrator/utils/utils_test.go b/src/orchestrator/utils/utils_test.go
new file mode 100644 (file)
index 0000000..63230e4
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 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 utils
+
+import (
+       "bytes"
+       "testing"
+)
+
+func TestIsTarGz(t *testing.T) {
+
+       t.Run("Valid tar.gz", func(t *testing.T) {
+               content := []byte{
+                       0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b,
+                       0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74,
+                       0x61, 0x72, 0x00, 0xed, 0xce, 0x41, 0x0a, 0xc2,
+                       0x30, 0x10, 0x85, 0xe1, 0xac, 0x3d, 0x45, 0x4e,
+                       0x50, 0x12, 0xd2, 0xc4, 0xe3, 0x48, 0xa0, 0x01,
+                       0x4b, 0x52, 0x0b, 0xed, 0x88, 0x1e, 0xdf, 0x48,
+                       0x11, 0x5c, 0x08, 0xa5, 0x8b, 0x52, 0x84, 0xff,
+                       0xdb, 0xbc, 0x61, 0x66, 0x16, 0x4f, 0xd2, 0x2c,
+                       0x8d, 0x3c, 0x45, 0xed, 0xc8, 0x54, 0x21, 0xb4,
+                       0xef, 0xb4, 0x67, 0x6f, 0xbe, 0x73, 0x61, 0x9d,
+                       0xb2, 0xce, 0xd5, 0x55, 0xf0, 0xde, 0xd7, 0x3f,
+                       0xdb, 0xd6, 0x49, 0x69, 0xb3, 0x67, 0xa9, 0x8f,
+                       0xfb, 0x2c, 0x71, 0xd2, 0x5a, 0xc5, 0xee, 0x92,
+                       0x73, 0x8e, 0x43, 0x7f, 0x4b, 0x3f, 0xff, 0xd6,
+                       0xee, 0x7f, 0xea, 0x9a, 0x4a, 0x19, 0x1f, 0xe3,
+                       0x54, 0xba, 0xd3, 0xd1, 0x55, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0x00, 0x00, 0x1b, 0xbc, 0x00, 0xb5, 0xe8,
+                       0x4a, 0xf9, 0x00, 0x28, 0x00, 0x00,
+               }
+
+               err := IsTarGz(bytes.NewBuffer(content))
+               if err != nil {
+                       t.Errorf("Error reading valid tar.gz file %s", err.Error())
+               }
+       })
+
+       t.Run("Invalid tar.gz", func(t *testing.T) {
+               content := []byte{
+                       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+                       0x00, 0xff, 0xf2, 0x48, 0xcd,
+               }
+
+               err := IsTarGz(bytes.NewBuffer(content))
+               if err == nil {
+                       t.Errorf("Error should NOT be nil")
+               }
+       })
+
+       t.Run("Empty tar.gz", func(t *testing.T) {
+               content := []byte{}
+               err := IsTarGz(bytes.NewBuffer(content))
+               if err == nil {
+                       t.Errorf("Error should NOT be nil")
+               }
+       })
+}