blob: dc92fc79af7577a8edd6db6ab1df3f39a4d61307 [file] [log] [blame]
ktimoney28fa9fb2022-05-30 16:08:27 +01001// -
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
21package main
22
23import (
24 "context"
25 "database/sql"
26 "encoding/json"
27 "flag"
28 "fmt"
29 "github.com/pkg/errors"
30 "gopkg.in/yaml.v2"
31 "helm.sh/helm/v3/pkg/action"
32 "helm.sh/helm/v3/pkg/chart"
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 "io/ioutil"
39 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
40 "k8s.io/cli-runtime/pkg/genericclioptions"
41 kubernetes "k8s.io/client-go/kubernetes"
42 "k8s.io/client-go/rest"
43 "net/http"
44 "os"
45 "path/filepath"
46)
47
48var settings *cli.EnvSettings
49var chartRequested *chart.Chart
50
51//var url string
52var repoName string
53var chartName string
54var releaseName string
55var namespace string
56
57type ChartInfo struct {
58 Name string `json:",omitempty"`
59 Namespace string `json:",omitempty"`
60 Revision int `json:",omitempty"`
61 Updated string `json:",omitempty"`
62 Status string `json:",omitempty"`
63 Chart string `json:",omitempty"`
64 AppVersion string `json:",omitempty"`
65 Values map[string]interface{} `json:"-"`
66}
67
68type Rapp struct {
69 Type string
70 SecurityEnabled bool
71 Realm string
72 Client string
73 Roles []struct {
74 Role string
75 Grants []string
76 }
77 Apps []struct {
78 Prefix string
79 Methods []string
80 }
81}
82
83var rapp Rapp
84
85const (
86 host = "postgres.default"
87 port = 5432
88 user = "capif"
89 password = "capif"
90 dbname = "capif"
91)
92
93func runInstall(res http.ResponseWriter, req *http.Request) {
94 query := req.URL.Query()
95 chartName = query.Get("chart")
96 releaseName = chartName
97 fmt.Println("Installing ", chartName)
98
99 var msg string
100 var chart string
101 var install *action.Install
102 chartMuseumService, chartMuseumPort := findService("chartmuseum", "default")
103 fmt.Printf("Chart Museum service:%s, Port:%d\n", chartMuseumService, chartMuseumPort)
104 url := "http://" + chartMuseumService + ":" + fmt.Sprint(chartMuseumPort)
105 if !chartInstalled(chartName) {
106 // Add repo
107 fmt.Printf("Adding %s to Helm Repo\n", url)
108 _, err := addToRepo(url)
109 if err != nil {
110 msg = err.Error()
111 } else {
112 install, err = dryRun()
113 if err != nil {
114 msg = err.Error()
115 } else {
116 if rapp.SecurityEnabled && rapp.Type == "provider" {
117 // keycloak client setup
118 fmt.Println("Setting up keycloak")
119 _, err = http.Get("http://rapps-keycloak-mgr.default/create?realm=" + rapp.Realm + "&name=" + rapp.Client + "&role=" + rapp.Roles[0].Role)
120 if err != nil {
121 msg = err.Error()
122 } else {
123 fmt.Println("Setting up istio")
124 _, err := http.Get("http://rapps-istio-mgr.default/create?name=" + chartName + "&realm=" + rapp.Realm + "&role=" + rapp.Roles[0].Role + "&method=" + rapp.Roles[0].Grants[0])
125 if err != nil {
126 msg = err.Error()
127 } else {
128 // Install chart
129 fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace)
130 chart, err = installHelmChart(install)
131 if err != nil {
132 msg = "Error occurred during installation " + err.Error()
133 } else {
134 msg = "Successfully installed release: " + chart
135 }
136 }
137 }
138 } else {
139 // Install chart
140 fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace)
141 chart, err = installHelmChart(install)
142 if err != nil {
143 msg = "Error occurred during installation " + err.Error()
144 } else {
145 msg = "Successfully installed release: " + chart
146 }
147 }
148
149 }
150 }
151 registrerRapp(chartName, rapp.Type)
152 } else {
153 msg = chartName + " has already been installed"
154 }
155
156 // create response binary data
157 data := []byte(msg) // slice of bytes
158 // write `data` to response
159 res.Write(data)
160}
161
162func runUninstall(res http.ResponseWriter, req *http.Request) {
163 query := req.URL.Query()
164 chartName = query.Get("chart")
165 releaseName = chartName
166 fmt.Println("Uninstalling ", chartName)
167
168 var msg string
169 var chart string
170 if chartInstalled(chartName) {
171 err := getChartValues(chartName)
172 if err != nil {
173 msg = err.Error()
174 } else {
175 chart, err = uninstallHelmChart(chartName)
176 if err != nil {
177 msg = "Error occurred during uninstall " + err.Error()
178 } else {
179 msg = "Successfully uninstalled release: " + chart
180 }
181 if rapp.SecurityEnabled && rapp.Type == "provider" {
182 // Remove istio objects for rapp
183 fmt.Println("Removing istio services")
184 _, err := http.Get("http://rapps-istio-mgr.default/remove?name=" + chartName)
185 if err != nil {
186 msg = err.Error()
187 }
188 // remove keycloak client
189 fmt.Println("Removing keycloak client")
190 _, err = http.Get("http://rapps-keycloak-mgr.default/remove?realm=" + rapp.Realm + "&name=" + rapp.Client + "&role=" + rapp.Roles[0].Role)
191 if err != nil {
192 msg = err.Error()
193 }
194 }
195 }
196 unregistrerRapp(chartName, rapp.Type)
197 } else {
198 msg = chartName + " is not installed"
199 }
200
201 // create response binary data
202 data := []byte(msg) // slice of bytes
203 // write `data` to response
204 res.Write(data)
205}
206
207func runList(res http.ResponseWriter, req *http.Request) {
208 chartInfo := list()
209 // create response binary data
210 data, err := json.Marshal(chartInfo)
211 if err != nil {
212 fmt.Printf("Error happened in JSON marshal. Err: %s\n", err)
213 }
214 // write `data` to response
215 res.Write(data)
216}
217
218func main() {
219 //flag.StringVar(&url, "url", "http://chartmuseum:8080", "ChartMuseum url")
220 flag.StringVar(&repoName, "repoName", "local-dev", "Repository name")
221 flag.StringVar(&namespace, "namespace", "istio-nonrtric", "namespace for install")
222 flag.Parse()
223 settings = cli.New()
224
225 runInstallHandler := http.HandlerFunc(runInstall)
226 http.Handle("/install", runInstallHandler)
227 runUninstallHandler := http.HandlerFunc(runUninstall)
228 http.Handle("/uninstall", runUninstallHandler)
229 runListHandler := http.HandlerFunc(runList)
230 http.Handle("/list", runListHandler)
231 http.ListenAndServe(":9000", nil)
232}
233
234func addToRepo(url string) (string, error) {
235 repoFile := settings.RepositoryConfig
236
237 //Ensure the file directory exists as it is required for file locking
238 err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
239 if err != nil && !os.IsExist(err) {
240 return "", err
241 }
242
243 b, err := ioutil.ReadFile(repoFile)
244 if err != nil && !os.IsNotExist(err) {
245 return "", err
246 }
247
248 var f repo.File
249 if err := yaml.Unmarshal(b, &f); err != nil {
250 return "", err
251 }
252
253 if f.Has(repoName) {
254 fmt.Printf("repository name (%s) already exists\n", repoName)
255 return "", nil
256 }
257
258 c := repo.Entry{
259 Name: repoName,
260 URL: url,
261 }
262
263 r, err := repo.NewChartRepository(&c, getter.All(settings))
264 if err != nil {
265 return "", err
266 }
267
268 if _, err := r.DownloadIndexFile(); err != nil {
269 err := errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
270 return "", err
271 }
272
273 f.Update(&c)
274
275 if err := f.WriteFile(repoFile, 0644); err != nil {
276 return "", err
277 }
278 fmt.Printf("%q has been added to your repositories\n", repoName)
279 return "", nil
280}
281
282func dryRun() (*action.Install, error) {
283 actionConfig, err := getActionConfig(namespace)
284
285 install := action.NewInstall(actionConfig)
286
287 cp, err := install.ChartPathOptions.LocateChart(fmt.Sprintf("%s/%s", repoName, chartName), settings)
288
289 chartRequested, err = loader.Load(cp)
290
291 install.Namespace = namespace
292 install.ReleaseName = releaseName
293 install.DryRun = true
294 rel, err := install.Run(chartRequested, nil)
295 if err != nil {
296 fmt.Println(err)
297 return install, err
298 }
299
300 rappMap := rel.Chart.Values["rapp"]
301 // Convert map to json string
302 jsonStr, err := json.Marshal(rappMap)
303 if err != nil {
304 fmt.Println(err)
305 return install, err
306 }
307
308 if err := json.Unmarshal(jsonStr, &rapp); err != nil {
309 fmt.Println(err)
310 return install, err
311 }
312 fmt.Printf("Keycloak key/value pairs in values.yaml - Realm: %s Client: %s Client Role: %s\n", rapp.Realm, rapp.Client, rapp.Roles)
313 return install, nil
314}
315
316func installHelmChart(install *action.Install) (string, error) {
317
318 install.DryRun = false
319 rel, err := install.Run(chartRequested, nil)
320 if err != nil {
321 fmt.Println(err)
322 }
323 fmt.Println("Successfully installed release: ", rel.Name)
324
325 return rel.Name, err
326}
327
328func getActionConfig(namespace string) (*action.Configuration, error) {
329 actionConfig := new(action.Configuration)
330 // Create the rest config instance with ServiceAccount values loaded in them
331 config, err := rest.InClusterConfig()
332 if err != nil {
333 // fallback to kubeconfig
334 home, exists := os.LookupEnv("HOME")
335 if !exists {
336 home = "/root"
337 }
338 kubeconfigPath := filepath.Join(home, ".kube", "config")
339 if envvar := os.Getenv("KUBECONFIG"); len(envvar) > 0 {
340 kubeconfigPath = envvar
341 }
342 if err := actionConfig.Init(kube.GetConfig(kubeconfigPath, "", namespace), namespace, os.Getenv("HELM_DRIVER"),
343 func(format string, v ...interface{}) {
344 fmt.Sprintf(format, v)
345 }); err != nil {
346 fmt.Println(err)
347 }
348 } else {
349 // Create the ConfigFlags struct instance with initialized values from ServiceAccount
350 var kubeConfig *genericclioptions.ConfigFlags
351 kubeConfig = genericclioptions.NewConfigFlags(false)
352 kubeConfig.APIServer = &config.Host
353 kubeConfig.BearerToken = &config.BearerToken
354 kubeConfig.CAFile = &config.CAFile
355 kubeConfig.Namespace = &namespace
356 if err := actionConfig.Init(kubeConfig, namespace, os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) {
357 fmt.Sprintf(format, v)
358 }); err != nil {
359 fmt.Println(err)
360 }
361 }
362 return actionConfig, err
363}
364
365func uninstallHelmChart(name string) (string, error) {
366 actionConfig, err := getActionConfig(namespace)
367 if err != nil {
368 fmt.Println(err)
369 }
370
371 iCli := action.NewUninstall(actionConfig)
372
373 resp, err := iCli.Run(name)
374 if err != nil {
375 fmt.Println(err)
376 }
377 fmt.Println("Successfully uninstalled release: ", resp.Release.Name)
378 return resp.Release.Name, err
379}
380
381func connectToK8s() *kubernetes.Clientset {
382 config, err := rest.InClusterConfig()
383 if err != nil {
384 fmt.Println("failed to create K8s config")
385 }
386
387 clientset, err := kubernetes.NewForConfig(config)
388 if err != nil {
389 fmt.Println("Failed to create K8s clientset")
390 }
391
392 return clientset
393}
394
395func findService(serviceName, namespace string) (string, int32) {
396 clientset := connectToK8s()
397 svc, err := clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{})
398 if err != nil {
399 fmt.Println(err.Error())
400 }
401 return svc.Name, svc.Spec.Ports[0].Port
402}
403
404func list() []ChartInfo {
405 var charts = []ChartInfo{}
406 var chart ChartInfo
407 actionConfig, err := getActionConfig(namespace)
408 if err != nil {
409 panic(err)
410 }
411
412 listAction := action.NewList(actionConfig)
413 releases, err := listAction.Run()
414 if err != nil {
415 fmt.Println(err)
416 }
417 for _, release := range releases {
418 //fmt.Println("Release: " + release.Name + " Status: " + release.Info.Status.String())
419 chart.Name = release.Name
420 chart.Namespace = release.Namespace
421 chart.Revision = release.Version
422 chart.Updated = release.Info.LastDeployed.String()
423 chart.Status = release.Info.Status.String()
424 chart.Chart = release.Chart.Metadata.Name + "-" + release.Chart.Metadata.Version
425 chart.AppVersion = release.Chart.Metadata.AppVersion
426 chart.Values = release.Chart.Values
427 charts = append(charts, chart)
428 }
429 return charts
430}
431
432func chartInstalled(chartName string) bool {
433 charts := list()
434 for _, chart := range charts {
435 if chart.Name == chartName {
436 return true
437 }
438 }
439 return false
440}
441
442func getChartValues(chartName string) error {
443 charts := list()
444 for _, chart := range charts {
445 if chart.Name == chartName {
446 rappMap := chart.Values["rapp"]
447 fmt.Println("rappMap:", rappMap)
448 // Convert map to json string
449 jsonStr, err := json.Marshal(rappMap)
450 if err != nil {
451 fmt.Println("Error:", err)
452 return err
453 }
454
455 if err := json.Unmarshal(jsonStr, &rapp); err != nil {
456 fmt.Println("Error:", err)
457 return err
458 }
459 return nil
460 }
461 }
462 return errors.New("Chart: cannot retrieve values")
463}
464
465func registrerRapp(chartName, chartType string) {
466 psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
467
468 db, err := sql.Open("postgres", psqlconn)
469 if err != nil {
470 fmt.Println(err)
471 } else {
472 fmt.Println("Connected!")
473 }
474
475 defer db.Close()
476
477 // create
478 // hardcoded
479 createStmt := `CREATE TABLE IF NOT EXISTS services (
480 id serial PRIMARY KEY,
481 name VARCHAR ( 50 ) UNIQUE NOT NULL,
482 type VARCHAR ( 50 ) NOT NULL,
483 created_on TIMESTAMP DEFAULT NOW()
484 );`
485 _, err = db.Exec(createStmt)
486 if err != nil {
487 fmt.Println(err)
488 } else {
489 fmt.Println("Created table for service registry")
490 }
491
492 // dynamic
493 insertDynStmt := `insert into "services"("name", "type") values($1, $2)`
494 _, err = db.Exec(insertDynStmt, chartName, chartType)
495 if err != nil {
496 fmt.Println(err)
497 } else {
498 fmt.Println("Inserted " + chartName + " into service registry")
499 }
500}
501
502func unregistrerRapp(chartName, chartType string) {
503 psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
504
505 db, err := sql.Open("postgres", psqlconn)
506 if err != nil {
507 fmt.Println(err)
508 } else {
509 fmt.Println("Connected!")
510 }
511
512 defer db.Close()
513
514 // dynamic
515 deleteDynStmt := `delete from services where name=$1 and type=$2`
516 _, err = db.Exec(deleteDynStmt, chartName, chartType)
517 if err != nil {
518 fmt.Println(err)
519 } else {
520 fmt.Println("Deleted " + chartName + " from service registry")
521 }
522}