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

Merge pull request #679 from porter-dev/0.3.0-status-revamp

stream kubelet events for a pod when in waiting/pending/failed state
abelanger5 5 лет назад
Родитель
Сommit
c76178112e

+ 69 - 0
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -2,6 +2,7 @@ import React, { Component } from "react";
 import styled from "styled-components";
 import { Context } from "shared/Context";
 import * as Anser from "anser";
+import api from "shared/api";
 
 type PropsType = {
   selectedPod: any;
@@ -91,6 +92,37 @@ export default class Logs extends Component<PropsType, StateType> {
     });
   };
 
+  getPodStatus = (status: any) => {
+    if (
+      status?.phase === "Pending" &&
+      status?.containerStatuses !== undefined
+    ) {
+      return status.containerStatuses[0].state.waiting.reason;
+    } else if (status?.phase === "Pending") {
+      return "pending";
+    }
+
+    if (status?.phase === "Failed") {
+      return "failed";
+    }
+
+    if (status?.phase === "Running") {
+      let collatedStatus = "running";
+
+      status?.containerStatuses?.forEach((s: any) => {
+        if (s.state?.waiting) {
+          collatedStatus =
+            s.state?.waiting.reason === "CrashLoopBackOff"
+              ? "failed"
+              : "waiting";
+        } else if (s.state?.terminated) {
+          collatedStatus = "failed";
+        }
+      });
+      return collatedStatus;
+    }
+  };
+
   setupWebsocket = () => {
     let { currentCluster, currentProject } = this.context;
     let { selectedPod } = this.props;
@@ -130,6 +162,43 @@ export default class Logs extends Component<PropsType, StateType> {
   };
 
   componentDidMount() {
+    let { selectedPod } = this.props;
+    let status = this.getPodStatus(selectedPod?.status);
+    if (status == "failed" || status == "pending" || status == "waiting") {
+      api
+        .getPodEvents(
+          "<token>",
+          {
+            cluster_id: this.context.currentCluster.id,
+          },
+          {
+            name: selectedPod?.metadata?.name,
+            namespace: selectedPod?.metadata?.namespace,
+            id: this.context.currentProject.id,
+          }
+        )
+        .then((res) => {
+          let logs = [] as Anser.AnserJsonEntry[][];
+          // TODO: column view
+          // logs.push(Anser.ansiToJson("\u001b[33;5;196mEvent Type\u001b[0m \t || \t \u001b[43m\u001b[34m\tReason\t\u001b[0m \t ||\tMessage"))
+
+          res.data.items.forEach((evt: any) => {
+            let ansiEvtType =
+              evt.type == "Warning" ? "\u001b[31m" : "\u001b[32m";
+            let ansiLog = Anser.ansiToJson(
+              `${ansiEvtType}${evt.type}\u001b[0m \t \u001b[43m\u001b[34m\t${evt.reason} \u001b[0m \t ${evt.message}`
+            );
+            logs.push(ansiLog);
+          });
+          this.setState({ logs: logs });
+          console.log(res);
+        })
+        .catch((err) => {
+          console.log(err);
+        });
+      return;
+    }
+
     this.setupWebsocket();
     this.scrollToBottom(false);
   }

+ 10 - 0
dashboard/src/shared/api.tsx

@@ -230,6 +230,15 @@ const deletePod = baseApi<
   return `/api/projects/${pathParams.id}/k8s/pods/${pathParams.namespace}/${pathParams.name}`;
 });
 
+const getPodEvents = baseApi<
+  {
+    cluster_id: number;
+  },
+  { name: string; namespace: string; id: number }
+>("GET", (pathParams) => {
+  return `/api/projects/${pathParams.id}/k8s/pods/${pathParams.namespace}/${pathParams.name}/events/list`;
+});
+
 const deleteProject = baseApi<{}, { id: number }>("DELETE", (pathParams) => {
   return `/api/projects/${pathParams.id}`;
 });
@@ -851,6 +860,7 @@ export default {
   getNamespaces,
   getNGINXIngresses,
   getOAuthIds,
+  getPodEvents,
   getProcfileContents,
   getProjectClusters,
   getProjectRegistries,

+ 10 - 0
internal/kubernetes/agent.go

@@ -215,6 +215,16 @@ func (a *Agent) ListConfigMaps(namespace string) (*v1.ConfigMapList, error) {
 	)
 }
 
+// ListEvents lists the events of a given object.
+func (a *Agent) ListEvents(name string, namespace string) (*v1.EventList, error) {
+	return a.Clientset.CoreV1().Events(namespace).List(
+		context.TODO(),
+		metav1.ListOptions{
+			FieldSelector: fmt.Sprintf("involvedObject.name=%s,involvedObject.namespace=%s", name, namespace),
+		},
+	)
+}
+
 // ListNamespaces simply lists namespaces
 func (a *Agent) ListNamespaces() (*v1.NamespaceList, error) {
 	return a.Clientset.CoreV1().Namespaces().List(

+ 51 - 0
server/api/k8s_handler.go

@@ -75,6 +75,57 @@ func (app *App) HandleListNamespaces(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+// HandleListPodEvents retrieves all events tied to a pod.
+func (app *App) HandleListPodEvents(w http.ResponseWriter, r *http.Request) {
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	// get path parameters
+	namespace := chi.URLParam(r, "namespace")
+	name := chi.URLParam(r, "name")
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
+		return
+	}
+
+	// get the filter options
+	form := &forms.K8sForm{
+		OutOfClusterConfig: &kubernetes.OutOfClusterConfig{
+			Repo:              app.Repo,
+			DigitalOceanOAuth: app.DOConf,
+		},
+	}
+
+	form.PopulateK8sOptionsFromQueryParams(vals, app.Repo.Cluster)
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		app.handleErrorFormValidation(err, ErrK8sValidate, w)
+		return
+	}
+
+	// create a new agent
+	var agent *kubernetes.Agent
+
+	if app.ServerConf.IsTesting {
+		agent = app.TestAgents.K8sAgent
+	} else {
+		agent, err = kubernetes.GetAgentOutOfClusterConfig(form.OutOfClusterConfig)
+	}
+
+	events, err := agent.ListEvents(name, namespace)
+
+	if err != nil {
+		app.handleErrorDataRead(err, w)
+		return
+	}
+
+	if err := json.NewEncoder(w).Encode(events); err != nil {
+		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
+		return
+	}
+}
+
 // HandleCreateConfigMap deletes the pod given the name and namespace.
 func (app *App) HandleCreateConfigMap(w http.ResponseWriter, r *http.Request) {
 	vals, err := url.ParseQuery(r.URL.RawQuery)

+ 14 - 0
server/router/router.go

@@ -1210,6 +1210,20 @@ func New(a *api.App) *chi.Mux {
 				),
 			)
 
+			r.Method(
+				"GET",
+				"/projects/{project_id}/k8s/pods/{namespace}/{name}/events/list",
+				auth.DoesUserHaveProjectAccess(
+					auth.DoesUserHaveClusterAccess(
+						requestlog.NewHandler(a.HandleListPodEvents, l),
+						mw.URLParam,
+						mw.QueryParam,
+					),
+					mw.URLParam,
+					mw.ReadAccess,
+				),
+			)
+
 			r.Method(
 				"POST",
 				"/projects/{project_id}/k8s/configmap/create",