X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=blobdiff_plain;f=service-exposure%2Frapps-helm-installer.go;fp=service-exposure%2Frapps-helm-installer.go;h=dc92fc79af7577a8edd6db6ab1df3f39a4d61307;hb=28fa9fbfee514da8c85171facbabe6747f499988;hp=0000000000000000000000000000000000000000;hpb=4bedc3ee0dafa48108d79f81174a6e679acabd25;p=nonrtric.git diff --git a/service-exposure/rapps-helm-installer.go b/service-exposure/rapps-helm-installer.go new file mode 100644 index 00000000..dc92fc79 --- /dev/null +++ b/service-exposure/rapps-helm-installer.go @@ -0,0 +1,522 @@ +// - +// ========================LICENSE_START================================= +// O-RAN-SC +// %% +// Copyright (C) 2022: Nordix Foundation +// %% +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ========================LICENSE_END=================================== +// + +package main + +import ( + "context" + "database/sql" + "encoding/json" + "flag" + "fmt" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/kube" + "helm.sh/helm/v3/pkg/repo" + "io/ioutil" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" + kubernetes "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "net/http" + "os" + "path/filepath" +) + +var settings *cli.EnvSettings +var chartRequested *chart.Chart + +//var url string +var repoName string +var chartName string +var releaseName string +var namespace string + +type ChartInfo struct { + Name string `json:",omitempty"` + Namespace string `json:",omitempty"` + Revision int `json:",omitempty"` + Updated string `json:",omitempty"` + Status string `json:",omitempty"` + Chart string `json:",omitempty"` + AppVersion string `json:",omitempty"` + Values map[string]interface{} `json:"-"` +} + +type Rapp struct { + Type string + SecurityEnabled bool + Realm string + Client string + Roles []struct { + Role string + Grants []string + } + Apps []struct { + Prefix string + Methods []string + } +} + +var rapp Rapp + +const ( + host = "postgres.default" + port = 5432 + user = "capif" + password = "capif" + dbname = "capif" +) + +func runInstall(res http.ResponseWriter, req *http.Request) { + query := req.URL.Query() + chartName = query.Get("chart") + releaseName = chartName + fmt.Println("Installing ", chartName) + + var msg string + var chart string + var install *action.Install + chartMuseumService, chartMuseumPort := findService("chartmuseum", "default") + fmt.Printf("Chart Museum service:%s, Port:%d\n", chartMuseumService, chartMuseumPort) + url := "http://" + chartMuseumService + ":" + fmt.Sprint(chartMuseumPort) + if !chartInstalled(chartName) { + // Add repo + fmt.Printf("Adding %s to Helm Repo\n", url) + _, err := addToRepo(url) + if err != nil { + msg = err.Error() + } else { + install, err = dryRun() + if err != nil { + msg = err.Error() + } else { + if rapp.SecurityEnabled && rapp.Type == "provider" { + // keycloak client setup + fmt.Println("Setting up keycloak") + _, err = http.Get("http://rapps-keycloak-mgr.default/create?realm=" + rapp.Realm + "&name=" + rapp.Client + "&role=" + rapp.Roles[0].Role) + if err != nil { + msg = err.Error() + } else { + fmt.Println("Setting up istio") + _, 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]) + if err != nil { + msg = err.Error() + } else { + // Install chart + fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace) + chart, err = installHelmChart(install) + if err != nil { + msg = "Error occurred during installation " + err.Error() + } else { + msg = "Successfully installed release: " + chart + } + } + } + } else { + // Install chart + fmt.Printf("Installing chart %s to %s namespace\n", chartName, namespace) + chart, err = installHelmChart(install) + if err != nil { + msg = "Error occurred during installation " + err.Error() + } else { + msg = "Successfully installed release: " + chart + } + } + + } + } + registrerRapp(chartName, rapp.Type) + } else { + msg = chartName + " has already been installed" + } + + // create response binary data + data := []byte(msg) // slice of bytes + // write `data` to response + res.Write(data) +} + +func runUninstall(res http.ResponseWriter, req *http.Request) { + query := req.URL.Query() + chartName = query.Get("chart") + releaseName = chartName + fmt.Println("Uninstalling ", chartName) + + var msg string + var chart string + if chartInstalled(chartName) { + err := getChartValues(chartName) + if err != nil { + msg = err.Error() + } else { + chart, err = uninstallHelmChart(chartName) + if err != nil { + msg = "Error occurred during uninstall " + err.Error() + } else { + msg = "Successfully uninstalled release: " + chart + } + if rapp.SecurityEnabled && rapp.Type == "provider" { + // Remove istio objects for rapp + fmt.Println("Removing istio services") + _, err := http.Get("http://rapps-istio-mgr.default/remove?name=" + chartName) + if err != nil { + msg = err.Error() + } + // remove keycloak client + fmt.Println("Removing keycloak client") + _, err = http.Get("http://rapps-keycloak-mgr.default/remove?realm=" + rapp.Realm + "&name=" + rapp.Client + "&role=" + rapp.Roles[0].Role) + if err != nil { + msg = err.Error() + } + } + } + unregistrerRapp(chartName, rapp.Type) + } else { + msg = chartName + " is not installed" + } + + // create response binary data + data := []byte(msg) // slice of bytes + // write `data` to response + res.Write(data) +} + +func runList(res http.ResponseWriter, req *http.Request) { + chartInfo := list() + // create response binary data + data, err := json.Marshal(chartInfo) + if err != nil { + fmt.Printf("Error happened in JSON marshal. Err: %s\n", err) + } + // write `data` to response + res.Write(data) +} + +func main() { + //flag.StringVar(&url, "url", "http://chartmuseum:8080", "ChartMuseum url") + flag.StringVar(&repoName, "repoName", "local-dev", "Repository name") + flag.StringVar(&namespace, "namespace", "istio-nonrtric", "namespace for install") + flag.Parse() + settings = cli.New() + + runInstallHandler := http.HandlerFunc(runInstall) + http.Handle("/install", runInstallHandler) + runUninstallHandler := http.HandlerFunc(runUninstall) + http.Handle("/uninstall", runUninstallHandler) + runListHandler := http.HandlerFunc(runList) + http.Handle("/list", runListHandler) + http.ListenAndServe(":9000", nil) +} + +func addToRepo(url string) (string, error) { + repoFile := settings.RepositoryConfig + + //Ensure the file directory exists as it is required for file locking + err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm) + if err != nil && !os.IsExist(err) { + return "", err + } + + b, err := ioutil.ReadFile(repoFile) + if err != nil && !os.IsNotExist(err) { + return "", err + } + + var f repo.File + if err := yaml.Unmarshal(b, &f); err != nil { + return "", err + } + + if f.Has(repoName) { + fmt.Printf("repository name (%s) already exists\n", repoName) + return "", nil + } + + c := repo.Entry{ + Name: repoName, + URL: url, + } + + r, err := repo.NewChartRepository(&c, getter.All(settings)) + if err != nil { + return "", err + } + + if _, err := r.DownloadIndexFile(); err != nil { + err := errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", url) + return "", err + } + + f.Update(&c) + + if err := f.WriteFile(repoFile, 0644); err != nil { + return "", err + } + fmt.Printf("%q has been added to your repositories\n", repoName) + return "", nil +} + +func dryRun() (*action.Install, error) { + actionConfig, err := getActionConfig(namespace) + + install := action.NewInstall(actionConfig) + + cp, err := install.ChartPathOptions.LocateChart(fmt.Sprintf("%s/%s", repoName, chartName), settings) + + chartRequested, err = loader.Load(cp) + + install.Namespace = namespace + install.ReleaseName = releaseName + install.DryRun = true + rel, err := install.Run(chartRequested, nil) + if err != nil { + fmt.Println(err) + return install, err + } + + rappMap := rel.Chart.Values["rapp"] + // Convert map to json string + jsonStr, err := json.Marshal(rappMap) + if err != nil { + fmt.Println(err) + return install, err + } + + if err := json.Unmarshal(jsonStr, &rapp); err != nil { + fmt.Println(err) + return install, err + } + fmt.Printf("Keycloak key/value pairs in values.yaml - Realm: %s Client: %s Client Role: %s\n", rapp.Realm, rapp.Client, rapp.Roles) + return install, nil +} + +func installHelmChart(install *action.Install) (string, error) { + + install.DryRun = false + rel, err := install.Run(chartRequested, nil) + if err != nil { + fmt.Println(err) + } + fmt.Println("Successfully installed release: ", rel.Name) + + return rel.Name, err +} + +func getActionConfig(namespace string) (*action.Configuration, error) { + actionConfig := new(action.Configuration) + // Create the rest config instance with ServiceAccount values loaded in them + config, err := rest.InClusterConfig() + if err != nil { + // fallback to kubeconfig + home, exists := os.LookupEnv("HOME") + if !exists { + home = "/root" + } + kubeconfigPath := filepath.Join(home, ".kube", "config") + if envvar := os.Getenv("KUBECONFIG"); len(envvar) > 0 { + kubeconfigPath = envvar + } + if err := actionConfig.Init(kube.GetConfig(kubeconfigPath, "", namespace), namespace, os.Getenv("HELM_DRIVER"), + func(format string, v ...interface{}) { + fmt.Sprintf(format, v) + }); err != nil { + fmt.Println(err) + } + } else { + // Create the ConfigFlags struct instance with initialized values from ServiceAccount + var kubeConfig *genericclioptions.ConfigFlags + kubeConfig = genericclioptions.NewConfigFlags(false) + kubeConfig.APIServer = &config.Host + kubeConfig.BearerToken = &config.BearerToken + kubeConfig.CAFile = &config.CAFile + kubeConfig.Namespace = &namespace + if err := actionConfig.Init(kubeConfig, namespace, os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) { + fmt.Sprintf(format, v) + }); err != nil { + fmt.Println(err) + } + } + return actionConfig, err +} + +func uninstallHelmChart(name string) (string, error) { + actionConfig, err := getActionConfig(namespace) + if err != nil { + fmt.Println(err) + } + + iCli := action.NewUninstall(actionConfig) + + resp, err := iCli.Run(name) + if err != nil { + fmt.Println(err) + } + fmt.Println("Successfully uninstalled release: ", resp.Release.Name) + return resp.Release.Name, err +} + +func connectToK8s() *kubernetes.Clientset { + config, err := rest.InClusterConfig() + if err != nil { + fmt.Println("failed to create K8s config") + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Println("Failed to create K8s clientset") + } + + return clientset +} + +func findService(serviceName, namespace string) (string, int32) { + clientset := connectToK8s() + svc, err := clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) + if err != nil { + fmt.Println(err.Error()) + } + return svc.Name, svc.Spec.Ports[0].Port +} + +func list() []ChartInfo { + var charts = []ChartInfo{} + var chart ChartInfo + actionConfig, err := getActionConfig(namespace) + if err != nil { + panic(err) + } + + listAction := action.NewList(actionConfig) + releases, err := listAction.Run() + if err != nil { + fmt.Println(err) + } + for _, release := range releases { + //fmt.Println("Release: " + release.Name + " Status: " + release.Info.Status.String()) + chart.Name = release.Name + chart.Namespace = release.Namespace + chart.Revision = release.Version + chart.Updated = release.Info.LastDeployed.String() + chart.Status = release.Info.Status.String() + chart.Chart = release.Chart.Metadata.Name + "-" + release.Chart.Metadata.Version + chart.AppVersion = release.Chart.Metadata.AppVersion + chart.Values = release.Chart.Values + charts = append(charts, chart) + } + return charts +} + +func chartInstalled(chartName string) bool { + charts := list() + for _, chart := range charts { + if chart.Name == chartName { + return true + } + } + return false +} + +func getChartValues(chartName string) error { + charts := list() + for _, chart := range charts { + if chart.Name == chartName { + rappMap := chart.Values["rapp"] + fmt.Println("rappMap:", rappMap) + // Convert map to json string + jsonStr, err := json.Marshal(rappMap) + if err != nil { + fmt.Println("Error:", err) + return err + } + + if err := json.Unmarshal(jsonStr, &rapp); err != nil { + fmt.Println("Error:", err) + return err + } + return nil + } + } + return errors.New("Chart: cannot retrieve values") +} + +func registrerRapp(chartName, chartType string) { + psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) + + db, err := sql.Open("postgres", psqlconn) + if err != nil { + fmt.Println(err) + } else { + fmt.Println("Connected!") + } + + defer db.Close() + + // create + // hardcoded + createStmt := `CREATE TABLE IF NOT EXISTS services ( + id serial PRIMARY KEY, + name VARCHAR ( 50 ) UNIQUE NOT NULL, + type VARCHAR ( 50 ) NOT NULL, + created_on TIMESTAMP DEFAULT NOW() + );` + _, err = db.Exec(createStmt) + if err != nil { + fmt.Println(err) + } else { + fmt.Println("Created table for service registry") + } + + // dynamic + insertDynStmt := `insert into "services"("name", "type") values($1, $2)` + _, err = db.Exec(insertDynStmt, chartName, chartType) + if err != nil { + fmt.Println(err) + } else { + fmt.Println("Inserted " + chartName + " into service registry") + } +} + +func unregistrerRapp(chartName, chartType string) { + psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) + + db, err := sql.Open("postgres", psqlconn) + if err != nil { + fmt.Println(err) + } else { + fmt.Println("Connected!") + } + + defer db.Close() + + // dynamic + deleteDynStmt := `delete from services where name=$1 and type=$2` + _, err = db.Exec(deleteDynStmt, chartName, chartType) + if err != nil { + fmt.Println(err) + } else { + fmt.Println("Deleted " + chartName + " from service registry") + } +}