FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add retries for the raw result upload from the lurker sidecar by J12934 · Pull Request #3785 · secureCodeBox/secureCodeBox · GitHub

Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .go  (2) .yaml  (2) dotfile  (1) All 3 file types selected
Only manifest files
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
This file contains hidden or 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
Expand Up @@ -194,7 +194,7 @@ jobs:
go vet ./...

- name: Test
working-directory: ./operator
working-directory: ./${{ matrix.component }}
run: task test

- name: Build Container Image
Expand Down
5 changes: 5 additions & 0 deletions lurker/.gitignore
This file contains hidden or 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
Expand Up @@ -3,3 +3,8 @@
# SPDX-License-Identifier: Apache-2.0

main
lurker
bin

# Output of the go coverage tool
*.out
38 changes: 38 additions & 0 deletions lurker/Taskfile.yaml
This file contains hidden or 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,38 @@
# SPDX-FileCopyrightText: the secureCodeBox authors
#
# SPDX-License-Identifier: Apache-2.0

version: "3.48.0"

tasks:
fmt:
desc: "Run go fmt against code"
dir: '{{ .TASKFILE_DIR }}'
cmds:
- go fmt ./...

vet:
desc: "Run go vet against code"
dir: '{{ .TASKFILE_DIR }}'
cmds:
- go vet ./...

test:
desc: "Run all tests"
deps: [fmt, vet]
dir: '{{ .TASKFILE_DIR }}'
cmds:
- go test ./... -coverprofile cover.out

view-coverage:
desc: "View test coverage in browser"
dir: '{{ .TASKFILE_DIR }}'
cmds:
- go tool cover -html=cover.out

build:
desc: "Build the lurker binary"
deps: [fmt, vet]
dir: '{{ .TASKFILE_DIR }}'
cmds:
- go build -o bin/lurker main.go
50 changes: 42 additions & 8 deletions lurker/main.go
Show comments Show annotations View file Open in desktop
This file contains hidden or 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
Expand Up @@ -8,6 +8,7 @@
"context"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
Expand Down Expand Up @@ -55,13 +56,44 @@

log.Printf("Uploading result files.")
log.Printf("Uploading %s", filePath)
err = uploadFile(filePath, uploadURL)
err = uploadFileWithRetries(filePath, uploadURL)
if err != nil {
log.Fatal(err)
}
log.Printf("Uploaded file successfully")
}

// delays waited before each retry of a failed upload. The number of entries
// defines how often the upload is retried after the initial attempt.
var uploadBackoffs = []time.Duration{1 * time.Second, 3 * time.Second, 5 * time.Second, 10 * time.Second, 30 * time.Second}

// indirection to allow tests to run without actually waiting
var sleep = time.Sleep

// uploadFileWithRetries uploads the file and retries every failure (transport
// errors as well as non 2xx responses) using the uploadBackoffs schedule.
func uploadFileWithRetries(path, url string) error {
attempts := len(uploadBackoffs) + 1

var err error
for attempt := 1; attempt <= attempts; attempt++ {
err = uploadFile(path, url)
if err == nil {
return nil
}

if attempt == attempts {
break
}

backoff := uploadBackoffs[attempt-1]
log.Printf("Upload attempt %d of %d failed: %v. Retrying in %s", attempt, attempts, err, backoff)
sleep(backoff)
}

return fmt.Errorf("lurker failed to upload scan result file after %d attempts: %w", attempts, err)
}

