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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 35 additions & 20 deletions pkg/connectors/microcks_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,31 +425,41 @@ func (c *microcksClient) UploadArtifact(specificationFilePath string, mainArtifa
}
defer file.Close()

// Create a multipart request body, reading the file.
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", filepath.Base(specificationFilePath))
if err != nil {
return "", err
}
_, err = io.Copy(part, file)
if err != nil {
panic(err.Error())
}
// Use io.Pipe to stream the multipart form data directly to the HTTP
// request without buffering the entire file in memory.
pr, pw := io.Pipe()
writer := multipart.NewWriter(pw)

// Write the multipart form data in a background goroutine so the pipe
// reader can be consumed concurrently by the HTTP request.
errCh := make(chan error, 1)
go func() {
defer pw.Close()

part, err := writer.CreateFormFile("file", filepath.Base(specificationFilePath))
if err != nil {
errCh <- err
return
}
if _, err = io.Copy(part, file); err != nil {
errCh <- err
return
}

// Add the mainArtifact flag to request.
_ = writer.WriteField("mainArtifact", strconv.FormatBool(mainArtifact))
// Add the mainArtifact flag to request.
if err = writer.WriteField("mainArtifact", strconv.FormatBool(mainArtifact)); err != nil {
errCh <- err
return
}

err = writer.Close()
if err != nil {
return "", err
}
errCh <- writer.Close()
}()

// Ensure we have a correct URL.
rel := &url.URL{Path: "artifact/upload"}
u := c.APIURL.ResolveReference(rel)

req, err := http.NewRequest("POST", u.String(), body)
req, err := http.NewRequest("POST", u.String(), pr)
if err != nil {
return "", err
}
Expand All @@ -465,20 +475,25 @@ func (c *microcksClient) UploadArtifact(specificationFilePath string, mainArtifa
}
defer resp.Body.Close()

// Check for errors from the multipart writer goroutine.
if pipeErr := <-errCh; pipeErr != nil {
return "", fmt.Errorf("failed to write multipart form: %w", pipeErr)
}

// Dump response if verbose required.
config.DumpResponseIfRequired("Microcks for uploading artifact", resp, true)

respBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err.Error())
return "", fmt.Errorf("failed to read upload response: %w", err)
}

// Raise exception if not created.
if resp.StatusCode != 201 {
return "", errs.New(string(respBody))
}

return string(respBody), err
return string(respBody), nil
}

func (c *microcksClient) DownloadArtifact(artifactURL string, mainArtifact bool, secret string) (string, error) {
Expand Down
61 changes: 61 additions & 0 deletions pkg/connectors/microcks_client_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,73 @@
package connectors

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

func TestUploadArtifactStreamsWithoutBuffering(t *testing.T) {
const fileContent = `{"openapi":"3.0.0","info":{"title":"Test API","version":"1.0.0"}}`
const expectedResponse = "artifact uploaded"

// Create a temporary file to simulate an API specification.
tmpDir := t.TempDir()
specPath := filepath.Join(tmpDir, "openapi.json")
if err := os.WriteFile(specPath, []byte(fileContent), 0o600); err != nil {
t.Fatalf("failed to create temp spec file: %v", err)
}

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/artifact/upload" {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
if r.Method != http.MethodPost {
t.Fatalf("unexpected method: %s", r.Method)
}

// Verify the multipart form contains the file.
file, header, err := r.FormFile("file")
if err != nil {
t.Fatalf("failed to get form file: %v", err)
}
defer file.Close()

if header.Filename != "openapi.json" {
t.Fatalf("unexpected filename: %s", header.Filename)
}

body, err := io.ReadAll(file)
if err != nil {
t.Fatalf("failed to read uploaded file: %v", err)
}
if string(body) != fileContent {
t.Fatalf("file content mismatch: got %q, want %q", string(body), fileContent)
}

// Verify the mainArtifact field.
if got := r.FormValue("mainArtifact"); got != "true" {
t.Fatalf("unexpected mainArtifact value: %s", got)
}

w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(expectedResponse))
}))
defer server.Close()

client := NewMicrocksClient(server.URL)
msg, err := client.UploadArtifact(specPath, true)
if err != nil {
t.Fatalf("UploadArtifact returned error: %v", err)
}
if strings.TrimSpace(msg) != expectedResponse {
t.Fatalf("expected response %q, got %q", expectedResponse, msg)
}
}

func TestDownloadArtifactReturnsResponseBody(t *testing.T) {
const expectedBody = "artifact downloaded"

Expand Down
Loading