forked from linuxboot/contest
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Fabian Wienand <[email protected]>
- Loading branch information
Showing
5 changed files
with
305 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} | ||
} |