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