func uploadFile(path, url string) error {
file, err := os.Open(path)
if err != nil {
Expand All @@ -81,7 +113,7 @@
// Create a new file upload request
req, err := http.NewRequest("PUT", url, file)
if err != nil {
log.Fatalf("Failed to create request: %v", err)
log.Printf("Failed to create request: %v", err)
return err
}

Expand Down Expand Up @@ -109,14 +141,16 @@
log.Printf("File upload returned non 2xx status code (%d)", res.StatusCode)

// Dump response for debugging purposes
resultBytes, err := httputil.DumpResponse(res, true)
if err != nil {
log.Fatal(fmt.Errorf("failed to dump out failed requests to upload scan report to the s3 bucket: %w", err))
resultBytes, dumpErr := httputil.DumpResponse(res, true)

Check warning on line 144 in lurker/main.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure this debug feature is deactivated before delivering the code in production.

See more on https://sonarcloud.io/project/issues?id=secureCodeBox_secureCodeBox&issues=AaA5PEaTS_a-7DRiGpH3&open=AaA5PEaTS_a-7DRiGpH3&pullRequest=3785
if dumpErr != nil {
log.Printf("failed to dump out failed requests to upload scan report to the s3 bucket: %v", dumpErr)
// drain the body so that the connection can be reused by the next attempt
io.Copy(io.Discard, res.Body)
} else {
log.Println("Response of Failed Request:")
log.Println(string(resultBytes))
}

log.Println("Response of Failed Request:")
log.Println(string(resultBytes))

return fmt.Errorf("lurker failed to upload scan result file. File upload returned non 2xx status code (%d)", res.StatusCode)
}

Expand Down
162 changes: 162 additions & 0 deletions lurker/main_test.go
This file contains hidden or 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,162 @@
// SPDX-FileCopyrightText: the secureCodeBox authors
//
// SPDX-License-Identifier: Apache-2.0

package main

import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
)

const testFileContents = `{"findings": []}`

// writeResultFile creates a temporary scan result file and returns its path.
func writeResultFile(t *testing.T) string {
t.Helper()

path := filepath.Join(t.TempDir(), "findings.json")
if err := os.WriteFile(path, []byte(testFileContents), 0o600); err != nil {
t.Fatalf("failed to write test file: %v", err)
}
return path
}

// stubSleep replaces the backoff sleep with a recorder, so that tests don't
// have to actually wait. Returns a pointer to the recorded delays.
func stubSleep(t *testing.T) *[]time.Duration {
t.Helper()

original := sleep
delays := []time.Duration{}
sleep = func(d time.Duration) {
delays = append(delays, d)
}
t.Cleanup(func() { sleep = original })

return &delays
}

func TestUploadFileWithRetriesSucceedsOnFirstAttempt(t *testing.T) {
delays := stubSleep(t)

requests := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

if err := uploadFileWithRetries(writeResultFile(t), server.URL); err != nil {
t.Fatalf("expected upload to succeed, got: %v", err)
}
if requests != 1 {
t.Errorf("expected 1 request, got %d", requests)
}
if len(*delays) != 0 {
t.Errorf("expected no backoff, got %v", *delays)
}
}

func TestUploadFileWithRetriesRetriesUntilSuccess(t *testing.T) {
delays := stubSleep(t)

requests := 0
receivedBodies := []string{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
body, err := io.ReadAll(r.Body)
if err != nil {
t.Errorf("failed to read request body: %v", err)
}
receivedBodies = append(receivedBodies, string(body))

if requests < 3 {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

if err := uploadFileWithRetries(writeResultFile(t), server.URL); err != nil {
t.Fatalf("expected upload to succeed after retries, got: %v", err)
}
if requests != 3 {
t.Errorf("expected 3 requests, got %d", requests)
}

// every attempt, especially the last one, has to send the complete file
for attempt, body := range receivedBodies {
if body != testFileContents {
t.Errorf("attempt %d uploaded %q, expected %q", attempt+1, body, testFileContents)
}
}

expectedDelays := uploadBackoffs[:2]
if len(*delays) != len(expectedDelays) {
t.Fatalf("expected %d backoffs, got %v", len(expectedDelays), *delays)
}
for i, delay := range *delays {
if delay != expectedDelays[i] {
t.Errorf("backoff %d was %s, expected %s", i+1, delay, expectedDelays[i])
}
}
}

func TestUploadFileWithRetriesGivesUpAfterAllAttempts(t *testing.T) {
delays := stubSleep(t)

requests := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()

if err := uploadFileWithRetries(writeResultFile(t), server.URL); err == nil {
t.Fatal("expected upload to fail")
}

expectedRequests := len(uploadBackoffs) + 1
if requests != expectedRequests {
t.Errorf("expected %d requests, got %d", expectedRequests, requests)
}
if len(*delays) != len(uploadBackoffs) {
t.Errorf("expected %d backoffs, got %v", len(uploadBackoffs), *delays)
}
for i, delay := range *delays {
if delay != uploadBackoffs[i] {
t.Errorf("backoff %d was %s, expected %s", i+1, delay, uploadBackoffs[i])
}
}
}

func TestUploadFileWithRetriesRetriesTransportErrors(t *testing.T) {
delays := stubSleep(t)

// closing the server up front makes every attempt fail on the transport level
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
url := server.URL
server.Close()

if err := uploadFileWithRetries(writeResultFile(t), url); err == nil {
t.Fatal("expected upload to fail")
}
if len(*delays) != len(uploadBackoffs) {
t.Errorf("expected %d backoffs, got %v", len(uploadBackoffs), *delays)
}
}

func TestUploadFileWithRetriesFailsForMissingFile(t *testing.T) {
stubSleep(t)

if err := uploadFileWithRetries(filepath.Join(t.TempDir(), "does-not-exist.json"), "http://127.0.0.1:1"); err == nil {
t.Fatal("expected upload of a missing file to fail")
}
}
Loading

Back | FazBrowse Home | New Git URL