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  }