github.com/kubeshop/testkube@v1.17.23/pkg/api/v1/client/common.go (about)

     1  package client
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    11  	"github.com/kubeshop/testkube/pkg/executor/output"
    12  	"github.com/kubeshop/testkube/pkg/logs/events"
    13  	"github.com/kubeshop/testkube/pkg/utils"
    14  )
    15  
    16  // Version is client version literal
    17  const Version = "v1"
    18  
    19  // TestkubeInstallationNamespace where Testkube is installed
    20  const TestkubeInstallationNamespace = "testkube"
    21  
    22  type TestingType string
    23  
    24  const (
    25  	Test      TestingType = "test"
    26  	Execution TestingType = "execution"
    27  )
    28  
    29  // StreamToLogsChannel converts io.Reader with SSE data like `data: {"type": "event", "message":"something"}`
    30  // to channel of output.Output objects, helps with logs streaming from SSE endpoint (passed from job executor)
    31  func StreamToLogsChannel(resp io.Reader, logs chan output.Output) {
    32  	reader := bufio.NewReader(resp)
    33  
    34  	for {
    35  		b, err := utils.ReadLongLine(reader)
    36  		if err != nil {
    37  			if err != io.EOF {
    38  				fmt.Printf("Read long line error: %+v' \n", err)
    39  			}
    40  
    41  			break
    42  		}
    43  		chunk := trimDataChunk(b)
    44  
    45  		// ignore lines which are not JSON objects
    46  		if len(chunk) < 2 || chunk[0] != '{' {
    47  			continue
    48  		}
    49  
    50  		// convert to output.Output object
    51  		out := output.Output{}
    52  		err = json.Unmarshal(chunk, &out)
    53  		if err != nil {
    54  			fmt.Printf("Unmarshal chunk error: %+v, json:'%s' \n", err, chunk)
    55  			continue
    56  		}
    57  
    58  		logs <- out
    59  	}
    60  }
    61  
    62  // StreamToLogsChannelV2 converts io.Reader with SSE data like `data: {"type": "event", "message":"something"}`
    63  // to channel of output.Output objects, helps with logs version 2 streaming from SSE endpoint (passed from job executor)
    64  func StreamToLogsChannelV2(resp io.Reader, logs chan events.Log) {
    65  	reader := bufio.NewReader(resp)
    66  
    67  	for {
    68  		b, err := utils.ReadLongLine(reader)
    69  		if err != nil {
    70  			if err != io.EOF {
    71  				fmt.Printf("Read long line error: %+v' \n", err)
    72  			}
    73  
    74  			break
    75  		}
    76  		chunk := trimDataChunk(b)
    77  
    78  		// ignore lines which are not JSON objects
    79  		if len(chunk) < 2 || chunk[0] != '{' {
    80  			continue
    81  		}
    82  
    83  		// convert to events.Log object
    84  		out := events.Log{}
    85  		err = json.Unmarshal(chunk, &out)
    86  		if err != nil {
    87  			fmt.Printf("Unmarshal chunk error: %+v, json:'%s' \n", err, chunk)
    88  			continue
    89  		}
    90  
    91  		logs <- out
    92  	}
    93  }
    94  
    95  // StreamToTestWorkflowExecutionNotificationsChannel converts io.Reader with SSE data to channel of actual notifications
    96  func StreamToTestWorkflowExecutionNotificationsChannel(resp io.Reader, notifications chan testkube.TestWorkflowExecutionNotification) {
    97  	reader := bufio.NewReader(resp)
    98  
    99  	for {
   100  		b, err := utils.ReadLongLine(reader)
   101  		if err != nil {
   102  			if err != io.EOF {
   103  				fmt.Printf("Read long line error: %+v' \n", err)
   104  			}
   105  
   106  			break
   107  		}
   108  		chunk := trimDataChunk(b)
   109  
   110  		// ignore lines which are not JSON objects
   111  		if len(chunk) < 2 || chunk[0] != '{' {
   112  			continue
   113  		}
   114  
   115  		out := testkube.TestWorkflowExecutionNotification{}
   116  		err = json.Unmarshal(chunk, &out)
   117  		if err != nil {
   118  			fmt.Printf("Unmarshal chunk error: %+v, json:'%s' \n", err, chunk)
   119  			continue
   120  		}
   121  
   122  		notifications <- out
   123  	}
   124  }
   125  
   126  // trimDataChunk remove data: and newlines from incoming SSE data line
   127  func trimDataChunk(in []byte) []byte {
   128  	prefix := []byte("data: ")
   129  	postfix := []byte("\\n\\n")
   130  	chunk := bytes.Replace(in, prefix, []byte{}, 1)
   131  	chunk = bytes.Replace(chunk, postfix, []byte{}, 1)
   132  
   133  	return chunk
   134  }