Skip to content

Commit

Permalink
feat: binarly teststep
Browse files Browse the repository at this point in the history
Signed-off-by: Fabian Wienand <[email protected]>
  • Loading branch information
Fabian Wienand authored and llogen committed Aug 26, 2024
1 parent bf26f2d commit 93e94c9
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 19 deletions.
29 changes: 17 additions & 12 deletions cmds/contest/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
uri "github.com/linuxboot/contest/plugins/testfetchers/uri"

// the teststep plugins
binarly "github.com/linuxboot/contest/plugins/teststeps/binarly"
bios_certificate "github.com/linuxboot/contest/plugins/teststeps/bios_certificate"
bios_setting_get "github.com/linuxboot/contest/plugins/teststeps/bios_settings_get"
bios_setting_set "github.com/linuxboot/contest/plugins/teststeps/bios_settings_set"
Expand Down Expand Up @@ -123,30 +124,34 @@ func GetPluginConfig() *PluginConfig {
var pc PluginConfig
pc.TargetManagerLoaders = append(pc.TargetManagerLoaders, csvtargetmanager.Load)
pc.TargetManagerLoaders = append(pc.TargetManagerLoaders, targetlist.Load)

pc.TestFetcherLoaders = append(pc.TestFetcherLoaders, literal.Load)
pc.TestFetcherLoaders = append(pc.TestFetcherLoaders, uri.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, cpustats.Load)

pc.TestStepLoaders = append(pc.TestStepLoaders, binarly.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, bios_certificate.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, bios_setting_get.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, bios_setting_set.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, chipsec.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, cmd.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, copy.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, cpuload.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, cpuset.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, chipsec.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, cpustats.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, dutctl.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, fwhunt.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, fwts.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, firmware_version.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, hwaas.Load)
pc.ReporterLoaders = append(pc.ReporterLoaders, noop.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, ping.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, robot.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, s0ix_selftest.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, secureboot.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, sleep.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, s0ix_selftest.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, cmd.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, sysbench.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, copy.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, bios_setting_set.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, bios_setting_get.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, bios_certificate.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, ping.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, dutctl.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, hwaas.Load)
pc.TestStepLoaders = append(pc.TestStepLoaders, qemu.Load)
pc.ReporterLoaders = append(pc.ReporterLoaders, noop.Load)

pc.ReporterLoaders = append(pc.ReporterLoaders, targetsuccess.Load)

return &pc
Expand Down
14 changes: 7 additions & 7 deletions pkg/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ var Events = []event.Name{
EventOutput,
}

type component struct {
Component string `json:"component"`
Data json.RawMessage `json:"data"`
type Component struct {
Name string `json:"name"`
Data []byte `json:"data"`
}

type payload struct {
Expand Down Expand Up @@ -72,10 +72,10 @@ func EmitLog(ctx xcontext.Context, message string, tgt *target.Target, ev testev
return nil
}

func EmitOuput(ctx xcontext.Context, componentName string, data json.RawMessage, tgt *target.Target, ev testevent.Emitter) error {
payload := component{
Component: componentName,
Data: data,
func EmitOuput(ctx xcontext.Context, name string, data []byte, tgt *target.Target, ev testevent.Emitter) error {
payload := Component{
Name: name,
Data: data,
}

if err := emitEvent(ctx, EventOutput, payload, tgt, ev); err != nil {
Expand Down
80 changes: 80 additions & 0 deletions plugins/teststeps/binarly/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package binarly

import (
"encoding/json"
"fmt"
"time"

"github.com/linuxboot/contest/pkg/event"
"github.com/linuxboot/contest/pkg/event/testevent"
"github.com/linuxboot/contest/pkg/events"
"github.com/linuxboot/contest/pkg/test"
"github.com/linuxboot/contest/pkg/xcontext"
"github.com/linuxboot/contest/plugins/teststeps"
"github.com/linuxboot/contest/plugins/teststeps/abstraction/options"
)

const (
parametersKeyword = "parameters"
)

const (
defaultTimeout time.Duration = 60 * time.Minute
)

type parameters struct {
Token string `json:"token"`
File string `json:"file_path"`
}

// Name is the name used to look this plugin up.
var Name = "Binarly Report"

// TestStep implementation for the exec plugin
type TestStep struct {
parameters
options options.Parameters
}

// Run executes the step.
func (ts *TestStep) Run(ctx xcontext.Context, ch test.TestStepChannels, params test.TestStepParameters, ev testevent.Emitter, resumeState json.RawMessage) (json.RawMessage, error) {
tr := NewTargetRunner(ts, ev)
return teststeps.ForEachTarget(Name, ctx, ch, tr.Run)
}

func (ts *TestStep) populateParams(stepParams test.TestStepParameters) error {
var parameters, optionsParams *test.Param

if parameters = stepParams.GetOne(parametersKeyword); parameters.IsEmpty() {
return fmt.Errorf("parameters cannot be empty")
}

if err := json.Unmarshal(parameters.JSON(), &ts.parameters); err != nil {
return fmt.Errorf("failed to deserialize parameters: %v", err)
}

optionsParams = stepParams.GetOne(options.Keyword)

if !optionsParams.IsEmpty() {
if err := json.Unmarshal(optionsParams.JSON(), &ts.options); err != nil {
return fmt.Errorf("failed to deserialize options: %v", err)
}
}

return nil
}

// ValidateParameters validates the parameters associated to the step
func (ts *TestStep) ValidateParameters(_ xcontext.Context, stepParams test.TestStepParameters) error {
return ts.populateParams(stepParams)
}

// New initializes and returns a new exec step.
func New() test.TestStep {
return &TestStep{}
}

// Load returns the name, factory and events which are needed to register the step.
func Load() (string, test.TestStepFactory, []event.Name) {
return Name, New, events.Events
}
33 changes: 33 additions & 0 deletions plugins/teststeps/binarly/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package binarly

import (
"fmt"
"strings"
"time"
)

// Name returns the name of the Step
func (ts TestStep) Name() string {
return Name
}

// Function to format teststep information and append it to a string builder.
func (ts TestStep) writeTestStep(builders ...*strings.Builder) {
for _, builder := range builders {

builder.WriteString("\n")

builder.WriteString(" Parameter:\n")
builder.WriteString(" Token: *hidden*\n")
builder.WriteString(fmt.Sprintf(" File: %s\n", ts.parameters.File))
builder.WriteString("\n")

builder.WriteString(" Options:\n")
builder.WriteString(fmt.Sprintf(" Timeout: %s\n", time.Duration(ts.options.Timeout)))
builder.WriteString("\n")

builder.WriteString("Default Values:\n")
builder.WriteString(fmt.Sprintf(" Timeout: %s\n", defaultTimeout))
builder.WriteString("\n\n")
}
}
168 changes: 168 additions & 0 deletions plugins/teststeps/binarly/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package binarly

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"time"

"github.com/linuxboot/contest/pkg/event/testevent"
"github.com/linuxboot/contest/pkg/events"
"github.com/linuxboot/contest/pkg/target"
"github.com/linuxboot/contest/pkg/xcontext"
"github.com/linuxboot/contest/plugins/teststeps/abstraction/options"
)

type Error struct {
Msg string `json:"error"`
}

type TargetRunner struct {
ts *TestStep
ev testevent.Emitter
}

func NewTargetRunner(ts *TestStep, ev testevent.Emitter) *TargetRunner {
return &TargetRunner{
ts: ts,
ev: ev,
}
}

func (r *TargetRunner) Run(ctx xcontext.Context, target *target.Target) error {
var outputBuf strings.Builder

ctx, cancel := options.NewOptions(ctx, defaultTimeout, r.ts.options.Timeout)
defer cancel()

r.ts.writeTestStep(&outputBuf)

if r.ts.Token == "" {
outputBuf.WriteString(fmt.Sprintf("%v", fmt.Errorf("Token is required")))

return events.EmitError(ctx, outputBuf.String(), target, r.ev)
}

client := &http.Client{}

id, err := postFileToAPI(ctx, client, r.ts.File, r.ts.Token)
if err != nil {
outputBuf.WriteString(fmt.Sprintf("Failed to post file to binarly: %v", err))

return events.EmitError(ctx, outputBuf.String(), target, r.ev)
}

result, err := awaitResult(ctx, client, id, r.ts.Token)
if err != nil {
outputBuf.WriteString(fmt.Sprintf("Failed to get results from binarly: %v", err))

return events.EmitError(ctx, outputBuf.String(), target, r.ev)
}

if err := events.EmitOuput(ctx, "binarly", result, target, r.ev); err != nil {
outputBuf.WriteString(fmt.Sprintf("Failed to emit output: %v", err))

return events.EmitError(ctx, outputBuf.String(), target, r.ev)
}

return events.EmitLog(ctx, outputBuf.String(), target, r.ev)
}

type StartScanResponse struct {
ID string `json:"id"`
Status string `json:"status"`
}

func postFileToAPI(ctx context.Context, client *http.Client, filePath string, token string) (string, error) {
// Prepare the file to be uploaded
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("failed to open file: %v", err)
}
defer file.Close()

// Create a new multipart form request
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", filepath.Base(file.Name()))
if err != nil {
return "", fmt.Errorf("failed to create form file: %v", err)
}
_, err = io.Copy(part, file)
if err != nil {
return "", fmt.Errorf("failed to copy file: %v", err)
}
writer.Close()

req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://risk-hunt-production-serve-y6yijivpnq-uc.a.run.app/scan", body)
if err != nil {
return "", fmt.Errorf("failed to create request: %v", err)
}

req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", writer.FormDataContentType())

resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()

var response StartScanResponse
if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
return "", fmt.Errorf("failed to decode response: %v", err)
}

if response.Status != "ok" {
return "", fmt.Errorf("failed to start scan: %v", response.Status)
}

return response.ID, nil
}

type ScanStatusResponse struct {
FileName string `json:"file_name"`
Scan json.RawMessage `json:"scan"`
Ratings json.RawMessage `json:"ratings"`
Status string `json:"status"`
UploadTime string `json:"upload_time"`
}

func awaitResult(ctx context.Context, client *http.Client, id string, token string) (json.RawMessage, error) {
var statusResponse ScanStatusResponse

req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://risk-hunt-production-serve-y6yijivpnq-uc.a.run.app/scan/%s", id), nil)
if err != nil {
return json.RawMessage{}, fmt.Errorf("failed to create request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+token)

for {
select {
case <-ctx.Done():
return json.RawMessage{}, fmt.Errorf("timed out waiting for results")
case <-time.After(30 * time.Second):
resp, err := client.Do(req)
if err != nil {
return json.RawMessage{}, fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()

if err = json.NewDecoder(resp.Body).Decode(&statusResponse); err != nil {
return json.RawMessage{}, fmt.Errorf("failed to decode response: %v", err)
}

// Check the status
if statusResponse.Status == "ok" {
return statusResponse.Scan, nil
}
}
}
}

0 comments on commit 93e94c9

Please sign in to comment.