github.com/kubeshop/testkube@v1.17.23/pkg/tcl/cloudtcl/data/testworkflow/output.go (about) 1 // Copyright 2024 Testkube. 2 // 3 // Licensed as a Testkube Pro file under the Testkube Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt 8 9 package testworkflow 10 11 import ( 12 "bytes" 13 "context" 14 "io" 15 "net/http" 16 17 "github.com/kubeshop/testkube/pkg/tcl/repositorytcl/testworkflow" 18 19 "github.com/pkg/errors" 20 "google.golang.org/grpc" 21 22 "github.com/kubeshop/testkube/pkg/cloud" 23 "github.com/kubeshop/testkube/pkg/cloud/data/executor" 24 ) 25 26 var _ testworkflow.OutputRepository = (*CloudOutputRepository)(nil) 27 28 type CloudOutputRepository struct { 29 executor executor.Executor 30 } 31 32 func NewCloudOutputRepository(client cloud.TestKubeCloudAPIClient, grpcConn *grpc.ClientConn, apiKey string) *CloudOutputRepository { 33 return &CloudOutputRepository{executor: executor.NewCloudGRPCExecutor(client, grpcConn, apiKey)} 34 } 35 36 // PresignSaveLog builds presigned storage URL to save the output in Cloud 37 func (r *CloudOutputRepository) PresignSaveLog(ctx context.Context, id, workflowName string) (string, error) { 38 req := OutputPresignSaveLogRequest{ID: id, WorkflowName: workflowName} 39 process := func(v OutputPresignSaveLogResponse) string { 40 return v.URL 41 } 42 return pass(r.executor, ctx, req, process) 43 } 44 45 // PresignReadLog builds presigned storage URL to read the output from Cloud 46 func (r *CloudOutputRepository) PresignReadLog(ctx context.Context, id, workflowName string) (string, error) { 47 req := OutputPresignReadLogRequest{ID: id, WorkflowName: workflowName} 48 process := func(v OutputPresignReadLogResponse) string { 49 return v.URL 50 } 51 return pass(r.executor, ctx, req, process) 52 } 53 54 // SaveLog streams the output from the workflow to Cloud 55 func (r *CloudOutputRepository) SaveLog(ctx context.Context, id, workflowName string, reader io.Reader) error { 56 url, err := r.PresignSaveLog(ctx, id, workflowName) 57 if err != nil { 58 return err 59 } 60 // FIXME: It should stream instead 61 data, err := io.ReadAll(reader) 62 if err != nil { 63 return err 64 } 65 req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, bytes.NewBuffer(data)) 66 req.Header.Add("Content-Type", "application/octet-stream") 67 if err != nil { 68 return err 69 } 70 res, err := http.DefaultClient.Do(req) 71 if err != nil { 72 return errors.Wrap(err, "failed to save file in cloud storage") 73 } 74 if res.StatusCode != http.StatusOK { 75 return errors.Errorf("error saving file with presigned url: expected 200 OK response code, got %d", res.StatusCode) 76 } 77 return nil 78 } 79 80 // ReadLog streams the output from Cloud 81 func (r *CloudOutputRepository) ReadLog(ctx context.Context, id, workflowName string) (io.Reader, error) { 82 url, err := r.PresignReadLog(ctx, id, workflowName) 83 if err != nil { 84 return nil, err 85 } 86 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 87 if err != nil { 88 return nil, err 89 } 90 res, err := http.DefaultClient.Do(req) 91 if err != nil { 92 return nil, errors.Wrap(err, "failed to get file from cloud storage") 93 } 94 if res.StatusCode != http.StatusOK { 95 return nil, errors.Errorf("error getting file from presigned url: expected 200 OK response code, got %d", res.StatusCode) 96 } 97 return res.Body, nil 98 } 99 100 // HasLog checks if there is an output in Cloud 101 func (r *CloudOutputRepository) HasLog(ctx context.Context, id, workflowName string) (bool, error) { 102 req := OutputHasLogRequest{ID: id, WorkflowName: workflowName} 103 process := func(v OutputHasLogResponse) bool { 104 return v.Has 105 } 106 return pass(r.executor, ctx, req, process) 107 }