From 798cd228dfb17b3234ac6982579a2f8598ce0f02 Mon Sep 17 00:00:00 2001 From: Robert Choi Date: Fri, 17 May 2024 14:01:01 +0900 Subject: [PATCH 1/4] app-serving: collect app pod log --- internal/delivery/api/endpoint.go | 1 + internal/delivery/http/app-serve-app.go | 66 +++++++++++++++++++++++++ internal/repository/app-serve-app.go | 22 +++++++-- internal/route/route.go | 1 + internal/usecase/app-serve-app.go | 65 ++++++++++++++++++++++++ pkg/domain/app-serve-app.go | 5 ++ 6 files changed, 157 insertions(+), 3 deletions(-) diff --git a/internal/delivery/api/endpoint.go b/internal/delivery/api/endpoint.go index c847c189..3de1e793 100644 --- a/internal/delivery/api/endpoint.go +++ b/internal/delivery/api/endpoint.go @@ -76,6 +76,7 @@ const ( GetNumOfAppsOnStack // 프로젝트 관리/앱 서빙/조회 GetAppServeApp // 프로젝트 관리/앱 서빙/조회 GetAppServeAppLatestTask // 프로젝트 관리/앱 서빙/조회 + GetAppServeAppLog // 프로젝트 관리/앱 서빙/조회 IsAppServeAppExist // 프로젝트 관리/앱 서빙/조회 // 프로젝트 관리/앱 서빙/배포 // 프로젝트 관리/앱 서빙/빌드 IsAppServeAppNameExist // 프로젝트 관리/앱 서빙/조회 // 프로젝트 관리/앱 서빙/배포 // 프로젝트 관리/앱 서빙/빌드 DeleteAppServeApp // 프로젝트 관리/앱 서빙/삭제 diff --git a/internal/delivery/http/app-serve-app.go b/internal/delivery/http/app-serve-app.go index 5a76487e..bfc3390d 100644 --- a/internal/delivery/http/app-serve-app.go +++ b/internal/delivery/http/app-serve-app.go @@ -422,6 +422,72 @@ func (h *AppServeAppHandler) GetNumOfAppsOnStack(w http.ResponseWriter, r *http. ResponseJSON(w, r, http.StatusOK, numApps) } +// GetAppServeAppLog godoc +// +// @Tags AppServeApps +// @Summary Get log and pod status of appServeApp +// @Description Get log and pod status of appServeApp +// @Accept json +// @Produce json +// @Param organizationId path string true "Organization ID" +// @Param projectId path string true "Project ID" +// @Param appId path string true "App ID" +// @Success 200 {object} domain.GetAppServeAppLogResponse +// @Router /organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/log [get] +// @Security JWT +func (h *AppServeAppHandler) GetAppServeAppLog(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + organizationId, ok := vars["organizationId"] + fmt.Printf("organizationId = [%v]\n", organizationId) + if !ok { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("invalid organizationId"), "", "")) + return + } + + projectId, ok := vars["projectId"] + log.Debugf(r.Context(), "projectId = [%v]\n", projectId) + if !ok { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("Invalid projectId: [%s]", projectId), "C_INVALID_PROJECT_ID", "")) + return + } + + appId, ok := vars["appId"] + fmt.Printf("appId = [%s]\n", appId) + if !ok { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("invalid appId"), "", "")) + return + } + + // Check if projectId exists + prj, err := h.prjUsecase.GetProject(r.Context(), organizationId, projectId) + if err != nil { + ErrorJSON(w, r, httpErrors.NewInternalServerError(fmt.Errorf("Error while checking project record: %s", err), "", "")) + return + } else if prj == nil { + ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("projectId not found: %s", projectId), "C_INVALID_PROJECT_ID", "")) + } + + podLog, podStatus, err := h.usecase.GetAppServeAppLog(r.Context(), appId) + if err != nil { + ErrorJSON(w, r, httpErrors.NewInternalServerError(err, "", "")) + return + } + + var out domain.GetAppServeAppLogResponse + // if err := serializer.Map(r.Context(), podLog, &out.Log); err != nil { + // log.Info(r.Context(), err) + // } + out.Log = podLog + + // if err := serializer.Map(r.Context(), podStatus, &out.PodStatus); err != nil { + // log.Info(r.Context(), err) + // } + out.PodStatus = podStatus + + ResponseJSON(w, r, http.StatusOK, out) +} + // GetAppServeAppTasksByAppId godoc // // @Tags AppServeApps diff --git a/internal/repository/app-serve-app.go b/internal/repository/app-serve-app.go index 2267fd94..dc9fba13 100644 --- a/internal/repository/app-serve-app.go +++ b/internal/repository/app-serve-app.go @@ -21,6 +21,7 @@ type IAppServeAppRepository interface { GetAppServeAppTasksByAppId(ctx context.Context, appId string, pg *pagination.Pagination) ([]model.AppServeAppTask, error) GetAppServeAppTaskById(ctx context.Context, taskId string) (*model.AppServeAppTask, error) GetAppServeAppLatestTask(ctx context.Context, appId string) (*model.AppServeAppTask, error) + GetClusterIdByAppId(ctx context.Context, appId string) (string, error) GetNumOfAppsOnStack(ctx context.Context, organizationId string, clusterId string) (int64, error) @@ -55,9 +56,9 @@ func (r *AppServeAppRepository) CreateAppServeApp(ctx context.Context, app *mode // Update creates new appServeApp task for existing appServeApp. func (r *AppServeAppRepository) CreateTask(ctx context.Context, task *model.AppServeAppTask, appId string) (string, error) { task.ID = uuid.New().String() - if len(appId) > 0 { - task.AppServeAppId = appId - } + if len(appId) > 0 { + task.AppServeAppId = appId + } res := r.db.WithContext(ctx).Create(task) if res.Error != nil { return "", res.Error @@ -174,6 +175,21 @@ func (r *AppServeAppRepository) GetAppServeAppLatestTask(ctx context.Context, ap return &task, nil } +func (r *AppServeAppRepository) GetClusterIdByAppId(ctx context.Context, appId string) (string, error) { + var app model.AppServeApp + + res := r.db.WithContext(ctx).Where("id = ?", appId).First(&app) + if res.Error != nil { + log.Debug(ctx, res.Error) + return "", res.Error + } + if res.RowsAffected == 0 { + return "", fmt.Errorf("No app with ID %s", appId) + } + + return app.TargetClusterId, nil +} + func (r *AppServeAppRepository) GetNumOfAppsOnStack(ctx context.Context, organizationId string, clusterId string) (int64, error) { var apps []model.AppServeApp diff --git a/internal/route/route.go b/internal/route/route.go index e9edbf12..f8dd46a1 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -166,6 +166,7 @@ func SetupRouter(db *gorm.DB, argoClient argowf.ArgoClient, kc keycloak.IKeycloa r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/tasks", customMiddleware.Handle(internalApi.GetAppServeAppTasksByAppId, http.HandlerFunc(appServeAppHandler.GetAppServeAppTasksByAppId))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/tasks/{taskId}", customMiddleware.Handle(internalApi.GetAppServeAppTaskDetail, http.HandlerFunc(appServeAppHandler.GetAppServeAppTaskDetail))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/latest-task", customMiddleware.Handle(internalApi.GetAppServeAppLatestTask, http.HandlerFunc(appServeAppHandler.GetAppServeAppLatestTask))).Methods(http.MethodGet) + r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/log", customMiddleware.Handle(internalApi.GetAppServeAppLog, http.HandlerFunc(appServeAppHandler.GetAppServeAppLog))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/exist", customMiddleware.Handle(internalApi.IsAppServeAppExist, http.HandlerFunc(appServeAppHandler.IsAppServeAppExist))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/app-serve-apps/name/{name}/existence", customMiddleware.Handle(internalApi.IsAppServeAppNameExist, http.HandlerFunc(appServeAppHandler.IsAppServeAppNameExist))).Methods(http.MethodGet) r.Handle(API_PREFIX+API_VERSION+"/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}", customMiddleware.Handle(internalApi.DeleteAppServeApp, http.HandlerFunc(appServeAppHandler.DeleteAppServeApp))).Methods(http.MethodDelete) diff --git a/internal/usecase/app-serve-app.go b/internal/usecase/app-serve-app.go index 033f3aae..93a74175 100644 --- a/internal/usecase/app-serve-app.go +++ b/internal/usecase/app-serve-app.go @@ -1,9 +1,11 @@ package usecase import ( + "bytes" "context" "encoding/json" "fmt" + "io" "strconv" "strings" @@ -11,6 +13,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/viper" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/openinfradev/tks-api/internal/model" @@ -27,6 +30,7 @@ type IAppServeAppUsecase interface { CreateAppServeApp(ctx context.Context, app *model.AppServeApp, task *model.AppServeAppTask) (appId string, taskId string, err error) GetAppServeApps(ctx context.Context, organizationId string, projectId string, showAll bool, pg *pagination.Pagination) ([]model.AppServeApp, error) GetAppServeAppById(ctx context.Context, appId string) (*model.AppServeApp, error) + GetAppServeAppLog(ctx context.Context, appId string) (string, string, error) GetAppServeAppTasks(ctx context.Context, appId string, pg *pagination.Pagination) ([]model.AppServeAppTask, error) GetAppServeAppTaskById(ctx context.Context, taskId string) (*model.AppServeAppTask, error) GetAppServeAppLatestTask(ctx context.Context, appId string) (*model.AppServeAppTask, error) @@ -233,6 +237,67 @@ func (u *AppServeAppUsecase) GetAppServeAppById(ctx context.Context, appId strin return asa, nil } +func (u *AppServeAppUsecase) GetAppServeAppLog(ctx context.Context, appId string) (string, string, error) { + var logStr string + var podStatus string + + app, err := u.repo.GetAppServeAppById(ctx, appId) + if err != nil { + return "", "", fmt.Errorf("error while getting ASA Info from DB. Err: %s", err) + } + if app == nil { + return "", "", httpErrors.NewNoContentError(fmt.Errorf("the appId doesn't exist"), "", "") + } + + clientset, err := kubernetes.GetClientFromClusterId(ctx, app.TargetClusterId) + if err != nil { + log.Error(ctx, err) + return "", "", err + } + + // Reference: https://github.com/nwaizer/GetPodLogsEfficiently/blob/main/cmd/basicgetlogs/basic.go + + labelStr := fmt.Sprintf("app=%s", app.Name) + pods, err := clientset.CoreV1().Pods(app.Namespace).List(context.TODO(), metav1.ListOptions{ + LabelSelector: labelStr}) + if err != nil { + log.Error(ctx, err) + return "", "", err + } + + for _, pod := range pods.Items { + log.Debugf(ctx, "Processing pod: %s", pod.Name) + + tailLines := int64(50) + + req := clientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &corev1.PodLogOptions{ + // name should be "tomcat" for legacy spring app + Container: "main", + TailLines: &tailLines, + }) + + podLogs, err := req.Stream(context.TODO()) + if err != nil { + return "", "", fmt.Errorf("Failed to open pod logs due to: %w", err) + } + defer podLogs.Close() + + buf := new(bytes.Buffer) + _, err = io.Copy(buf, podLogs) + + if err != nil { + return "", "", fmt.Errorf("Failed to decode logs binary input due to: %w", err) + } + + logStr = "`" + buf.String() + "`" + + podStatus = fmt.Sprintf("%s", pod.Status.Phase) + log.Debugf(ctx, "Pod status: %s", pod.Status.Phase) + } + + return logStr, podStatus, nil +} + func (u *AppServeAppUsecase) GetAppServeAppTasks(ctx context.Context, appId string, pg *pagination.Pagination) ([]model.AppServeAppTask, error) { tasks, err := u.repo.GetAppServeAppTasksByAppId(ctx, appId, pg) if err != nil { diff --git a/pkg/domain/app-serve-app.go b/pkg/domain/app-serve-app.go index 757b877a..f7b747ec 100644 --- a/pkg/domain/app-serve-app.go +++ b/pkg/domain/app-serve-app.go @@ -158,6 +158,11 @@ type GetAppServeAppTaskResponse struct { Stages []StageResponse `json:"stages"` } +type GetAppServeAppLogResponse struct { + Log string `json:"log"` + PodStatus string `json:"podStatus"` +} + type StageResponse struct { Name string `json:"name"` // BUILD (빌드), DEPLOY (배포), PROMOTE (프로모트), ROLLBACK (롤백) Status string `json:"status"` From 42780ba9c84141775c045aedd92cfcb9c8a67fdb Mon Sep 17 00:00:00 2001 From: Robert Choi Date: Mon, 20 May 2024 14:13:15 +0900 Subject: [PATCH 2/4] update swagger docs --- api/swagger/docs.go | 62 ++++++++++++++++++++++++++++++++++++++++ api/swagger/swagger.json | 62 ++++++++++++++++++++++++++++++++++++++++ api/swagger/swagger.yaml | 40 ++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) diff --git a/api/swagger/docs.go b/api/swagger/docs.go index 42716929..8bccf48a 100644 --- a/api/swagger/docs.go +++ b/api/swagger/docs.go @@ -6312,6 +6312,57 @@ const docTemplate = `{ } } }, + "/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/log": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get log and pod status of appServeApp", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AppServeApps" + ], + "summary": "Get log and pod status of appServeApp", + "parameters": [ + { + "type": "string", + "description": "Organization ID", + "name": "organizationId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Project ID", + "name": "projectId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "App ID", + "name": "appId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse" + } + } + } + } + }, "/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/rollback": { "post": { "security": [ @@ -12579,6 +12630,17 @@ const docTemplate = `{ } } }, + "github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse": { + "type": "object", + "properties": { + "log": { + "type": "string" + }, + "podStatus": { + "type": "string" + } + } + }, "github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppTaskResponse": { "type": "object", "properties": { diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index f2faa9d1..67edf19f 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -6306,6 +6306,57 @@ } } }, + "/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/log": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get log and pod status of appServeApp", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "AppServeApps" + ], + "summary": "Get log and pod status of appServeApp", + "parameters": [ + { + "type": "string", + "description": "Organization ID", + "name": "organizationId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Project ID", + "name": "projectId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "App ID", + "name": "appId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse" + } + } + } + } + }, "/organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/rollback": { "post": { "security": [ @@ -12573,6 +12624,17 @@ } } }, + "github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse": { + "type": "object", + "properties": { + "log": { + "type": "string" + }, + "podStatus": { + "type": "string" + } + } + }, "github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppTaskResponse": { "type": "object", "properties": { diff --git a/api/swagger/swagger.yaml b/api/swagger/swagger.yaml index fe6f2961..82c2e66f 100644 --- a/api/swagger/swagger.yaml +++ b/api/swagger/swagger.yaml @@ -1535,6 +1535,13 @@ definitions: pagination: $ref: '#/definitions/github_com_openinfradev_tks-api_pkg_domain.PaginationResponse' type: object + github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse: + properties: + log: + type: string + podStatus: + type: string + type: object github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppTaskResponse: properties: appServeApp: @@ -8187,6 +8194,39 @@ paths: summary: Get latest task from appServeApp tags: - AppServeApps + /organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/log: + get: + consumes: + - application/json + description: Get log and pod status of appServeApp + parameters: + - description: Organization ID + in: path + name: organizationId + required: true + type: string + - description: Project ID + in: path + name: projectId + required: true + type: string + - description: App ID + in: path + name: appId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse' + security: + - JWT: [] + summary: Get log and pod status of appServeApp + tags: + - AppServeApps /organizations/{organizationId}/projects/{projectId}/app-serve-apps/{appId}/rollback: post: consumes: From eb1f63951573afb4745f45a418825401454cb9fa Mon Sep 17 00:00:00 2001 From: Robert Choi Date: Mon, 20 May 2024 14:17:59 +0900 Subject: [PATCH 3/4] trivial: rename log to podLog --- api/swagger/docs.go | 2 +- api/swagger/swagger.json | 2 +- api/swagger/swagger.yaml | 2 +- internal/delivery/http/app-serve-app.go | 4 ++-- pkg/domain/app-serve-app.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/swagger/docs.go b/api/swagger/docs.go index 8bccf48a..a3ba31bf 100644 --- a/api/swagger/docs.go +++ b/api/swagger/docs.go @@ -12633,7 +12633,7 @@ const docTemplate = `{ "github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse": { "type": "object", "properties": { - "log": { + "podLog": { "type": "string" }, "podStatus": { diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index 67edf19f..c66c556a 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -12627,7 +12627,7 @@ "github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse": { "type": "object", "properties": { - "log": { + "podLog": { "type": "string" }, "podStatus": { diff --git a/api/swagger/swagger.yaml b/api/swagger/swagger.yaml index 82c2e66f..b48f4c49 100644 --- a/api/swagger/swagger.yaml +++ b/api/swagger/swagger.yaml @@ -1537,7 +1537,7 @@ definitions: type: object github_com_openinfradev_tks-api_pkg_domain.GetAppServeAppLogResponse: properties: - log: + podLog: type: string podStatus: type: string diff --git a/internal/delivery/http/app-serve-app.go b/internal/delivery/http/app-serve-app.go index bfc3390d..602b098e 100644 --- a/internal/delivery/http/app-serve-app.go +++ b/internal/delivery/http/app-serve-app.go @@ -475,10 +475,10 @@ func (h *AppServeAppHandler) GetAppServeAppLog(w http.ResponseWriter, r *http.Re } var out domain.GetAppServeAppLogResponse - // if err := serializer.Map(r.Context(), podLog, &out.Log); err != nil { + // if err := serializer.Map(r.Context(), podLog, &out.PodLog); err != nil { // log.Info(r.Context(), err) // } - out.Log = podLog + out.PodLog = podLog // if err := serializer.Map(r.Context(), podStatus, &out.PodStatus); err != nil { // log.Info(r.Context(), err) diff --git a/pkg/domain/app-serve-app.go b/pkg/domain/app-serve-app.go index f7b747ec..f6bb1048 100644 --- a/pkg/domain/app-serve-app.go +++ b/pkg/domain/app-serve-app.go @@ -159,7 +159,7 @@ type GetAppServeAppTaskResponse struct { } type GetAppServeAppLogResponse struct { - Log string `json:"log"` + PodLog string `json:"podLog"` PodStatus string `json:"podStatus"` } From 29573116ff7462dc6a5b0419b13ab0291306b53e Mon Sep 17 00:00:00 2001 From: Robert Choi Date: Mon, 20 May 2024 14:32:32 +0900 Subject: [PATCH 4/4] trivial: fix lint error --- internal/usecase/app-serve-app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/usecase/app-serve-app.go b/internal/usecase/app-serve-app.go index 93a74175..c59b71eb 100644 --- a/internal/usecase/app-serve-app.go +++ b/internal/usecase/app-serve-app.go @@ -291,8 +291,8 @@ func (u *AppServeAppUsecase) GetAppServeAppLog(ctx context.Context, appId string logStr = "`" + buf.String() + "`" - podStatus = fmt.Sprintf("%s", pod.Status.Phase) - log.Debugf(ctx, "Pod status: %s", pod.Status.Phase) + podStatus = string(pod.Status.Phase) + log.Debugf(ctx, "Pod status: %s", podStatus) } return logStr, podStatus, nil