Path: blob/main/components/ws-manager-mk2/pkg/proxy/imagebuilder.go
2500 views
// Copyright (c) 2022 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 proxy56import (7"context"8"errors"9"io"1011"github.com/gitpod-io/gitpod/common-go/log"12"github.com/gitpod-io/gitpod/image-builder/api"1314"google.golang.org/grpc/codes"15"google.golang.org/grpc/status"16"google.golang.org/protobuf/proto"17)1819type ImageBuilder struct {20D api.ImageBuilderClient2122api.UnimplementedImageBuilderServer23}2425func (p ImageBuilder) ResolveBaseImage(ctx context.Context, req *api.ResolveBaseImageRequest) (*api.ResolveBaseImageResponse, error) {26return p.D.ResolveBaseImage(ctx, req)27}2829func (p ImageBuilder) ResolveWorkspaceImage(ctx context.Context, req *api.ResolveWorkspaceImageRequest) (*api.ResolveWorkspaceImageResponse, error) {30return p.D.ResolveWorkspaceImage(ctx, req)31}3233func (p ImageBuilder) Build(req *api.BuildRequest, srv api.ImageBuilder_BuildServer) error {34c, err := p.D.Build(srv.Context(), req)35if err != nil {36return err37}38defer c.CloseSend()3940return forwardStream(srv.Context(), c.Recv, srv.Send)41}4243func (p ImageBuilder) Logs(req *api.LogsRequest, srv api.ImageBuilder_LogsServer) error {44c, err := p.D.Logs(srv.Context(), req)45if err != nil {46return err47}48defer c.CloseSend()4950return forwardStream(srv.Context(), c.Recv, srv.Send)51}5253func (p ImageBuilder) ListBuilds(ctx context.Context, req *api.ListBuildsRequest) (*api.ListBuildsResponse, error) {54return p.D.ListBuilds(ctx, req)55}5657type ProtoMessage interface {58proto.Message59comparable60}6162func forwardStream[R ProtoMessage](ctx context.Context, recv func() (R, error), send func(R) error) error {63for {64resp, err := recv()65if err != nil {66return handleProxyError(err)67}6869// generic hack, can't compare R to nil because R's default value is unclear (not even sure this is nil)70// But, we can get the default value which will be nil because underneath R is an interface.71var defaultResp R72if resp == defaultResp {73break74}75err = send(resp)76if err != nil {77return handleProxyError(err)78}79}8081return nil82}8384// handleProxyError ensures all errors have proper gRPC status codes85func handleProxyError(err error) error {86if err == nil {87return nil88}8990// If it's already a gRPC status error, check for DeadlineExceeded91if st, ok := status.FromError(err); ok {92if st.Code() == codes.DeadlineExceeded {93// Return nil (OK) for DeadlineExceeded as requested94return nil95}9697log.WithError(err).WithField("code", status.Code(err)).Error("unexpected error while sending stream response upstream")98return err99}100101// Handle context errors102if errors.Is(err, context.DeadlineExceeded) {103// Return nil (OK) for DeadlineExceeded104return nil105}106107if errors.Is(err, io.EOF) {108// Return nil (OK) for EOF, which is a normal when the client ends the stream109return nil110}111112log.WithError(err).Error("unexpected error while sending stream response upstream")113114if errors.Is(err, context.Canceled) {115return status.Error(codes.Canceled, err.Error())116}117118// Wrap any other error as Internal119return status.Error(codes.Internal, err.Error())120}121122123