这是indexloc提供的服务,不要输入任何密码
Skip to content

Explicitly read the artifact into memory before uploading to the remote cache api #898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 21, 2022
11 changes: 10 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "Basic Turbo Build",
"name": "Build Basic",
"type": "go",
"request": "launch",
"mode": "debug",
Expand All @@ -21,6 +21,15 @@
"program": "${workspaceRoot}/cli/cmd/turbo",
"cwd": "${workspaceRoot}/examples/basic",
"args": ["--version"]
},
{
"name": "Build All (Force)",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceRoot}/cli/cmd/turbo",
"cwd": "${workspaceRoot}",
"args": ["run", "build", "--force"]
}
]
}
23 changes: 21 additions & 2 deletions cli/internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package client

import (
"context"
"crypto/md5"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -132,7 +134,7 @@ func (c *ApiClient) UserAgent() string {
return fmt.Sprintf("turbo %v %v %v (%v)", c.turboVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH)
}

func (c *ApiClient) PutArtifact(hash string, duration int, rawBody interface{}) error {
func (c *ApiClient) PutArtifact(hash string, duration int, artifactReader io.Reader) error {
if err := c.okToRequest(); err != nil {
return err
}
Expand All @@ -143,11 +145,28 @@ func (c *ApiClient) PutArtifact(hash string, duration int, rawBody interface{})
if encoded != "" {
encoded = "?" + encoded
}
req, err := retryablehttp.NewRequest(http.MethodPut, c.makeUrl("/v8/artifacts/"+hash+encoded), rawBody)
// Read the entire artifactReader into memory so we can easily compute the Content-MD5.
// Note: retryablehttp.NewRequest reads the artifactReader into memory so there's no
// additional overhead by doing the ioutil.ReadAll here instead.
artifactBody, err := ioutil.ReadAll(artifactReader)
if err != nil {
return fmt.Errorf("failed to store files in HTTP cache: %w", err)
}
md5Sum := md5.Sum(artifactBody)
contentMd5 := base64.StdEncoding.EncodeToString(md5Sum[:])

req, err := retryablehttp.NewRequest(http.MethodPut, c.makeUrl("/v8/artifacts/"+hash+encoded), artifactBody)
// We need to initialize the trailer since it's not auto-initialized by the http module
req.Trailer = make(http.Header)
req.Trailer.Add("Content-MD5", contentMd5)
// ContentLenth -1 is required to set trailers on the http request.
req.ContentLength = -1
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("x-artifact-duration", fmt.Sprintf("%v", duration))
req.Header.Set("Authorization", "Bearer "+c.Token)
req.Header.Set("User-Agent", c.UserAgent())
req.Header.Set("Trailer", "Content-MD5")

if err != nil {
return fmt.Errorf("[WARNING] Invalid cache URL: %w", err)
}
Expand Down
69 changes: 54 additions & 15 deletions cli/internal/client/client_test.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
package client

import (
"crypto/md5"
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"

"github.com/google/uuid"
"github.com/hashicorp/go-hclog"
)

func Test_sendToServer(t *testing.T) {
handler := http.NewServeMux()
ch := make(chan []byte, 1)
handler.HandleFunc("/v8/artifacts/events", func(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
b, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Errorf("failed to read request %v", err)
}
ch <- b
w.WriteHeader(200)
w.Write([]byte{})
})
server := &http.Server{Addr: "localhost:8888", Handler: handler}
go server.ListenAndServe()
ts := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
b, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Errorf("failed to read request %v", err)
}
ch <- b
w.WriteHeader(200)
w.Write([]byte{})
}))
defer ts.Close()

apiClient := NewClient("http://localhost:8888", hclog.Default(), "v1", "", "my-team-slug", 1)
apiClient := NewClient(ts.URL, hclog.Default(), "v1", "", "my-team-slug", 1)
apiClient.SetToken("my-token")

myUUID, err := uuid.NewUUID()
Expand Down Expand Up @@ -61,6 +64,42 @@ func Test_sendToServer(t *testing.T) {
if !reflect.DeepEqual(events, result) {
t.Errorf("roundtrip got %v, want %v", result, events)
}
}

func Test_PutArtifact(t *testing.T) {
ch := make(chan string, 2)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
b, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Errorf("failed to read request %v", err)
}
ch <- string(b)
trailerMd5 := req.Trailer.Get("Content-MD5")
ch <- trailerMd5
w.WriteHeader(200)
w.Write([]byte{})
}))
defer ts.Close()

// Set up test expected values
apiClient := NewClient(ts.URL+"/hash", hclog.Default(), "v1", "", "my-team-slug", 1)
apiClient.SetToken("my-token")
expectedArtifactBody := "My string artifact"
artifactReader := strings.NewReader(expectedArtifactBody)
md5Sum := md5.Sum([]byte(expectedArtifactBody))
expectedMd5 := base64.StdEncoding.EncodeToString(md5Sum[:])

// Test Put Artifact
apiClient.PutArtifact("hash", 500, artifactReader)
testBody := <-ch
if expectedArtifactBody != testBody {
t.Errorf("Handler read '%v', wants '%v'", testBody, expectedArtifactBody)
}

testMd5 := <-ch
if expectedMd5 != testMd5 {
t.Errorf("Handler read trailer '%v', wants '%v'", testMd5, expectedMd5)
}

server.Close()
}