Path: blob/main/components/content-service/pkg/service/headless-log-service.go
2499 views
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package service56import (7"context"8"strings"910"github.com/opentracing/opentracing-go"11"golang.org/x/xerrors"12"google.golang.org/grpc/codes"13"google.golang.org/grpc/status"1415"github.com/gitpod-io/gitpod/common-go/log"16"github.com/gitpod-io/gitpod/common-go/tracing"17"github.com/gitpod-io/gitpod/content-service/api"18"github.com/gitpod-io/gitpod/content-service/api/config"19"github.com/gitpod-io/gitpod/content-service/pkg/logs"20"github.com/gitpod-io/gitpod/content-service/pkg/storage"21)2223// HeadlessLogService implements LogServiceServer24type HeadlessLogService struct {25cfg config.StorageConfig26s storage.PresignedAccess27daFactory func(cfg *config.StorageConfig) (storage.DirectAccess, error)2829api.UnimplementedHeadlessLogServiceServer30}3132// NewHeadlessLogService create a new content service33func NewHeadlessLogService(cfg config.StorageConfig) (res *HeadlessLogService, err error) {34s, err := storage.NewPresignedAccess(&cfg)35if err != nil {36return nil, err37}38daFactory := func(cfg *config.StorageConfig) (storage.DirectAccess, error) {39return storage.NewDirectAccess(cfg)40}41return &HeadlessLogService{42cfg: cfg,43s: s,44daFactory: daFactory,45}, nil46}4748// LogDownloadURL provides a URL from where the content of a workspace log stream can be downloaded from49func (ls *HeadlessLogService) LogDownloadURL(ctx context.Context, req *api.LogDownloadURLRequest) (resp *api.LogDownloadURLResponse, err error) {50span, ctx := opentracing.StartSpanFromContext(ctx, "WorkspaceDownloadURL")51span.SetTag("user", req.OwnerId)52span.SetTag("workspaceId", req.WorkspaceId)53span.SetTag("instanceId", req.InstanceId)54defer tracing.FinishSpan(span, &err)5556blobName := ls.s.InstanceObject(req.OwnerId, req.WorkspaceId, req.InstanceId, logs.UploadedHeadlessLogPath(req.TaskId))57info, err := ls.s.SignDownload(ctx, ls.s.Bucket(req.OwnerId), blobName, &storage.SignedURLOptions{})58if err != nil {59log.WithFields(log.OWI(req.OwnerId, req.WorkspaceId, "")).60WithField("bucket", ls.s.Bucket(req.OwnerId)).61WithField("blobName", blobName).62WithError(err).63Error("error getting SignDownload URL")64if err == storage.ErrNotFound {65return nil, status.Error(codes.NotFound, err.Error())66}67return nil, status.Error(codes.Unknown, err.Error())68}6970return &api.LogDownloadURLResponse{71Url: info.URL,72}, nil73}7475// ListLogs returns a list of taskIds for the specified workspace instance76func (ls *HeadlessLogService) ListLogs(ctx context.Context, req *api.ListLogsRequest) (resp *api.ListLogsResponse, err error) {77da, err := ls.daFactory(&ls.cfg)78if err != nil {79return nil, xerrors.Errorf("cannot use configured storage: %w", err)80}8182err = da.Init(ctx, req.OwnerId, req.WorkspaceId, req.InstanceId)83if err != nil {84return nil, xerrors.Errorf("cannot use configured storage: %w", err)85}86// we do not need to check whether the bucket exists because ListObjects() does that for us8788// all files under this prefix are headless log files, named after their respective taskId89prefix := ls.s.InstanceObject(req.OwnerId, req.WorkspaceId, req.InstanceId, logs.UploadedHeadlessLogPathPrefix)90objects, err := da.ListObjects(ctx, prefix)91if err != nil {92return nil, err93}9495var taskIds []string96for _, obj := range objects {97ss := strings.Split(obj, "/")98taskId := ss[len(ss)-1]99taskIds = append(taskIds, taskId)100}101102return &api.ListLogsResponse{103TaskId: taskIds,104}, nil105}106107108