go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/rpcs/tasks_get_stdout.go (about) 1 // Copyright 2024 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package rpcs 16 17 import ( 18 "context" 19 20 "google.golang.org/grpc/codes" 21 "google.golang.org/grpc/status" 22 23 "go.chromium.org/luci/common/errors" 24 "go.chromium.org/luci/common/logging" 25 "go.chromium.org/luci/gae/service/datastore" 26 27 apipb "go.chromium.org/luci/swarming/proto/api_v2" 28 "go.chromium.org/luci/swarming/server/acls" 29 "go.chromium.org/luci/swarming/server/model" 30 ) 31 32 const ( 33 // Default byte offset to start fetching. 34 DefaultOffset = 0 35 36 // Maximum content fetched at once, mostly for compatibility with previous 37 // behavior. See TaskResult.ChunkSize. 38 MaxOutputLength = 16 * 1000 * 1024 39 ) 40 41 // GetStdout implements the GetStdout RPC. 42 func (*TasksServer) GetStdout(ctx context.Context, req *apipb.TaskIdWithOffsetRequest) (*apipb.TaskOutputResponse, error) { 43 if req.TaskId == "" { 44 return nil, status.Errorf(codes.InvalidArgument, "task_id is required") 45 } 46 trKey, err := model.TaskIDToRequestKey(ctx, req.TaskId) 47 if err != nil { 48 return nil, status.Errorf(codes.InvalidArgument, "task_id %s: %s", req.TaskId, err) 49 } 50 trs := &model.TaskResultSummary{Key: model.TaskResultSummaryKey(ctx, trKey)} 51 err = datastore.Get(ctx, trs) 52 switch { 53 case errors.Is(err, datastore.ErrNoSuchEntity): 54 return nil, status.Errorf(codes.NotFound, "no such task") 55 case err != nil: 56 logging.Errorf(ctx, "Error fetching TaskResultSummary %s: %s", req.TaskId, err) 57 return nil, status.Errorf(codes.Internal, "datastore error fetching the task") 58 } 59 res := State(ctx).ACL.CheckTaskPerm(ctx, trs.TaskAuthInfo(), acls.PermTasksGet) 60 if !res.Permitted { 61 return nil, res.ToGrpcErr() 62 } 63 length := req.GetLength() 64 if length <= 0 || length > MaxOutputLength { 65 length = MaxOutputLength 66 } 67 offset := req.GetOffset() 68 if offset < 0 { 69 offset = DefaultOffset 70 } 71 output, err := trs.GetOutput(ctx, length, offset) 72 if err != nil { 73 logging.Errorf(ctx, "Error getting task result output: %s", err.Error()) 74 return nil, status.Errorf(codes.Internal, "error getting task result output") 75 } 76 return &apipb.TaskOutputResponse{ 77 Output: output, 78 State: trs.State, 79 }, nil 80 }