Refactor Helm management
[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         "github.com/pkg/errors"
30         log "github.com/sirupsen/logrus"
31         "gopkg.in/yaml.v2"
32         "helm.sh/helm/v3/pkg/action"
33         "helm.sh/helm/v3/pkg/chart/loader"
34         "helm.sh/helm/v3/pkg/cli"
35         "helm.sh/helm/v3/pkg/getter"
36         "helm.sh/helm/v3/pkg/kube"
37         "helm.sh/helm/v3/pkg/repo"
38         "k8s.io/cli-runtime/pkg/genericclioptions"
39         "k8s.io/client-go/rest"
40 )
41
42 //go:generate mockery --name HelmManager
43 type HelmManager interface {
44         SetUpRepo(repoName, url string) error
45         InstallHelmChart(namespace, repoName, chartName, releaseName string) error
46         UninstallHelmChart(namespace, chartName string)
47 }
48
49 type helmManagerImpl struct {
50         settings *cli.EnvSettings
51         repo     *repo.ChartRepository
52         setUp    bool
53 }
54
55 func NewHelmManager(s *cli.EnvSettings) *helmManagerImpl {
56         return &helmManagerImpl{
57                 settings: s,
58         }
59 }
60
61 func (hm *helmManagerImpl) SetUpRepo(repoName, url string) error {
62         if len(strings.TrimSpace(url)) == 0 {
63                 log.Info("No ChartMuseum repo set up.")
64                 return nil
65         }
66         log.Debugf("Adding %s to Helm Repo\n", url)
67         repoFile := filepath.Join(filepath.Dir(hm.settings.RepositoryConfig), "index.yaml")
68         log.Debug("Repo file: ", repoFile)
69
70         c := repo.Entry{
71                 Name: filepath.Dir(repoFile),
72                 URL:  url,
73         }
74
75         var err error
76         r := hm.repo
77         if r == nil {
78                 r, err = repo.NewChartRepository(&c, getter.All(hm.settings))
79                 if err != nil {
80                         return err
81                 }
82         }
83
84         //Ensure the file directory exists as it is required for file locking
85         err = os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
86         if err != nil && !errors.Is(err, os.ErrNotExist) {
87                 log.Error("Unable to create folder for Helm.")
88                 return err
89         }
90
91         b, err := os.ReadFile(repoFile)
92         if err != nil {
93                 log.Info("Creating repo file: ", repoFile)
94                 err = r.Index()
95                 if err != nil {
96                         log.Error("Unable to create repo file: ", repoFile)
97                         return err
98                 }
99                 b, err = os.ReadFile(repoFile)
100                 if err != nil {
101                         log.Error("Unable to read repo file: ", repoFile)
102                         return err
103                 }
104         }
105
106         var f repo.File
107         if err := yaml.Unmarshal(b, &f); err != nil {
108                 return err
109         }
110
111         if f.Has(repoName) {
112                 log.Debugf("repository name (%s) already exists\n", repoName)
113                 return nil
114         }
115
116         if _, err := r.DownloadIndexFile(); err != nil {
117                 err = errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
118                 return err
119         }
120
121         f.Update(&c)
122
123         if err := f.WriteFile(repoFile, 0644); err != nil {
124                 return err
125         }
126         hm.setUp = true
127         log.Debugf("%q has been added to your repositories\n", repoName)
128         return nil
129 }
130
131 func (hm *helmManagerImpl) InstallHelmChart(namespace, repoName, chartName, releaseName string) error {
132         if !hm.setUp {
133                 log.Warnf("Helm repo not added, so chart %s not installed", chartName)
134                 return nil
135         }
136         actionConfig, err := getActionConfig(namespace)
137         if err != nil {
138                 return err
139         }
140
141         install := action.NewInstall(actionConfig)
142
143         cp, err := install.ChartPathOptions.LocateChart(fmt.Sprintf("%s/%s", repoName, chartName), hm.settings)
144         if err != nil {
145                 log.Error("Unable to locate chart!")
146                 return err
147         }
148
149         chartRequested, err := loader.Load(cp)
150         if err != nil {
151                 log.Error("Unable to load chart path!")
152                 return err
153         }
154
155         install.Namespace = namespace
156         install.ReleaseName = releaseName
157         _, err = install.Run(chartRequested, nil)
158         if err != nil {
159                 log.Error("Unable to run chart!")
160                 return err
161         }
162         log.Debug("Successfully onboarded ", namespace, repoName, chartName, releaseName)
163         return nil
164 }
165
166 func (hm *helmManagerImpl) UninstallHelmChart(namespace, chartName string) {
167         actionConfig, err := getActionConfig(namespace)
168         if err != nil {
169                 log.Error("unable to get action config: ", err)
170                 return
171         }
172
173         iCli := action.NewUninstall(actionConfig)
174
175         resp, err := iCli.Run(chartName)
176         if err != nil {
177                 log.Error("Unable to uninstall chart ", chartName, err)
178                 return
179         }
180         log.Debug("Successfully uninstalled chart: ", resp.Release.Name)
181 }
182
183 func getActionConfig(namespace string) (*action.Configuration, error) {
184         actionConfig := new(action.Configuration)
185         // Create the rest config instance with ServiceAccount values loaded in them
186         config, err := rest.InClusterConfig()
187         if err != nil {
188                 // fallback to kubeconfig
189                 home, exists := os.LookupEnv("HOME")
190                 if !exists {
191                         home = "/root"
192                 }
193                 kubeconfigPath := filepath.Join(home, ".kube", "config")
194                 if envvar := os.Getenv("KUBECONFIG"); len(envvar) > 0 {
195                         kubeconfigPath = envvar
196                 }
197                 if err := actionConfig.Init(kube.GetConfig(kubeconfigPath, "", namespace), namespace, os.Getenv("HELM_DRIVER"), log.Debugf); err != nil {
198                         log.Error(err)
199                 }
200         } else {
201                 // Create the ConfigFlags struct instance with initialized values from ServiceAccount
202                 kubeConfig := genericclioptions.NewConfigFlags(false)
203                 kubeConfig.APIServer = &config.Host
204                 kubeConfig.BearerToken = &config.BearerToken
205                 kubeConfig.CAFile = &config.CAFile
206                 kubeConfig.Namespace = &namespace
207                 if err := actionConfig.Init(kubeConfig, namespace, os.Getenv("HELM_DRIVER"), log.Debugf); err != nil {
208                         log.Error(err)
209                 }
210         }
211         return actionConfig, err
212 }