blob: 9dec9f7dbad595f0ea978ea5b61d49881661f1db [file] [log] [blame]
// -
// ========================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
Authenticator 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 {
err := installSecurity(rapp)
if err != nil {
msg = err.Error()
} else {
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 installSecurity(rapp Rapp) error {
var url string
var params string
role := rapp.Roles[0].Role
grants := rapp.Roles[0].Grants[0]
realm := rapp.Realm
client := rapp.Client
authenticator := rapp.Authenticator
if !rapp.SecurityEnabled {
return nil
}
// Different security requirements depending on the rapp type
if rapp.Type == "provider" {
// keycloak client setup
fmt.Println("Setting up keycloak")
url = "http://rapps-keycloak-mgr.default/create?"
params = "realm=" + realm + "&name=" + client + "&role=" + role + "&authType=" + authenticator
url += params
_, err := http.Get(url)
if err != nil {
return err
} else {
fmt.Println("Setting up istio")
url = "http://rapps-istio-mgr.default/create-policy?"
params = "name=" + chartName + "&realm=" + realm + "&role=" + role + "&method=" + grants
url += params
_, err := http.Get(url)
if err != nil {
return err
}
}
} else {
fmt.Println("Setting up istio")
url = "http://rapps-istio-mgr.default/create-filter?"
params = "name=" + chartName + "&realm=" + realm + "&client=" + client + "&authType=" + authenticator
url += params
_, err := http.Get(url)
if err != nil {
return err
}
}
return nil
}
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
}
err := uninstallSecurity(rapp, chartName)
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 uninstallSecurity(rapp Rapp, chartName string) error {
var url string
var params string
role := rapp.Roles[0].Role
realm := rapp.Realm
client := rapp.Client
authenticator := rapp.Authenticator
if !rapp.SecurityEnabled {
return nil
}
if rapp.Type == "provider" {
// Remove istio objects for rapp
fmt.Println("Removing istio services")
_, err := http.Get("http://rapps-istio-mgr.default/remove-policy?name=" + chartName)
if err != nil {
return err
}
// remove keycloak client
fmt.Println("Removing keycloak client")
url = "http://rapps-keycloak-mgr.default/remove?"
params = "name=" + client + "&realm=" + realm + "&role=" + role + "&authType=" + authenticator
url += params
_, err = http.Get(url)
if err != nil {
return err
}
}
if rapp.Type == "invoker" {
// Remove istio objects for rapp
fmt.Println("Removing istio services")
_, err := http.Get("http://rapps-istio-mgr.default/remove-filter?name=" + chartName)
if err != nil {
return err
}
}
return nil
}
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
fmt.Printf("Repo File %s\n", repoFile)
//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)
fmt.Printf("Repo Name: %s\n",repoName)
fmt.Printf("Chart Name: %s\n",chartName)
cp, err := install.ChartPathOptions.LocateChart(fmt.Sprintf("%s/%s", repoName, chartName), settings)
fmt.Printf("Chart location: %s\n",cp)
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")
}
}