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

Implemented get previous logs endpoint

jnfrati 4 лет назад
Родитель
Сommit
7d4bce05e0

+ 62 - 0
api/server/handlers/namespace/get_previous_logs.go

@@ -0,0 +1,62 @@
+package namespace
+
+import (
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/requestutils"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+type GetPreviousLogsHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewGetPreviousLogsHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *GetPreviousLogsHandler {
+	return &GetPreviousLogsHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *GetPreviousLogsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	request := &types.GetPreviousPodLogsRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	namespace := r.Context().Value(types.NamespaceScope).(string)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	name, _ := requestutils.GetURLParamString(r, types.URLParamPodName)
+
+	agent, err := c.GetAgent(r, cluster, "")
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	logs, err := agent.GetPreviousPodLogs(namespace, name, request.Container)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	var res types.GetPreviousPodLogsResponse = types.GetPreviousPodLogsResponse{
+		PrevLogs: logs,
+	}
+
+	c.WriteResult(w, r, res)
+}

+ 1 - 1
api/server/handlers/namespace/stream_pod_logs.go

@@ -53,7 +53,7 @@ func (c *StreamPodLogsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	err = agent.GetPodLogs(namespace, name, request.Previous, request.Container, safeRW)
+	err = agent.GetPodLogs(namespace, name, request.Container, safeRW)
 
 	if targetErr := kubernetes.IsNotFoundError; errors.Is(err, targetErr) {
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(

+ 34 - 0
api/server/router/namespace.go

@@ -328,6 +328,40 @@ func getNamespaceRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/pod/{name}/previous_logs
+	getPreviousLogsEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent: basePath,
+				RelativePath: fmt.Sprintf(
+					"%s/pod/{%s}/previous_logs",
+					relPath,
+					types.URLParamPodName,
+				),
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+			},
+		},
+	)
+
+	getPreviousLogsHandler := namespace.NewListReleasesHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: getPreviousLogsEndpoint,
+		Handler:  getPreviousLogsHandler,
+		Router:   r,
+	})
+
 	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/jobs/{name}/pods -> jobs.NewGetPodsHandler
 	getJobPodsEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 8 - 1
api/types/namespace.go

@@ -114,5 +114,12 @@ type DeleteConfigMapRequest struct {
 
 type GetPodLogsRequest struct {
 	Container string `schema:"container_name"`
-	Previous  bool   `schema:"previous"`
+}
+
+type GetPreviousPodLogsRequest struct {
+	Container string `schema:"container_name"`
+}
+
+type GetPreviousPodLogsResponse struct {
+	PrevLogs string `json:"previous_logs"`
 }

+ 74 - 2
internal/kubernetes/agent.go

@@ -554,7 +554,7 @@ func (a *Agent) DeletePod(namespace string, name string) error {
 }
 
 // GetPodLogs streams real-time logs from a given pod.
-func (a *Agent) GetPodLogs(namespace string, name string, showPreviousLogs bool, selectedContainer string, rw *websocket.WebsocketSafeReadWriter) error {
+func (a *Agent) GetPodLogs(namespace string, name string, selectedContainer string, rw *websocket.WebsocketSafeReadWriter) error {
 	// get the pod to read in the list of contains
 	pod, err := a.Clientset.CoreV1().Pods(namespace).Get(
 		context.Background(),
@@ -593,7 +593,6 @@ func (a *Agent) GetPodLogs(namespace string, name string, showPreviousLogs bool,
 		Follow:    true,
 		TailLines: &tails,
 		Container: container,
-		Previous:  showPreviousLogs,
 	}
 
 	req := a.Clientset.CoreV1().Pods(namespace).GetLogs(name, &podLogOpts)
@@ -656,6 +655,79 @@ func (a *Agent) GetPodLogs(namespace string, name string, showPreviousLogs bool,
 	}
 }
 
+// GetPodLogs streams real-time logs from a given pod.
+func (a *Agent) GetPreviousPodLogs(namespace string, name string, selectedContainer string) (string, error) {
+	// get the pod to read in the list of contains
+	pod, err := a.Clientset.CoreV1().Pods(namespace).Get(
+		context.Background(),
+		name,
+		metav1.GetOptions{},
+	)
+
+	if err != nil && errors.IsNotFound(err) {
+		return "", IsNotFoundError
+	} else if err != nil {
+		return "", fmt.Errorf("Cannot get logs from pod %s: %s", name, err.Error())
+	}
+
+	// see if container is ready and able to open a stream. If not, wait for container
+	// to be ready.
+	err, _ = a.waitForPod(pod)
+
+	if err != nil && goerrors.Is(err, IsNotFoundError) {
+		return "", IsNotFoundError
+	} else if err != nil {
+		return "", fmt.Errorf("Cannot get logs from pod %s: %s", name, err.Error())
+	}
+
+	container := pod.Spec.Containers[0].Name
+
+	if len(selectedContainer) > 0 {
+		container = selectedContainer
+	}
+
+	tails := int64(400)
+
+	// follow logs
+	podLogOpts := v1.PodLogOptions{
+		Follow:    true,
+		TailLines: &tails,
+		Container: container,
+		Previous:  true,
+	}
+
+	req := a.Clientset.CoreV1().Pods(namespace).GetLogs(name, &podLogOpts)
+
+	podLogs, err := req.Stream(context.TODO())
+
+	// in the case of bad request errors, such as if the pod is stuck in "ContainerCreating",
+	// we'd like to pass this through to the client.
+	if err != nil && errors.IsBadRequest(err) {
+		return "", &BadRequestError{err.Error()}
+	} else if err != nil {
+		return "", fmt.Errorf("Cannot open log stream for pod %s: %s", name, err.Error())
+	}
+
+	defer podLogs.Close()
+
+	r := bufio.NewReader(podLogs)
+	logs := ""
+
+	for {
+		line, err := r.ReadString('\n')
+		logs += line + "\n"
+
+		if err == io.EOF {
+			break
+		} else if err != nil {
+
+			return "", err
+		}
+	}
+
+	return logs, nil
+}
+
 // StopJobWithJobSidecar sends a termination signal to a job running with a sidecar
 func (a *Agent) StopJobWithJobSidecar(namespace, name string) error {
 	jobPods, err := a.GetJobPods(namespace, name)