Skip to content

Commit bbd1754

Browse files
committed
story(version up ) : main merge
2 parents 16ca05d + 95f7e2d commit bbd1754

File tree

15 files changed

+461
-204
lines changed

15 files changed

+461
-204
lines changed

api/k8s/application/k8sApplicationRestHandler.go

Lines changed: 127 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package application
22

33
import (
4+
"bufio"
5+
"bytes"
46
"context"
57
"encoding/json"
68
"errors"
79
"fmt"
810
"github.com/devtron-labs/common-lib/utils"
9-
"net/http"
10-
"strconv"
11-
"strings"
12-
1311
util3 "github.com/devtron-labs/common-lib/utils/k8s"
1412
k8sCommonBean "github.com/devtron-labs/common-lib/utils/k8s/commonBean"
1513
"github.com/devtron-labs/common-lib/utils/k8sObjectsUtil"
@@ -27,12 +25,19 @@ import (
2725
"github.com/devtron-labs/devtron/pkg/terminal"
2826
"github.com/devtron-labs/devtron/util"
2927
"github.com/devtron-labs/devtron/util/rbac"
28+
"github.com/google/uuid"
3029
"github.com/gorilla/mux"
3130
errors2 "github.com/juju/errors"
3231
"go.uber.org/zap"
3332
"gopkg.in/go-playground/validator.v9"
33+
"io"
3434
errors3 "k8s.io/apimachinery/pkg/api/errors"
3535
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36+
"net/http"
37+
"regexp"
38+
"strconv"
39+
"strings"
40+
"time"
3641
)
3742

3843
type K8sApplicationRestHandler interface {
@@ -42,6 +47,7 @@ type K8sApplicationRestHandler interface {
4247
DeleteResource(w http.ResponseWriter, r *http.Request)
4348
ListEvents(w http.ResponseWriter, r *http.Request)
4449
GetPodLogs(w http.ResponseWriter, r *http.Request)
50+
DownloadPodLogs(w http.ResponseWriter, r *http.Request)
4551
GetTerminalSession(w http.ResponseWriter, r *http.Request)
4652
GetResourceInfo(w http.ResponseWriter, r *http.Request)
4753
GetHostUrlsByBatch(w http.ResponseWriter, r *http.Request)
@@ -620,11 +626,127 @@ func (handler *K8sApplicationRestHandlerImpl) GetPodLogs(w http.ResponseWriter,
620626
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
621627
return
622628
}
629+
handler.requestValidationAndRBAC(w, r, token, request)
630+
lastEventId := r.Header.Get(bean2.LastEventID)
631+
isReconnect := false
632+
if len(lastEventId) > 0 {
633+
lastSeenMsgId, err := strconv.ParseInt(lastEventId, bean2.IntegerBase, bean2.IntegerBitSize)
634+
if err != nil {
635+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
636+
return
637+
}
638+
lastSeenMsgId = lastSeenMsgId + bean2.TimestampOffsetToAvoidDuplicateLogs //increased by one ns to avoid duplicate
639+
t := v1.Unix(0, lastSeenMsgId)
640+
request.K8sRequest.PodLogsRequest.SinceTime = &t
641+
isReconnect = true
642+
}
643+
stream, err := handler.k8sApplicationService.GetPodLogs(r.Context(), request)
644+
//err is handled inside StartK8sStreamWithHeartBeat method
645+
ctx, cancel := context.WithCancel(r.Context())
646+
if cn, ok := w.(http.CloseNotifier); ok {
647+
go func(done <-chan struct{}, closed <-chan bool) {
648+
select {
649+
case <-done:
650+
case <-closed:
651+
cancel()
652+
}
653+
}(ctx.Done(), cn.CloseNotify())
654+
}
655+
defer cancel()
656+
defer util.Close(stream, handler.logger)
657+
handler.pump.StartK8sStreamWithHeartBeat(w, isReconnect, stream, err)
658+
}
659+
660+
func (handler *K8sApplicationRestHandlerImpl) DownloadPodLogs(w http.ResponseWriter, r *http.Request) {
661+
token := r.Header.Get("token")
662+
request, err := handler.k8sApplicationService.ValidatePodLogsRequestQuery(r)
663+
if err != nil {
664+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
665+
return
666+
}
667+
handler.requestValidationAndRBAC(w, r, token, request)
668+
669+
// just to make sure follow flag is set to false when downloading logs
670+
request.K8sRequest.PodLogsRequest.Follow = false
671+
672+
stream, err := handler.k8sApplicationService.GetPodLogs(r.Context(), request)
673+
if err != nil {
674+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
675+
return
676+
}
677+
ctx, cancel := context.WithCancel(r.Context())
678+
if cn, ok := w.(http.CloseNotifier); ok {
679+
go func(done <-chan struct{}, closed <-chan bool) {
680+
select {
681+
case <-done:
682+
case <-closed:
683+
cancel()
684+
}
685+
}(ctx.Done(), cn.CloseNotify())
686+
}
687+
defer cancel()
688+
defer util.Close(stream, handler.logger)
689+
690+
var dataBuffer bytes.Buffer
691+
bufReader := bufio.NewReader(stream)
692+
eof := false
693+
for !eof {
694+
log, err := bufReader.ReadString('\n')
695+
log = strings.TrimSpace(log) // Remove trailing line ending
696+
a := regexp.MustCompile(" ")
697+
var res []byte
698+
splitLog := a.Split(log, 2)
699+
if len(splitLog[0]) > 0 {
700+
parsedTime, err := time.Parse(time.RFC3339, splitLog[0])
701+
if err != nil {
702+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
703+
return
704+
}
705+
gmtTimeLoc := time.FixedZone(bean2.LocalTimezoneInGMT, bean2.LocalTimeOffset)
706+
humanReadableTime := parsedTime.In(gmtTimeLoc).Format(time.RFC1123)
707+
res = append(res, humanReadableTime...)
708+
}
709+
710+
if len(splitLog) == 2 {
711+
res = append(res, " "...)
712+
res = append(res, splitLog[1]...)
713+
}
714+
res = append(res, "\n"...)
715+
if err == io.EOF {
716+
eof = true
717+
// stop if we reached end of stream and the next line is empty
718+
if log == "" {
719+
break
720+
}
721+
} else if err != nil && err != io.EOF {
722+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
723+
return
724+
}
725+
_, err = dataBuffer.Write(res)
726+
if err != nil {
727+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
728+
return
729+
}
730+
}
731+
if len(dataBuffer.Bytes()) == 0 {
732+
common.WriteJsonResp(w, nil, nil, http.StatusNoContent)
733+
return
734+
}
735+
podLogsFilename := generatePodLogsFilename(request.K8sRequest.ResourceIdentifier.Name)
736+
common.WriteOctetStreamResp(w, r, dataBuffer.Bytes(), podLogsFilename)
737+
return
738+
}
739+
740+
func generatePodLogsFilename(filename string) string {
741+
return fmt.Sprintf("podlogs-%s-%s.log", filename, uuid.New().String())
742+
}
743+
744+
func (handler *K8sApplicationRestHandlerImpl) requestValidationAndRBAC(w http.ResponseWriter, r *http.Request, token string, request *k8s.ResourceRequestBean) {
623745
if request.AppIdentifier != nil {
624746
if request.DeploymentType == bean2.HelmInstalledType {
625747
valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
626748
if err != nil || !valid {
627-
handler.logger.Errorw("error in validating resource request", "err", err)
749+
handler.logger.Errorw("error in validating resource request", "err", err, "request.AppIdentifier", request.AppIdentifier, "request.K8sRequest", request.K8sRequest)
628750
apiError := util2.ApiError{
629751
InternalMessage: "failed to validate the resource with error " + err.Error(),
630752
UserMessage: "Failed to validate resource",
@@ -671,34 +793,6 @@ func (handler *K8sApplicationRestHandlerImpl) GetPodLogs(w http.ResponseWriter,
671793
common.WriteJsonResp(w, errors.New("can not get pod logs as target cluster is not provided"), nil, http.StatusBadRequest)
672794
return
673795
}
674-
lastEventId := r.Header.Get("Last-Event-ID")
675-
isReconnect := false
676-
if len(lastEventId) > 0 {
677-
lastSeenMsgId, err := strconv.ParseInt(lastEventId, 10, 64)
678-
if err != nil {
679-
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
680-
return
681-
}
682-
lastSeenMsgId = lastSeenMsgId + 1 //increased by one ns to avoid duplicate
683-
t := v1.Unix(0, lastSeenMsgId)
684-
request.K8sRequest.PodLogsRequest.SinceTime = &t
685-
isReconnect = true
686-
}
687-
stream, err := handler.k8sApplicationService.GetPodLogs(r.Context(), request)
688-
//err is handled inside StartK8sStreamWithHeartBeat method
689-
ctx, cancel := context.WithCancel(r.Context())
690-
if cn, ok := w.(http.CloseNotifier); ok {
691-
go func(done <-chan struct{}, closed <-chan bool) {
692-
select {
693-
case <-done:
694-
case <-closed:
695-
cancel()
696-
}
697-
}(ctx.Done(), cn.CloseNotify())
698-
}
699-
defer cancel()
700-
defer util.Close(stream, handler.logger)
701-
handler.pump.StartK8sStreamWithHeartBeat(w, isReconnect, stream, err)
702796
}
703797

704798
func (handler *K8sApplicationRestHandlerImpl) GetTerminalSession(w http.ResponseWriter, r *http.Request) {

api/k8s/application/k8sApplicationRouter.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ func (impl *K8sApplicationRouterImpl) InitK8sApplicationRouter(k8sAppRouter *mux
4242

4343
k8sAppRouter.Path("/pods/logs/{podName}").
4444
Queries("containerName", "{containerName}").
45-
//Queries("containerName", "{containerName}", "appId", "{appId}").
46-
//Queries("clusterId", "{clusterId}", "namespace", "${namespace}").
47-
//Queries("sinceSeconds", "{sinceSeconds}").
4845
Queries("follow", "{follow}").
49-
Queries("tailLines", "{tailLines}").
5046
HandlerFunc(impl.k8sApplicationRestHandler.GetPodLogs).Methods("GET")
5147

48+
k8sAppRouter.Path("/pods/logs/download/{podName}").
49+
Queries("containerName", "{containerName}").
50+
HandlerFunc(impl.k8sApplicationRestHandler.DownloadPodLogs).Methods("GET")
51+
5252
k8sAppRouter.Path("/pod/exec/session/{identifier}/{namespace}/{pod}/{shell}/{container}").
5353
HandlerFunc(impl.k8sApplicationRestHandler.GetTerminalSession).Methods("GET")
5454
k8sAppRouter.PathPrefix("/pod/exec/sockjs/ws").Handler(terminal.CreateAttachHandler("/pod/exec/sockjs/ws"))

api/restHandler/common/ApiResponseWriter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ func WriteApiJsonResponseStructured(w http.ResponseWriter, apiResponse *ApiRespo
5252
}
5353

5454
func WriteOctetStreamResp(w http.ResponseWriter, r *http.Request, byteArr []byte, defaultFilename string) {
55-
w.WriteHeader(http.StatusOK)
55+
w.Header().Set(CONTENT_TYPE, "application/octet-stream")
5656
if defaultFilename != "" {
5757
w.Header().Set(CONTENT_DISPOSITION, "attachment; filename="+defaultFilename)
5858
}
59-
w.Header().Set(CONTENT_TYPE, "application/octet-stream")
6059
w.Header().Set(CONTENT_LENGTH, r.Header.Get(CONTENT_LENGTH))
60+
w.WriteHeader(http.StatusOK)
6161
w.Write(byteArr)
6262
}

docs/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@
107107
* [Deploying MongoDB Helm Chart](user-guide/deploy-chart/examples/deploying-mongodb-helm-chart.md)
108108
* [Chart Group](user-guide/deploy-chart/chart-group.md)
109109
* [Security](user-guide/security-features.md)
110+
* [Security Scans](user-guide/security-features/security-scans.md)
111+
* [Security Policies](user-guide/security-features/security-policies.md)
110112
* [Clusters](user-guide/clusters.md)
111113
* [Bulk Edit](user-guide/bulk-update.md)
112114
* [Integrations](user-guide/integrations/README.md)

0 commit comments

Comments
 (0)