Refactor Helm management

Issue-ID: NONRTRIC-814
Signed-off-by: elinuxhenrik <henrik.b.andersson@est.tech>
Change-Id: I2a78f7c878f4b757a7c78bb102871c733926d0b0
diff --git a/capifcore/configs/capif.yaml b/capifcore/configs/capif.yaml
new file mode 100644
index 0000000..f85ff6b
--- /dev/null
+++ b/capifcore/configs/capif.yaml
@@ -0,0 +1,67 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: helm-app
+  namespace: default
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: helm-app
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: cluster-admin
+subjects:
+  - kind: ServiceAccount
+    name: helm-app
+    namespace: default
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: capif-deployment
+  namespace: default
+  labels:
+    app: capif
+spec:
+  selector:
+    matchLabels:
+      app: capif
+  template:
+    metadata:
+      labels:
+        app: capif
+        version: v1
+    spec:
+      containers:
+      - name: capif
+        image: o-ran-sc.org/nonrtric/plt/capifcore
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 8090
+        resources:
+          limits:
+            memory: 256Mi
+            cpu: "250m"
+          requests:
+            memory: 128Mi
+            cpu: "80m"
+        args: ["-chartMuseumUrl", "http://chartmuseum:8080"]
+      serviceAccountName: helm-app
+  replicas: 1
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: capif
+  namespace: default
+spec:
+  selector:
+    app: capif
+  ports:
+    - protocol: TCP
+      port: 80
+      targetPort: 8090
+      nodePort: 31570
+  type: NodePort
diff --git a/capifcore/configs/chartmuseum.yaml b/capifcore/configs/chartmuseum.yaml
new file mode 100644
index 0000000..8c169c8
--- /dev/null
+++ b/capifcore/configs/chartmuseum.yaml
@@ -0,0 +1,93 @@
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: chartmuseum-storage-pv-volume
+  namespace: default
+  labels:
+    type: local
+    app: chartmuseum
+spec:
+  storageClassName: manual
+  capacity:
+    storage: 2Gi
+  accessModes:
+    - ReadWriteOnce
+  hostPath:
+    path: "/var/chartmuseum/charts"
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: chartmuseum-storage-pv-claim
+  namespace: default
+  labels:
+    app: chartmuseum
+spec:
+  storageClassName: manual
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 2Gi
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: chartmuseum-deployment
+  namespace: default
+  labels:
+    app: chartmuseum
+spec:
+  selector:
+    matchLabels:
+      app: chartmuseum
+  template:
+    metadata:
+      labels:
+        app: chartmuseum
+        version: v1
+    spec:
+      securityContext:
+        runAsUser: 0
+      containers:
+      - name: chartmuseum
+        image: chartmuseum/chartmuseum:latest
+        imagePullPolicy: IfNotPresent
+        env:
+        - name: STORAGE
+          value: local
+        - name: STORAGE_LOCAL_ROOTDIR
+          value: /charts
+        ports:
+        - name: http
+          containerPort: 8080
+        resources:
+          limits:
+            memory: 256Mi
+            cpu: "250m"
+          requests:
+            memory: 128Mi
+            cpu: "80m"
+        volumeMounts:
+        - name: chartmuseum-persistent-storage
+          mountPath: /charts
+      volumes:
+      - name: chartmuseum-persistent-storage
+        persistentVolumeClaim:
+          claimName: chartmuseum-storage-pv-claim
+  replicas: 1
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: chartmuseum
+  namespace: default
+spec:
+  selector:
+    app: chartmuseum
+  ports:
+  - name: http
+    port: 8080
+    targetPort: 8080
+    nodePort: 31580
+  type: LoadBalancer
diff --git a/capifcore/internal/helmmanagement/helm.go b/capifcore/internal/helmmanagement/helm.go
index 99c27cc..9473bda 100644
--- a/capifcore/internal/helmmanagement/helm.go
+++ b/capifcore/internal/helmmanagement/helm.go
@@ -22,9 +22,9 @@
 
 import (
 	"fmt"
-	"io/fs"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
@@ -49,6 +49,7 @@
 type helmManagerImpl struct {
 	settings *cli.EnvSettings
 	repo     *repo.ChartRepository
+	setUp    bool
 }
 
 func NewHelmManager(s *cli.EnvSettings) *helmManagerImpl {
@@ -58,17 +59,48 @@
 }
 
 func (hm *helmManagerImpl) SetUpRepo(repoName, url string) error {
-	repoFile := hm.settings.RepositoryConfig
+	if len(strings.TrimSpace(url)) == 0 {
+		log.Info("No ChartMuseum repo set up.")
+		return nil
+	}
+	log.Debugf("Adding %s to Helm Repo\n", url)
+	repoFile := filepath.Join(filepath.Dir(hm.settings.RepositoryConfig), "index.yaml")
+	log.Debug("Repo file: ", repoFile)
+
+	c := repo.Entry{
+		Name: filepath.Dir(repoFile),
+		URL:  url,
+	}
+
+	var err error
+	r := hm.repo
+	if r == nil {
+		r, err = repo.NewChartRepository(&c, getter.All(hm.settings))
+		if err != nil {
+			return err
+		}
+	}
 
 	//Ensure the file directory exists as it is required for file locking
-	err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
-	if err != nil && !errors.Is(err, fs.ErrNotExist) {
+	err = os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
+	if err != nil && !errors.Is(err, os.ErrNotExist) {
+		log.Error("Unable to create folder for Helm.")
 		return err
 	}
 
 	b, err := os.ReadFile(repoFile)
 	if err != nil {
-		return err
+		log.Info("Creating repo file: ", repoFile)
+		err = r.Index()
+		if err != nil {
+			log.Error("Unable to create repo file: ", repoFile)
+			return err
+		}
+		b, err = os.ReadFile(repoFile)
+		if err != nil {
+			log.Error("Unable to read repo file: ", repoFile)
+			return err
+		}
 	}
 
 	var f repo.File
@@ -81,21 +113,8 @@
 		return nil
 	}
 
-	c := repo.Entry{
-		Name: repoName,
-		URL:  url,
-	}
-
-	r := hm.repo
-	if r == nil {
-		r, err = repo.NewChartRepository(&c, getter.All(hm.settings))
-		if err != nil {
-			return err
-		}
-	}
-
 	if _, err := r.DownloadIndexFile(); err != nil {
-		err := errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
+		err = errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
 		return err
 	}
 
@@ -104,11 +123,16 @@
 	if err := f.WriteFile(repoFile, 0644); err != nil {
 		return err
 	}
+	hm.setUp = true
 	log.Debugf("%q has been added to your repositories\n", repoName)
 	return nil
 }
 
 func (hm *helmManagerImpl) InstallHelmChart(namespace, repoName, chartName, releaseName string) error {
+	if !hm.setUp {
+		log.Warnf("Helm repo not added, so chart %s not installed", chartName)
+		return nil
+	}
 	actionConfig, err := getActionConfig(namespace)
 	if err != nil {
 		return err
diff --git a/capifcore/internal/helmmanagement/helm_test.go b/capifcore/internal/helmmanagement/helm_test.go
index 6f89c53..5eb83f3 100644
--- a/capifcore/internal/helmmanagement/helm_test.go
+++ b/capifcore/internal/helmmanagement/helm_test.go
@@ -38,19 +38,40 @@
 	"oransc.org/nonrtric/capifcore/internal/helmmanagement/mocks"
 )
 
-func TestSetUpRepo_repoShouldBeAddedToReposFile(t *testing.T) {
+func TestNoChartURL_reoNotSetUp(t *testing.T) {
+	managerUnderTest := NewHelmManager(nil)
+
+	res := managerUnderTest.SetUpRepo("repoName", "")
+
+	assert.Nil(t, res)
+	assert.False(t, managerUnderTest.setUp)
+}
+
+// func TestSetUpRepo_repoShouldBeAddedToReposFile(t *testing.T) {
+// 	settings := createReposFile(t)
+
+// 	managerUnderTest := NewHelmManager(settings)
+
+// 	repoName := "repoName"
+// 	repoURL := "http://url"
+// 	managerUnderTest.repo = getChartRepo(settings)
+
+// 	res := managerUnderTest.SetUpRepo(repoName, repoURL)
+
+// 	assert.Nil(t, res)
+// 	assert.True(t, containsRepo(settings.RepositoryConfig, repoName))
+// 	assert.True(t, managerUnderTest.setUp)
+// }
+
+func TestSetUpRepoFail_shouldNotBeSetUp(t *testing.T) {
 	settings := createReposFile(t)
 
 	managerUnderTest := NewHelmManager(settings)
 
-	repoName := "repoName"
-	repoURL := "http://url"
-	managerUnderTest.repo = getChartRepo(settings)
+	res := managerUnderTest.SetUpRepo("repoName", "repoURL")
 
-	res := managerUnderTest.SetUpRepo(repoName, repoURL)
-
-	assert.Nil(t, res)
-	assert.True(t, containsRepo(settings.RepositoryConfig, repoName))
+	assert.NotNil(t, res)
+	assert.False(t, managerUnderTest.setUp)
 }
 
 func createReposFile(t *testing.T) *cli.EnvSettings {
diff --git a/capifcore/internal/publishservice/publishservice.go b/capifcore/internal/publishservice/publishservice.go
index e073e38..8b2c427 100644
--- a/capifcore/internal/publishservice/publishservice.go
+++ b/capifcore/internal/publishservice/publishservice.go
@@ -142,14 +142,12 @@
 
 	newId := "api_id_" + newServiceAPIDescription.ApiName
 	newServiceAPIDescription.ApiId = &newId
-	info := strings.Split(*newServiceAPIDescription.Description, ",")
-	if len(info) == 5 {
-		err = ps.helmManager.InstallHelmChart(info[1], info[2], info[3], info[4])
-		if err != nil {
-			return sendCoreError(ctx, http.StatusBadRequest, "Unable to install Helm chart due to: "+err.Error())
-		}
-		log.Info("Installed service: ", newId)
+
+	shouldReturn, returnValue := ps.installHelmChart(newServiceAPIDescription, err, ctx, newId)
+	if shouldReturn {
+		return returnValue
 	}
+
 	_, ok := ps.publishedServices[apfId]
 	if ok {
 		ps.publishedServices[apfId] = append(ps.publishedServices[apfId], &newServiceAPIDescription)
@@ -168,6 +166,18 @@
 	return nil
 }
 
+func (ps *PublishService) installHelmChart(newServiceAPIDescription publishserviceapi.ServiceAPIDescription, err error, ctx echo.Context, newId string) (bool, error) {
+	info := strings.Split(*newServiceAPIDescription.Description, ",")
+	if len(info) == 5 {
+		err = ps.helmManager.InstallHelmChart(info[1], info[2], info[3], info[4])
+		if err != nil {
+			return true, sendCoreError(ctx, http.StatusBadRequest, "Unable to install Helm chart due to: "+err.Error())
+		}
+		log.Info("Installed service: ", newId)
+	}
+	return false, nil
+}
+
 func (ps *PublishService) DeleteApfIdServiceApisServiceApiId(ctx echo.Context, apfId string, serviceApiId string) error {
 	serviceDescriptions, ok := ps.publishedServices[string(apfId)]
 	if ok {
diff --git a/capifcore/main.go b/capifcore/main.go
index 7a3f96f..37137d7 100644
--- a/capifcore/main.go
+++ b/capifcore/main.go
@@ -50,7 +50,7 @@
 
 func main() {
 	var port = flag.Int("port", 8090, "Port for CAPIF Core Function HTTP server")
-	flag.StringVar(&url, "url", "http://localhost:8080", "ChartMuseum url")
+	flag.StringVar(&url, "chartMuseumUrl", "", "ChartMuseum URL")
 	flag.StringVar(&repoName, "repoName", "capifcore", "Repository name")
 	var logLevelStr = flag.String("loglevel", "Info", "Log level")
 	flag.Parse()
@@ -60,11 +60,10 @@
 	}
 
 	// Add repo
-	fmt.Printf("Adding %s to Helm Repo\n", url)
 	helmManager = helmmanagement.NewHelmManager(cli.New())
 	err := helmManager.SetUpRepo(repoName, url)
 	if err != nil {
-		log.Fatal(err.Error())
+		log.Warnf("No Helm repo added due to: %s", err.Error())
 	}
 
 	go startWebServer(getEcho(), *port)
diff --git a/capifcore/start_pods.sh b/capifcore/start_pods.sh
new file mode 100755
index 0000000..47e886a
--- /dev/null
+++ b/capifcore/start_pods.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+kubectl create -f configs/chartmuseum.yaml
+kubectl wait deployment -n default chartmuseum-deployment --for=condition=available --timeout=90s
+kubectl create -f configs/capif.yaml
diff --git a/capifcore/stop_pods.sh b/capifcore/stop_pods.sh
new file mode 100755
index 0000000..10167c0
--- /dev/null
+++ b/capifcore/stop_pods.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+kubectl delete -f configs/capif.yaml
+kubectl delete -f configs/chartmuseum.yaml