Roll versions after J-Relase (master branch)
[nonrtric/plt/sme.git] / capifcore / internal / helmmanagement / helm.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2022: Nordix Foundation
6 //   %%
7 //   Licensed under the Apache License, Version 2.0 (the "License");
8 //   you may not use this file except in compliance with the License.
9 //   You may obtain a copy of the License at
10 //
11 //        http://www.apache.org/licenses/LICENSE-2.0
12 //
13 //   Unless required by applicable law or agreed to in writing, software
14 //   distributed under the License is distributed on an "AS IS" BASIS,
15 //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 //   See the License for the specific language governing permissions and
17 //   limitations under the License.
18 //   ========================LICENSE_END===================================
19 //
20
21 package helmmanagement
22
23 import (
24         "fmt"
25         "os"
26         "path/filepath"
27         "strings"
28
29         log "github.com/sirupsen/logrus"
30         "gopkg.in/yaml.v2"
31         "helm.sh/helm/v3/pkg/action"
32         "helm.sh/helm/v3/pkg/chart/loader"
33         "helm.sh/helm/v3/pkg/cli"
34         "helm.sh/helm/v3/pkg/getter"
35         "helm.sh/helm/v3/pkg/kube"
36         "helm.sh/helm/v3/pkg/repo"
37         "k8s.io/cli-runtime/pkg/genericclioptions"
38         "k8s.io/client-go/rest"
39 )
40
41 //go:generate mockery --name HelmManager
42 type HelmManager interface {
43         SetUpRepo(repoName, url string) error
44         InstallHelmChart(namespace, repoName, chartName, releaseName string) error
45         UninstallHelmChart(namespace, chartName string)
46 }
47
48 type helmManagerImpl struct {
49         settings *cli.EnvSettings
50         repo     *repo.ChartRepository
51         setUp    bool
52 }
53
54 func NewHelmManager(s *cli.EnvSettings) *helmManagerImpl {
55         return &helmManagerImpl{
56                 settings: s,
57         }
58 }
59
60 func (hm *helmManagerImpl) SetUpRepo(repoName, url string) error {
61         if len(strings.TrimSpace(url)) == 0 {
62                 log.Info("No ChartMuseum repo set up.")
63                 return nil
64         }
65         log.Debugf("Adding %s to Helm Repo\n", url)
66         repoFile := hm.settings.RepositoryConfig
67
68         //Ensure the file directory exists as it is required for file locking
69         err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
70         if err != nil && !os.IsExist(err) {
71                 return err
72         }
73
74         b, err := os.ReadFile(repoFile)
75         if err != nil && !os.IsNotExist(err) {
76                 return err
77         }
78
79         var f repo.File
80         if err := yaml.Unmarshal(b, &f); err != nil {
81                 return err
82         }
83
84         if f.Has(repoName) {
85                 log.Debugf("repository name (%s) already exists\n", repoName)
86                 hm.setUp = true
87                 return nil
88         }
89
90         c := repo.Entry{
91                 Name: repoName,
92                 URL:  url,
93         }
94
95         r := hm.repo
96         if r == nil {
97                 r, err = repo.NewChartRepository(&c, getter.All(hm.settings))
98                 if err != nil {
99                         return err
100                 }
101         }
102
103         if _, err = r.DownloadIndexFile(); err != nil {
104                 log.Errorf("looks like %q is not a valid chart repository or cannot be reached", url)
105                 return err
106         }
107
108         f.Update(&c)
109
110         if err = f.WriteFile(repoFile, 0644); err != nil {
111                 return err
112         }
113         log.Debugf("%q has been added to your repositories\n", repoName)
114         hm.setUp = true
115         return nil
116 }
117
118 func (hm *helmManagerImpl) InstallHelmChart(namespace, repoName, chartName, releaseName string) error {
119         if !hm.setUp {
120                 log.Warnf("Helm repo not added, so chart %s not installed", chartName)
121                 return nil
122         }
123         actionConfig, err := getActionConfig(namespace)
124         if err != nil {
125                 return err
126         }
127
128         install := action.NewInstall(actionConfig)
129
130         cp, err := install.ChartPathOptions.LocateChart(fmt.Sprintf("%s/%s", repoName, chartName), hm.settings)
131         if err != nil {
132                 log.Errorf("Unable to locate chart: %s", chartName)
133                 return err
134         }
135
136         chartRequested, err := loader.Load(cp)
137         if err != nil {
138                 log.Errorf("Unable to load chart path for chart: %s", chartName)
139                 return err
140         }
141
142         install.Namespace = namespace
143         install.ReleaseName = releaseName
144         _, err = install.Run(chartRequested, nil)
145         if err != nil {
146                 log.Errorf("Unable to run chart: %s", chartName)
147                 return err
148         }
149         log.Debug("Successfully onboarded ", namespace, repoName, chartName, releaseName)
150         return nil
151 }
152
153 func (hm *helmManagerImpl) UninstallHelmChart(namespace, chartName string) {
154         actionConfig, err := getActionConfig(namespace)
155         if err != nil {
156                 log.Error("unable to get action config: ", err)
157                 return
158         }
159
160         iCli := action.NewUninstall(actionConfig)
161
162         resp, err := iCli.Run(chartName)
163         if err != nil {
164                 log.Error("Unable to uninstall chart ", chartName, err)
165                 return
166         }
167         log.Debug("Successfully uninstalled chart: ", resp.Release.Name)
168 }
169
170 func getActionConfig(namespace string) (*action.Configuration, error) {
171         actionConfig := new(action.Configuration)
172         // Create the rest config instance with ServiceAccount values loaded in them
173         config, err := rest.InClusterConfig()
174         if err != nil {
175                 // fallback to kubeconfig
176                 home, exists := os.LookupEnv("HOME")
177                 if !exists {
178                         home = "/root"
179                 }
180                 kubeconfigPath := filepath.Join(home, ".kube", "config")
181                 if envvar := os.Getenv("KUBECONFIG"); len(envvar) > 0 {
182                         kubeconfigPath = envvar
183                 }
184                 if err := actionConfig.Init(kube.GetConfig(kubeconfigPath, "", namespace), namespace, os.Getenv("HELM_DRIVER"), log.Debugf); err != nil {
185                         log.Error(err)
186                 }
187         } else {
188                 // Create the ConfigFlags struct instance with initialized values from ServiceAccount
189                 kubeConfig := genericclioptions.NewConfigFlags(false)
190                 kubeConfig.APIServer = &config.Host
191                 kubeConfig.BearerToken = &config.BearerToken
192                 kubeConfig.CAFile = &config.CAFile
193                 kubeConfig.Namespace = &namespace
194                 if err := actionConfig.Init(kubeConfig, namespace, os.Getenv("HELM_DRIVER"), log.Debugf); err != nil {
195                         log.Error(err)
196                 }
197         }
198         return actionConfig, err
199 }