2 // ========================LICENSE_START=================================
5 // Copyright (C) 2022-2023: Nordix Foundation
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
11 // http://www.apache.org/licenses/LICENSE-2.0
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===================================
28 "github.com/pkg/errors"
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"
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"
47 var settings *cli.EnvSettings
48 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:"-"`
86 host = "postgres.default"
93 func 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)
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) {
107 fmt.Printf("Adding %s to Helm Repo\n", url)
108 _, err := addToRepo(url)
112 install, err = dryRun()
116 err := installSecurity(rapp)
120 fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace)
121 chart, err = installHelmChart(install)
123 msg = "Error occurred during installation " + err.Error()
125 msg = "Successfully installed release: " + chart
131 registrerRapp(chartName, rapp.Type)
133 msg = chartName + " has already been installed"
136 // create response binary data
137 data := []byte(msg) // slice of bytes
138 // write `data` to response
142 func installSecurity(rapp Rapp) error {
145 role := rapp.Roles[0].Role
146 grants := rapp.Roles[0].Grants[0]
148 client := rapp.Client
149 authenticator := rapp.Authenticator
151 if !rapp.SecurityEnabled {
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
161 _, err := http.Get(url)
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
170 _, err := http.Get(url)
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
180 _, err := http.Get(url)
190 func 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)
198 if chartInstalled(chartName) {
199 err := getChartValues(chartName)
203 chart, err = uninstallHelmChart(chartName)
205 msg = "Error occurred during uninstall " + err.Error()
207 msg = "Successfully uninstalled release: " + chart
209 err := uninstallSecurity(rapp, chartName)
214 unregistrerRapp(chartName, rapp.Type)
216 msg = chartName + " is not installed"
219 // create response binary data
220 data := []byte(msg) // slice of bytes
221 // write `data` to response
225 func uninstallSecurity(rapp Rapp, chartName string) error {
229 client := rapp.Client
230 authenticator := rapp.Authenticator
232 if !rapp.SecurityEnabled {
235 if rapp.Type == "provider" {
236 // Remove istio objects for rapp
237 fmt.Println("Removing istio services")
238 _, err := http.Get("http://rapps-istio-mgr.default/remove-policy?name=" + chartName)
242 // remove keycloak client
243 fmt.Println("Removing keycloak client")
244 url = "http://rapps-keycloak-mgr.default/remove?"
245 params = "name=" + client + "&realm=" + realm + "&authType=" + authenticator
247 _, err = http.Get(url)
252 if rapp.Type == "invoker" {
253 // Remove istio objects for rapp
254 fmt.Println("Removing istio services")
255 _, err := http.Get("http://rapps-istio-mgr.default/remove-filter?name=" + chartName)
263 func runList(res http.ResponseWriter, req *http.Request) {
265 // create response binary data
266 data, err := json.Marshal(chartInfo)
268 fmt.Printf("Error happened in JSON marshal. Err: %s\n", err)
270 // write `data` to response
275 //flag.StringVar(&url, "url", "http://chartmuseum:8080", "ChartMuseum url")
276 flag.StringVar(&repoName, "repoName", "local-dev", "Repository name")
277 flag.StringVar(&namespace, "namespace", "istio-nonrtric", "namespace for install")
281 runInstallHandler := http.HandlerFunc(runInstall)
282 http.Handle("/install", runInstallHandler)
283 runUninstallHandler := http.HandlerFunc(runUninstall)
284 http.Handle("/uninstall", runUninstallHandler)
285 runListHandler := http.HandlerFunc(runList)
286 http.Handle("/list", runListHandler)
287 http.ListenAndServe(":9000", nil)
290 func addToRepo(url string) (string, error) {
291 repoFile := settings.RepositoryConfig
292 fmt.Printf("Repo File %s\n", repoFile)
294 //Ensure the file directory exists as it is required for file locking
295 err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
296 if err != nil && !os.IsExist(err) {
300 b, err := ioutil.ReadFile(repoFile)
301 if err != nil && !os.IsNotExist(err) {
306 if err := yaml.Unmarshal(b, &f); err != nil {
311 fmt.Printf("repository name (%s) already exists\n", repoName)
320 r, err := repo.NewChartRepository(&c, getter.All(settings))
325 if _, err := r.DownloadIndexFile(); err != nil {
326 err := errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url)
332 if err := f.WriteFile(repoFile, 0644); err != nil {
335 fmt.Printf("%q has been added to your repositories\n", repoName)
339 func dryRun() (*action.Install, error) {
340 actionConfig, err := getActionConfig(namespace)
342 install := action.NewInstall(actionConfig)
344 fmt.Printf("Repo Name: %s\n",repoName)
345 fmt.Printf("Chart Name: %s\n",chartName)
346 cp, err := install.ChartPathOptions.LocateChart(fmt.Sprintf("%s/%s", repoName, chartName), settings)
347 fmt.Printf("Chart location: %s\n",cp)
349 chartRequested, err = loader.Load(cp)
351 install.Namespace = namespace
352 install.ReleaseName = releaseName
353 install.DryRun = true
354 rel, err := install.Run(chartRequested, nil)
360 rappMap := rel.Chart.Values["rapp"]
361 // Convert map to json string
362 jsonStr, err := json.Marshal(rappMap)
368 if err := json.Unmarshal(jsonStr, &rapp); err != nil {
372 fmt.Printf("Keycloak key/value pairs in values.yaml - Realm: %s Client: %s Client Role: %s\n", rapp.Realm, rapp.Client, rapp.Roles)
376 func installHelmChart(install *action.Install) (string, error) {
378 install.DryRun = false
379 rel, err := install.Run(chartRequested, nil)
383 fmt.Println("Successfully installed release: ", rel.Name)
388 func getActionConfig(namespace string) (*action.Configuration, error) {
389 actionConfig := new(action.Configuration)
390 // Create the rest config instance with ServiceAccount values loaded in them
391 config, err := rest.InClusterConfig()
393 // fallback to kubeconfig
394 home, exists := os.LookupEnv("HOME")
398 kubeconfigPath := filepath.Join(home, ".kube", "config")
399 if envvar := os.Getenv("KUBECONFIG"); len(envvar) > 0 {
400 kubeconfigPath = envvar
402 if err := actionConfig.Init(kube.GetConfig(kubeconfigPath, "", namespace), namespace, os.Getenv("HELM_DRIVER"),
403 func(format string, v ...interface{}) {
404 fmt.Sprintf(format, v)
409 // Create the ConfigFlags struct instance with initialized values from ServiceAccount
410 var kubeConfig *genericclioptions.ConfigFlags
411 kubeConfig = genericclioptions.NewConfigFlags(false)
412 kubeConfig.APIServer = &config.Host
413 kubeConfig.BearerToken = &config.BearerToken
414 kubeConfig.CAFile = &config.CAFile
415 kubeConfig.Namespace = &namespace
416 if err := actionConfig.Init(kubeConfig, namespace, os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) {
417 fmt.Sprintf(format, v)
422 return actionConfig, err
425 func uninstallHelmChart(name string) (string, error) {
426 actionConfig, err := getActionConfig(namespace)
431 iCli := action.NewUninstall(actionConfig)
433 resp, err := iCli.Run(name)
437 fmt.Println("Successfully uninstalled release: ", resp.Release.Name)
438 return resp.Release.Name, err
441 func connectToK8s() *kubernetes.Clientset {
442 config, err := rest.InClusterConfig()
444 fmt.Println("failed to create K8s config")
447 clientset, err := kubernetes.NewForConfig(config)
449 fmt.Println("Failed to create K8s clientset")
455 func findService(serviceName, namespace string) (string, int32) {
456 clientset := connectToK8s()
457 svc, err := clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{})
459 fmt.Println(err.Error())
461 return svc.Name, svc.Spec.Ports[0].Port
464 func list() []ChartInfo {
465 var charts = []ChartInfo{}
467 actionConfig, err := getActionConfig(namespace)
472 listAction := action.NewList(actionConfig)
473 releases, err := listAction.Run()
477 for _, release := range releases {
478 //fmt.Println("Release: " + release.Name + " Status: " + release.Info.Status.String())
479 chart.Name = release.Name
480 chart.Namespace = release.Namespace
481 chart.Revision = release.Version
482 chart.Updated = release.Info.LastDeployed.String()
483 chart.Status = release.Info.Status.String()
484 chart.Chart = release.Chart.Metadata.Name + "-" + release.Chart.Metadata.Version
485 chart.AppVersion = release.Chart.Metadata.AppVersion
486 chart.Values = release.Chart.Values
487 charts = append(charts, chart)
492 func chartInstalled(chartName string) bool {
494 for _, chart := range charts {
495 if chart.Name == chartName {
502 func getChartValues(chartName string) error {
504 for _, chart := range charts {
505 if chart.Name == chartName {
506 rappMap := chart.Values["rapp"]
507 fmt.Println("rappMap:", rappMap)
508 // Convert map to json string
509 jsonStr, err := json.Marshal(rappMap)
511 fmt.Println("Error:", err)
515 if err := json.Unmarshal(jsonStr, &rapp); err != nil {
516 fmt.Println("Error:", err)
522 return errors.New("Chart: cannot retrieve values")
525 func registrerRapp(chartName, chartType string) {
526 psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
528 db, err := sql.Open("postgres", psqlconn)
532 fmt.Println("Connected!")
539 createStmt := `CREATE TABLE IF NOT EXISTS services (
540 id serial PRIMARY KEY,
541 name VARCHAR ( 50 ) UNIQUE NOT NULL,
542 type VARCHAR ( 50 ) NOT NULL,
543 created_on TIMESTAMP DEFAULT NOW()
545 _, err = db.Exec(createStmt)
549 fmt.Println("Created table for service registry")
553 insertDynStmt := `insert into "services"("name", "type") values($1, $2)`
554 _, err = db.Exec(insertDynStmt, chartName, chartType)
558 fmt.Println("Inserted " + chartName + " into service registry")
562 func unregistrerRapp(chartName, chartType string) {
563 psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
565 db, err := sql.Open("postgres", psqlconn)
569 fmt.Println("Connected!")
575 deleteDynStmt := `delete from services where name=$1 and type=$2`
576 _, err = db.Exec(deleteDynStmt, chartName, chartType)
580 fmt.Println("Deleted " + chartName + " from service registry")