3 // ========================LICENSE_START=================================
6 // Copyright (C) 2022-2023: Nordix Foundation
8 // Licensed under the Apache License, Version 2.0 (the "License");
9 // you may not use this file except in compliance with the License.
10 // You may obtain a copy of the License at
12 // http://www.apache.org/licenses/LICENSE-2.0
14 // Unless required by applicable law or agreed to in writing, software
15 // distributed under the License is distributed on an "AS IS" BASIS,
16 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 // See the License for the specific language governing permissions and
18 // limitations under the License.
19 // ========================LICENSE_END===================================
29 "github.com/pkg/errors"
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"
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"
49 var settings *cli.EnvSettings
50 var chartRequested *chart.Chart
53 var releaseName string
56 type 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:"-"`
92 host = "postgres.default"
99 func runInstall(res http.ResponseWriter, req *http.Request) {
100 query := req.URL.Query()
101 chartName = query.Get("chart")
102 releaseName = chartName
103 fmt.Println("Installing ", chartName)
107 var install *action.Install
108 chartMuseumService, chartMuseumPort := findService("chartmuseum", "default")
109 fmt.Printf("Chart Museum service:%s, Port:%d\n", chartMuseumService, chartMuseumPort)
110 url := "http://" + chartMuseumService + ":" + fmt.Sprint(chartMuseumPort)
111 if !chartInstalled(chartName) {
113 fmt.Printf("Adding %s to Helm Repo\n", url)
114 _, err := addToRepo(url)
118 install, err = dryRun()
122 err := installSecurity(rapp)
126 fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace)
127 chart, err = installHelmChart(install)
129 msg = "Error occurred during installation " + err.Error()
131 msg = "Successfully installed release: " + chart
137 registrerRapp(chartName, rapp.Type)
139 msg = chartName + " has already been installed"
142 // create response binary data
143 data := []byte(msg) // slice of bytes
144 // write `data` to response
148 func installSecurity(rapp Rapp) error {
151 role := rapp.Roles[0].Role
152 grants := rapp.Roles[0].Grants[0]
154 client := rapp.Client
155 authenticator := rapp.Authenticator
157 tlsCrt := rapp.TlsCrt
158 tlsKey := rapp.TlsKey
160 subjectDN := rapp.SubjectDN
161 mappingSource := rapp.MappingSource
163 httpClient := &http.Client{
164 Timeout: time.Second * 10,
167 if !rapp.SecurityEnabled {
170 // Different security requirements depending on the rapp type
171 if rapp.Type == "provider" {
172 // keycloak client setup
173 fmt.Println("Setting up keycloak")
174 url = "http://rapps-keycloak-mgr.default/create"
175 values := map[string]string{"realm": realm, "name": client, "role": role, "authType": authenticator,
176 "tlsCrt": tlsCrt, "email": email, "subjectDN": subjectDN, "mappingSource": mappingSource}
177 jsonValue, _ := json.Marshal(values)
178 req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue))
180 fmt.Printf("Got error %s", err.Error())
182 req.Header.Set("Content-type", "application/json")
183 resp, err := httpClient.Do(req)
184 fmt.Println("Keycloak response status:", resp.Status)
186 fmt.Printf("Got error %s", err.Error())
189 fmt.Println("Setting up istio")
190 url = "http://rapps-istio-mgr.default/create-policy?"
191 params = "name=" + chartName + "&realm=" + realm + "&role=" + role + "&method=" + grants
194 _, err := http.Get(url)
200 fmt.Println("Setting up istio")
201 url = "http://rapps-istio-mgr.default/create-filter?"
202 params = "name=" + chartName + "&realm=" + realm + "&client=" + client + "&authType=" + authenticator +
203 "&tlsCrt=" + tlsCrt + "&tlsKey=" + tlsKey + "&caCrt=" + caCrt
205 _, err := http.Get(url)
215 func runUninstall(res http.ResponseWriter, req *http.Request) {
216 query := req.URL.Query()
217 chartName = query.Get("chart")
218 releaseName = chartName
219 fmt.Println("Uninstalling ", chartName)
223 if chartInstalled(chartName) {
224 err := getChartValues(chartName)
228 chart, err = uninstallHelmChart(chartName)
230 msg = "Error occurred during uninstall " + err.Error()
232 msg = "Successfully uninstalled release: " + chart
234 err := uninstallSecurity(rapp, chartName)
239 unregistrerRapp(chartName, rapp.Type)
241 msg = chartName + " is not installed"
244 // create response binary data
245 data := []byte(msg) // slice of bytes
246 // write `data` to response
250 func uninstallSecurity(rapp Rapp, chartName string) error {
254 client := rapp.Client
255 authenticator := rapp.Authenticator
257 if !rapp.SecurityEnabled {
260 if rapp.Type == "provider" {
261 // Remove istio objects for rapp
262 fmt.Println("Removing istio services")
263 _, err := http.Get("http://rapps-istio-mgr.default/remove-policy?name=" + chartName)
267 // remove keycloak client
268 fmt.Println("Removing keycloak client")
269 url = "http://rapps-keycloak-mgr.default/remove?"
270 params = "name=" + client + "&realm=" + realm + "&authType=" + authenticator
272 _, err = http.Get(url)
277 if rapp.Type == "invoker" {
278 // Remove istio objects for rapp
279 fmt.Println("Removing istio services")
280 _, err := http.Get("http://rapps-istio-mgr.default/remove-filter?name=" + chartName)
288 func runList(res http.ResponseWriter, req *http.Request) {
290 // create response binary data
291 data, err := json.Marshal(chartInfo)
293 fmt.Printf("Error happened in JSON marshal. Err: %s\n", err)
295 // write `data` to response
300 flag.StringVar(&repoName, "repoName", "local-dev", "Repository name")
301 flag.StringVar(&namespace, "namespace", "istio-nonrtric", "namespace for install")
305 runInstallHandler := http.HandlerFunc(runInstall)
306 http.Handle("/install", runInstallHandler)
307 runUninstallHandler := http.HandlerFunc(runUninstall)
308 http.Handle("/uninstall", runUninstallHandler)
309 runListHandler := http.HandlerFunc(runList)
310 http.Handle("/list", runListHandler)
311 http.ListenAndServe(":9000", nil)
314 func addToRepo(url string) (string, error) {
315 repoFile := settings.RepositoryConfig
316 fmt.Printf("Repo File %s\n", repoFile)
318 //Ensure the file directory exists as it is required for file locking
319 err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
320 if err != nil && !os.IsExist(err) {
324 b, err := ioutil.ReadFile(repoFile)
325 if err != nil && !os.IsNotExist(err) {
330 if err := yaml.Unmarshal(b, &f); err != nil {
335 fmt.Printf("repository name (%s) already exists\n", repoName)
344 r, err := repo.NewChartRepository(&c, getter.All(settings))
349 if _, err := r.DownloadIndexFile(); err != nil {
350 err := errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
356 if err := f.WriteFile(repoFile, 0644); err != nil {
359 fmt.Printf("%q has been added to your repositories\n", repoName)
363 func dryRun() (*action.Install, error) {
364 actionConfig, err := getActionConfig(namespace)
366 install := action.NewInstall(actionConfig)
368 fmt.Printf("Repo Name: %s\n", repoName)
369 fmt.Printf("Chart Name: %s\n", chartName)
370 cp, err := install.ChartPathOptions.LocateChart(fmt.Sprintf("%s/%s", repoName, chartName), settings)
371 fmt.Printf("Chart location: %s\n", cp)
373 chartRequested, err = loader.Load(cp)
375 install.Namespace = namespace
376 install.ReleaseName = releaseName
377 install.DryRun = true
378 rel, err := install.Run(chartRequested, nil)
384 rappMap := rel.Chart.Values["rapp"]
385 // Convert map to json string
386 jsonStr, err := json.Marshal(rappMap)
392 if err := json.Unmarshal(jsonStr, &rapp); err != nil {
396 fmt.Printf("Keycloak key/value pairs in values.yaml - Realm: %s Client: %s Client Role: %s\n", rapp.Realm, rapp.Client, rapp.Roles)
400 func installHelmChart(install *action.Install) (string, error) {
402 install.DryRun = false
403 rel, err := install.Run(chartRequested, nil)
407 fmt.Println("Successfully installed release: ", rel.Name)
412 func getActionConfig(namespace string) (*action.Configuration, error) {
413 actionConfig := new(action.Configuration)
414 // Create the rest config instance with ServiceAccount values loaded in them
415 config, err := rest.InClusterConfig()
417 // fallback to kubeconfig
418 home, exists := os.LookupEnv("HOME")
422 kubeconfigPath := filepath.Join(home, ".kube", "config")
423 if envvar := os.Getenv("KUBECONFIG"); len(envvar) > 0 {
424 kubeconfigPath = envvar
426 if err := actionConfig.Init(kube.GetConfig(kubeconfigPath, "", namespace), namespace, os.Getenv("HELM_DRIVER"),
427 func(format string, v ...interface{}) {
428 fmt.Sprintf(format, v)
433 // Create the ConfigFlags struct instance with initialized values from ServiceAccount
434 var kubeConfig *genericclioptions.ConfigFlags
435 kubeConfig = genericclioptions.NewConfigFlags(false)
436 kubeConfig.APIServer = &config.Host
437 kubeConfig.BearerToken = &config.BearerToken
438 kubeConfig.CAFile = &config.CAFile
439 kubeConfig.Namespace = &namespace
440 if err := actionConfig.Init(kubeConfig, namespace, os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) {
441 fmt.Sprintf(format, v)
446 return actionConfig, err
449 func uninstallHelmChart(name string) (string, error) {
450 actionConfig, err := getActionConfig(namespace)
455 iCli := action.NewUninstall(actionConfig)
457 resp, err := iCli.Run(name)
461 fmt.Println("Successfully uninstalled release: ", resp.Release.Name)
462 return resp.Release.Name, err
465 func connectToK8s() *kubernetes.Clientset {
466 config, err := rest.InClusterConfig()
468 fmt.Println("failed to create K8s config")
471 clientset, err := kubernetes.NewForConfig(config)
473 fmt.Println("Failed to create K8s clientset")
479 func findService(serviceName, namespace string) (string, int32) {
480 clientset := connectToK8s()
481 svc, err := clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{})
483 fmt.Println(err.Error())
485 return svc.Name, svc.Spec.Ports[0].Port
488 func list() []ChartInfo {
489 var charts = []ChartInfo{}
491 actionConfig, err := getActionConfig(namespace)
496 listAction := action.NewList(actionConfig)
497 releases, err := listAction.Run()
501 for _, release := range releases {
502 //fmt.Println("Release: " + release.Name + " Status: " + release.Info.Status.String())
503 chart.Name = release.Name
504 chart.Namespace = release.Namespace
505 chart.Revision = release.Version
506 chart.Updated = release.Info.LastDeployed.String()
507 chart.Status = release.Info.Status.String()
508 chart.Chart = release.Chart.Metadata.Name + "-" + release.Chart.Metadata.Version
509 chart.AppVersion = release.Chart.Metadata.AppVersion
510 chart.Values = release.Chart.Values
511 charts = append(charts, chart)
516 func chartInstalled(chartName string) bool {
518 for _, chart := range charts {
519 if chart.Name == chartName {
526 func getChartValues(chartName string) error {
528 for _, chart := range charts {
529 if chart.Name == chartName {
530 rappMap := chart.Values["rapp"]
531 fmt.Println("rappMap:", rappMap)
532 // Convert map to json string
533 jsonStr, err := json.Marshal(rappMap)
535 fmt.Println("Error:", err)
539 if err := json.Unmarshal(jsonStr, &rapp); err != nil {
540 fmt.Println("Error:", err)
546 return errors.New("Chart: cannot retrieve values")
549 func registrerRapp(chartName, chartType string) {
550 psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
552 db, err := sql.Open("postgres", psqlconn)
556 fmt.Println("Connected!")
563 createStmt := `CREATE TABLE IF NOT EXISTS services (
564 id serial PRIMARY KEY,
565 name VARCHAR ( 50 ) UNIQUE NOT NULL,
566 type VARCHAR ( 50 ) NOT NULL,
567 created_on TIMESTAMP DEFAULT NOW()
569 _, err = db.Exec(createStmt)
573 fmt.Println("Created table for service registry")
577 insertDynStmt := `insert into "services"("name", "type") values($1, $2)`
578 _, err = db.Exec(insertDynStmt, chartName, chartType)
582 fmt.Println("Inserted " + chartName + " into service registry")
586 func unregistrerRapp(chartName, chartType string) {
587 psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
589 db, err := sql.Open("postgres", psqlconn)
593 fmt.Println("Connected!")
599 deleteDynStmt := `delete from services where name=$1 and type=$2`
600 _, err = db.Exec(deleteDynStmt, chartName, chartType)
604 fmt.Println("Deleted " + chartName + " from service registry")