Просмотр исходного кода

Merge pull request #976 from kubecost/kaelan-costanalyzerserver

Migrate cost-analyzer-server endpoints used on frontend to cost-model router
Kaelan Patel 4 лет назад
Родитель
Сommit
c5c9ad147e
1 измененных файлов с 437 добавлено и 0 удалено
  1. 437 0
      pkg/costmodel/router.go

+ 437 - 0
pkg/costmodel/router.go

@@ -2,9 +2,12 @@ package costmodel
 
 import (
 	"context"
+	"encoding/base64"
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"net/http"
+	"os"
 	"reflect"
 	"strconv"
 	"strings"
@@ -15,9 +18,13 @@ import (
 	"github.com/kubecost/cost-model/pkg/util/httputil"
 	"github.com/kubecost/cost-model/pkg/util/timeutil"
 	"github.com/kubecost/cost-model/pkg/util/watcher"
+	"github.com/microcosm-cc/bluemonday"
 
+	v1 "k8s.io/api/core/v1"
 	"k8s.io/klog"
 
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+
 	"github.com/julienschmidt/httprouter"
 
 	sentry "github.com/getsentry/sentry-go"
@@ -43,6 +50,8 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 )
 
+var sanitizePolicy = bluemonday.UGCPolicy()
+
 const (
 	RFC3339Milli         = "2006-01-02T15:04:05.000Z"
 	maxCacheMinutes1d    = 11
@@ -51,6 +60,8 @@ const (
 	maxCacheMinutes30d   = 137
 	CustomPricingSetting = "CustomPricing"
 	DiscountSetting      = "Discount"
+	epRules              = apiPrefix + "/rules"
+	LogSeparator         = "+-------------------------------------------------------------------------------------"
 )
 
 var (
@@ -910,6 +921,412 @@ func (a *Accesses) GetPrometheusMetrics(w http.ResponseWriter, _ *http.Request,
 	w.Write(WrapData(result, nil))
 }
 
+func (a *Accesses) GetAllPersistentVolumes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	pvList, err := a.KubeClientSet.CoreV1().PersistentVolumes().List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting persistent volume %v\n", err)
+	}
+
+	body, err := json.Marshal(pvList)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding persistent volumes: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+
+}
+
+func (a *Accesses) GetAllDeployments(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+	namespace := r.URL.Query().Get("namespace")
+	deploymentsList, err := a.KubeClientSet.AppsV1().Deployments(namespace).List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting deployments %v\n", err)
+	}
+	body, err := json.Marshal(deploymentsList)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetAllStorageClasses(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	scList, err := a.KubeClientSet.StorageV1().StorageClasses().List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting storageclasses: "+err.Error())
+	}
+	body, err := json.Marshal(scList)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding storageclasses: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetAllStatefulSets(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+	namespace := r.URL.Query().Get("namespace")
+	deploymentsList, err := a.KubeClientSet.AppsV1().StatefulSets(namespace).List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting deployments %v\n", err)
+	}
+	body, err := json.Marshal(deploymentsList)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetAllNodes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	nodeList, err := a.KubeClientSet.CoreV1().Nodes().List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting node %v\n", err)
+	}
+
+	body, err := json.Marshal(nodeList)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding nodes: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetAllPods(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	podlist, err := a.KubeClientSet.CoreV1().Pods("").List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting pod %v\n", err)
+	}
+
+	body, err := json.Marshal(podlist)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding pods: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetAllNamespaces(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	namespaces, err := a.KubeClientSet.CoreV1().Namespaces().List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting namespaces %v\n", err)
+	}
+	body, err := json.Marshal(namespaces)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding deployment: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetAllDaemonSets(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+	daemonSets, err := a.KubeClientSet.AppsV1().DaemonSets("").List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting daemon sets %v\n", err)
+	}
+	body, err := json.Marshal(daemonSets)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding daemon set: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetPod(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	podName := ps.ByName("name")
+	podNamespace := ps.ByName("namespace")
+
+	// Examples for error handling:
+	// - Use helper functions like e.g. errors.IsNotFound()
+	// - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
+	pod, err := a.KubeClientSet.CoreV1().Pods(podNamespace).Get(r.Context(), podName, metav1.GetOptions{})
+	if k8serrors.IsNotFound(err) {
+		fmt.Fprintf(w, "Pod not found\n")
+	} else if statusError, isStatus := err.(*k8serrors.StatusError); isStatus {
+		fmt.Fprintf(w, "Error getting pod %v\n", statusError.ErrStatus.Message)
+	} else if err != nil {
+		fmt.Fprintf(w, "Error getting pod: "+err.Error())
+	} else {
+		body, err := json.Marshal(pod)
+		if err != nil {
+			fmt.Fprintf(w, "Error decoding pod: "+err.Error())
+		} else {
+			w.Write(body)
+		}
+	}
+}
+
+func (a *Accesses) PrometheusRecordingRules(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	u := a.PrometheusClient.URL(epRules, nil)
+
+	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
+	if err != nil {
+		fmt.Fprintf(w, "Error creating Prometheus rule request: "+err.Error())
+	}
+
+	_, body, _, err := a.PrometheusClient.Do(r.Context(), req)
+	if err != nil {
+		fmt.Fprintf(w, "Error making Prometheus rule request: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) PrometheusConfig(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	pConfig := make(map[string]string)
+
+	pConfig["address"] = os.Getenv("PROMETHEUS_SERVER_ENDPOINT")
+
+	body, err := json.Marshal(pConfig)
+	if err != nil {
+		fmt.Fprintf(w, "Error marshalling prometheus config")
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) PrometheusTargets(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	u := a.PrometheusClient.URL(epTargets, nil)
+
+	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
+	if err != nil {
+		fmt.Fprintf(w, "Error creating Prometheus rule request: "+err.Error())
+	}
+
+	_, body, _, err := a.PrometheusClient.Do(r.Context(), req)
+	if err != nil {
+		fmt.Fprintf(w, "Error making Prometheus rule request: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetOrphanedPods(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	podlist, err := a.KubeClientSet.CoreV1().Pods("").List(r.Context(), metav1.ListOptions{})
+	if err != nil {
+		fmt.Fprintf(w, "Error getting pod %v\n", err)
+	}
+
+	var lonePods []v1.Pod
+	for _, pod := range podlist.Items {
+		if len(pod.OwnerReferences) == 0 {
+			lonePods = append(lonePods, pod)
+		}
+	}
+
+	body, err := json.Marshal(lonePods)
+	if err != nil {
+		fmt.Fprintf(w, "Error decoding pod: "+err.Error())
+	} else {
+		w.Write(body)
+	}
+}
+
+func (a *Accesses) GetInstallNamespace(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	ns := os.Getenv("KUBECOST_NAMESPACE")
+	w.Write([]byte(ns))
+}
+
+func logsFor(c kubernetes.Interface, namespace string, pod string, container string, dur time.Duration, ctx context.Context) (string, error) {
+	since := time.Now().UTC().Add(-dur)
+
+	logOpts := v1.PodLogOptions{
+		SinceTime: &metav1.Time{Time: since},
+	}
+	if container != "" {
+		logOpts.Container = container
+	}
+
+	req := c.CoreV1().Pods(namespace).GetLogs(pod, &logOpts)
+	reader, err := req.Stream(ctx)
+	if err != nil {
+		return "", err
+	}
+
+	podLogs, err := ioutil.ReadAll(reader)
+	if err != nil {
+		return "", err
+	}
+
+	return string(podLogs), nil
+}
+
+func (a *Accesses) GetPodLogs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	q := r.URL.Query()
+	ns := q.Get("namespace")
+	if ns == "" {
+		ns = os.Getenv("KUBECOST_NAMESPACE")
+	}
+	pod := q.Get("pod")
+	selector := q.Get("selector")
+	container := q.Get("container")
+	since := q.Get("since")
+	if since == "" {
+		since = "24h"
+	}
+
+	sinceDuration, err := time.ParseDuration(since)
+	if err != nil {
+		fmt.Fprintf(w, "Invalid Duration String: "+err.Error())
+		return
+	}
+
+	var logResult string
+	appendLog := func(ns string, pod string, container string, l string) {
+		if l == "" {
+			return
+		}
+
+		logResult += fmt.Sprintf("%s\n| %s:%s:%s\n%s\n%s\n\n", LogSeparator, ns, pod, container, LogSeparator, l)
+	}
+
+	if pod != "" {
+		pd, err := a.KubeClientSet.CoreV1().Pods(ns).Get(r.Context(), pod, metav1.GetOptions{})
+		if err != nil {
+			fmt.Fprintf(w, "Error Finding Pod: "+err.Error())
+			return
+		}
+
+		if container != "" {
+			var foundContainer bool
+			for _, cont := range pd.Spec.Containers {
+				if strings.EqualFold(cont.Name, container) {
+					foundContainer = true
+					break
+				}
+			}
+			if !foundContainer {
+				fmt.Fprintf(w, "Could not find container: "+container)
+				return
+			}
+		}
+
+		logs, err := logsFor(a.KubeClientSet, ns, pod, container, sinceDuration, r.Context())
+		if err != nil {
+			fmt.Fprintf(w, "Error Getting Logs: "+err.Error())
+			return
+		}
+
+		appendLog(ns, pod, container, logs)
+
+		w.Write([]byte(logResult))
+		return
+	}
+
+	if selector != "" {
+		pods, err := a.KubeClientSet.CoreV1().Pods(ns).List(r.Context(), metav1.ListOptions{LabelSelector: selector})
+		if err != nil {
+			fmt.Fprintf(w, "Error Finding Pod: "+err.Error())
+			return
+		}
+
+		for _, pd := range pods.Items {
+			for _, cont := range pd.Spec.Containers {
+				logs, err := logsFor(a.KubeClientSet, ns, pd.Name, cont.Name, sinceDuration, r.Context())
+				if err != nil {
+					continue
+				}
+				appendLog(ns, pd.Name, cont.Name, logs)
+			}
+		}
+	}
+
+	w.Write([]byte(logResult))
+}
+
+func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	r.ParseForm()
+
+	//p.CloudProvider.AddServiceKey(r.PostForm)
+
+	key := r.PostForm.Get("key")
+	k := []byte(key)
+	err := ioutil.WriteFile("/var/configs/key.json", k, 0644)
+	if err != nil {
+		fmt.Fprintf(w, "Error writing service key: "+err.Error())
+	}
+
+	w.WriteHeader(http.StatusOK)
+}
+
+func (a *Accesses) GetHelmValues(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	encodedValues := os.Getenv("HELM_VALUES")
+	if encodedValues == "" {
+		fmt.Fprintf(w, "Values reporting disabled")
+		return
+	}
+
+	result, err := base64.StdEncoding.DecodeString(encodedValues)
+	if err != nil {
+		fmt.Fprintf(w, "Failed to decode encoded values: %s", err)
+		return
+	}
+
+	w.Write(result)
+}
+
+func (a *Accesses) Status(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+
+	api := prometheusAPI.NewAPI(a.PrometheusClient)
+	result, err := api.Config(r.Context())
+	if err != nil {
+
+		fmt.Fprintf(w, "Using Prometheus at "+os.Getenv("PROMETHEUS_SERVER_ENDPOINT")+". Error: "+err.Error())
+	} else {
+
+		fmt.Fprintf(w, "Using Prometheus at "+os.Getenv("PROMETHEUS_SERVER_ENDPOINT")+". PrometheusConfig: "+result.YAML)
+	}
+}
+
 // captures the panic event in sentry
 func capturePanicEvent(err string, stack string) {
 	msg := fmt.Sprintf("Panic: %s\nStackTrace: %s\n", err, stack)
@@ -1184,6 +1601,26 @@ func Initialize(additionalConfigWatchers ...*watcher.ConfigMapWatcher) *Accesses
 	a.Router.GET("/pricingSourceStatus", a.GetPricingSourceStatus)
 	a.Router.GET("/pricingSourceCounts", a.GetPricingSourceCounts)
 
+	// endpoints migrated from server
+	a.Router.GET("/allPersistentVolumes", a.GetAllPersistentVolumes)
+	a.Router.GET("/allDeployments", a.GetAllDeployments)
+	a.Router.GET("/allStorageClasses", a.GetAllStorageClasses)
+	a.Router.GET("/allStatefulSets", a.GetAllStatefulSets)
+	a.Router.GET("/allNodes", a.GetAllNodes)
+	a.Router.GET("/allPods", a.GetAllPods)
+	a.Router.GET("/allNamespaces", a.GetAllNamespaces)
+	a.Router.GET("/allDaemonSets", a.GetAllDaemonSets)
+	a.Router.GET("/pod/:namespace/:name", a.GetPod)
+	a.Router.GET("/prometheusRecordingRules", a.PrometheusRecordingRules)
+	a.Router.GET("/prometheusConfig", a.PrometheusConfig)
+	a.Router.GET("/prometheusTargets", a.PrometheusTargets)
+	a.Router.GET("/orphanedPods", a.GetOrphanedPods)
+	a.Router.GET("/installNamespace", a.GetInstallNamespace)
+	a.Router.GET("/podLogs", a.GetPodLogs)
+	a.Router.POST("/serviceKey", a.AddServiceKey)
+	a.Router.GET("/helmValues", a.GetHelmValues)
+	a.Router.GET("/status", a.Status)
+
 	// prom query proxies
 	a.Router.GET("/prometheusQuery", a.PrometheusQuery)
 	a.Router.GET("/prometheusQueryRange", a.PrometheusQueryRange)