From 8e7454e7b041376780b02b79c62ea997bee113a1 Mon Sep 17 00:00:00 2001 From: Bruno Calza Date: Tue, 16 Jan 2024 17:39:16 -0300 Subject: [PATCH 1/2] updates retrieve command to read from cache Signed-off-by: Bruno Calza --- cmd/vaults/commands.go | 88 ++----------- internal/app/retriever.go | 121 ++++++++++++++++++ internal/app/retriever_test.go | 44 +++++++ internal/app/streamer_test.go | 7 + .../{basin_provider.go => vaults_provider.go} | 8 ++ pkg/vaultsprovider/provider.go | 36 +++++- 6 files changed, 228 insertions(+), 76 deletions(-) create mode 100644 internal/app/retriever.go create mode 100644 internal/app/retriever_test.go rename internal/app/{basin_provider.go => vaults_provider.go} (84%) diff --git a/cmd/vaults/commands.go b/cmd/vaults/commands.go index 7396b55..634009d 100644 --- a/cmd/vaults/commands.go +++ b/cmd/vaults/commands.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "os" "path" "regexp" @@ -16,13 +15,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" - "github.com/filecoin-project/lassie/pkg/lassie" - "github.com/filecoin-project/lassie/pkg/storage" - "github.com/filecoin-project/lassie/pkg/types" "github.com/ipfs/go-cid" - "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/storage/deferred" - trustlessutils "github.com/ipld/go-trustless-utils" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "github.com/olekukonko/tablewriter" @@ -596,7 +589,7 @@ func newListEventsCommand() *cli.Command { } func newRetrieveCommand() *cli.Command { - var output string + var output, provider string return &cli.Command{ Name: "retrieve", @@ -614,6 +607,15 @@ func newRetrieveCommand() *cli.Command { DefaultText: "current directory", Destination: &output, }, + &cli.StringFlag{ + Name: "provider", + Aliases: []string{"p"}, + Category: "OPTIONAL:", + Usage: "The provider's address and port (e.g., localhost:8080)", + DefaultText: DefaultProviderHost, + Destination: &provider, + Value: DefaultProviderHost, + }, }, Action: func(cCtx *cli.Context) error { arg := cCtx.Args().Get(0) @@ -626,73 +628,9 @@ func newRetrieveCommand() *cli.Command { return errors.New("CID is invalid") } - lassie, err := lassie.NewLassie(cCtx.Context) - if err != nil { - return fmt.Errorf("failed to create lassie instance: %s", err) - } - - carOpts := []car.Option{ - car.WriteAsCarV1(true), - car.StoreIdentityCIDs(false), - car.UseWholeCIDs(false), - } - - var carWriter *deferred.DeferredCarWriter - var tmpFile *os.File - - if output == "-" { - // Create a temporary file only for writing to stdout case - tmpFile, err = os.CreateTemp("", fmt.Sprintf("%s.car", arg)) - if err != nil { - return fmt.Errorf("failed to create temporary file: %s", err) - } - defer func() { - _ = os.Remove(tmpFile.Name()) - }() - carWriter = deferred.NewDeferredCarWriterForPath(tmpFile.Name(), []cid.Cid{rootCid}, carOpts...) - } else { - // Write to the provided path or current directory - if output == "" { - output = "." // Default to current directory - } - // Ensure path is a valid directory - info, err := os.Stat(output) - if err != nil { - return fmt.Errorf("failed to access output directory: %s", err) - } - if !info.IsDir() { - return fmt.Errorf("output path is not a directory: %s", output) - } - carPath := path.Join(output, fmt.Sprintf("%s.car", arg)) - carWriter = deferred.NewDeferredCarWriterForPath(carPath, []cid.Cid{rootCid}, carOpts...) - } - - defer func() { - _ = carWriter.Close() - }() - carStore := storage.NewCachingTempStore( - carWriter.BlockWriteOpener(), storage.NewDeferredStorageCar(os.TempDir(), rootCid), - ) - defer func() { - _ = carStore.Close() - }() - - request, err := types.NewRequestForPath(carStore, rootCid, "", trustlessutils.DagScopeAll, nil) - if err != nil { - return fmt.Errorf("failed to create request: %s", err) - } - - if _, err := lassie.Fetch(cCtx.Context, request, []types.FetchOption{}...); err != nil { - return fmt.Errorf("failed to fetch: %s", err) - } - - // Write to stdout only if the output flag is set to '-' - if output == "-" && tmpFile != nil { - _, _ = tmpFile.Seek(0, io.SeekStart) - _, err = io.Copy(os.Stdout, tmpFile) - if err != nil { - return fmt.Errorf("failed to write to stdout: %s", err) - } + retriever := app.NewRetriever(vaultsprovider.New(provider)) + if err := retriever.Retrieve(cCtx.Context, rootCid, output, arg); err != nil { + return fmt.Errorf("failed to retrieve: %s", err) } return nil diff --git a/internal/app/retriever.go b/internal/app/retriever.go new file mode 100644 index 0000000..0a7372a --- /dev/null +++ b/internal/app/retriever.go @@ -0,0 +1,121 @@ +package app + +import ( + "context" + "fmt" + "io" + "os" + "path" + + "github.com/filecoin-project/lassie/pkg/lassie" + "github.com/filecoin-project/lassie/pkg/storage" + "github.com/filecoin-project/lassie/pkg/types" + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/storage/deferred" + trustlessutils "github.com/ipld/go-trustless-utils" +) + +// Retriever is responsible for retrieving file from the network. +type Retriever struct { + cacheStore *cacheStore + coldStore *coldStore // nolint +} + +// NewRetriever creates a new Retriever. +func NewRetriever(provider VaultsProvider) *Retriever { + return &Retriever{ + cacheStore: &cacheStore{ + provider: provider, + }, + } +} + +// Retrieve retrieves file from the network. +func (r *Retriever) Retrieve(ctx context.Context, c cid.Cid, output string, name string) error { + if output == "-" { + return r.cacheStore.retrieveStdout(ctx, c) + } + + return r.cacheStore.retrieveFile(ctx, c, output, name) +} + +type cacheStore struct { + provider VaultsProvider +} + +func (cs *cacheStore) retrieveStdout(ctx context.Context, cid cid.Cid) error { + if err := cs.provider.RetrieveEvent(ctx, RetrieveEventParams{ + CID: cid, + }, os.Stdout); err != nil { + return fmt.Errorf("failed to retrieve to file: %s", err) + } + + return nil +} + +func (cs *cacheStore) retrieveFile(ctx context.Context, cid cid.Cid, output string, name string) error { + // Write to the provided path or current directory + if output == "" { + output = "." // Default to current directory + } + // Ensure path is a valid directory + info, err := os.Stat(output) + if err != nil { + return fmt.Errorf("failed to access output directory: %s", err) + } + if !info.IsDir() { + return fmt.Errorf("output path is not a directory: %s", output) + } + + f, err := os.OpenFile(path.Join(output, name), os.O_RDWR|os.O_CREATE, 0o666) + if err != nil { + return fmt.Errorf("failed to open tmp file: %s", err) + } + _, _ = f.Seek(0, io.SeekStart) + + if err := cs.provider.RetrieveEvent(ctx, RetrieveEventParams{ + CID: cid, + }, f); err != nil { + return fmt.Errorf("failed to retrieve to file: %s", err) + } + + return nil +} + +type coldStore struct{} // nolint + +func (cs *coldStore) retrieve(ctx context.Context, c cid.Cid, path string) error { // nolint + lassie, err := lassie.NewLassie(ctx) + if err != nil { + return fmt.Errorf("failed to create lassie instance: %s", err) + } + + carOpts := []car.Option{ + car.WriteAsCarV1(true), + car.StoreIdentityCIDs(false), + car.UseWholeCIDs(false), + } + + carWriter := deferred.NewDeferredCarWriterForPath(path, []cid.Cid{c}, carOpts...) + defer func() { + _ = carWriter.Close() + }() + carStore := storage.NewCachingTempStore( + carWriter.BlockWriteOpener(), storage.NewDeferredStorageCar(os.TempDir(), c), + ) + defer func() { + _ = carStore.Close() + }() + + request, err := types.NewRequestForPath(carStore, c, "", trustlessutils.DagScopeAll, nil) + if err != nil { + return fmt.Errorf("failed to create request: %s", err) + } + + if _, err := lassie.Fetch(ctx, request, []types.FetchOption{}...); err != nil { + return fmt.Errorf("failed to fetch: %s", err) + } + + return nil +} diff --git a/internal/app/retriever_test.go b/internal/app/retriever_test.go new file mode 100644 index 0000000..4da5304 --- /dev/null +++ b/internal/app/retriever_test.go @@ -0,0 +1,44 @@ +package app + +import ( + "context" + "io" + "os" + "path" + "testing" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" +) + +func TestRetrieverFileOutput(t *testing.T) { + retriever := NewRetriever(&vaultsProviderMock{}) + output := t.TempDir() + err := retriever.Retrieve(context.Background(), cid.Cid{}, output, "test.txt") + require.NoError(t, err) + + f, err := os.Open(path.Join(output, "test.txt")) + require.NoError(t, err) + + data, err := io.ReadAll(f) + require.NoError(t, err) + + require.Equal(t, []byte("Hello"), data) +} + +func TestRetrieverStdoutOutput(t *testing.T) { + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w // overwrite os.Stdout so we can read from it + + retriever := NewRetriever(&vaultsProviderMock{}) + + err := retriever.Retrieve(context.Background(), cid.Cid{}, "-", "test.txt") + require.NoError(t, err) + + _ = w.Close() + data, _ := io.ReadAll(r) + os.Stdout = old + + require.Equal(t, []byte("Hello"), data) +} diff --git a/internal/app/streamer_test.go b/internal/app/streamer_test.go index fa09b5c..10ce1df 100644 --- a/internal/app/streamer_test.go +++ b/internal/app/streamer_test.go @@ -282,3 +282,10 @@ func (bp *vaultsProviderMock) WriteVaultEvent( close(bp.uploaderInputs) return nil } + +func (bp *vaultsProviderMock) RetrieveEvent( + _ context.Context, _ RetrieveEventParams, w io.Writer, +) error { + _, _ = w.Write([]byte("Hello")) + return nil +} diff --git a/internal/app/basin_provider.go b/internal/app/vaults_provider.go similarity index 84% rename from internal/app/basin_provider.go rename to internal/app/vaults_provider.go index dd6b6f5..2a48eda 100644 --- a/internal/app/basin_provider.go +++ b/internal/app/vaults_provider.go @@ -3,6 +3,8 @@ package app import ( "context" "io" + + "github.com/ipfs/go-cid" ) // VaultsProvider defines Vaults API. @@ -11,6 +13,7 @@ type VaultsProvider interface { ListVaults(context.Context, ListVaultsParams) ([]Vault, error) ListVaultEvents(context.Context, ListVaultEventsParams) ([]EventInfo, error) WriteVaultEvent(context.Context, WriteVaultEventParams) error + RetrieveEvent(context.Context, RetrieveEventParams, io.Writer) error } // CreateVaultParams ... @@ -43,3 +46,8 @@ type WriteVaultEventParams struct { ProgressBar io.Writer Size int64 } + +// RetrieveEventParams ... +type RetrieveEventParams struct { + CID cid.Cid +} diff --git a/pkg/vaultsprovider/provider.go b/pkg/vaultsprovider/provider.go index 361ac3b..e80aefa 100644 --- a/pkg/vaultsprovider/provider.go +++ b/pkg/vaultsprovider/provider.go @@ -149,7 +149,7 @@ func (bp *VaultsProvider) WriteVaultEvent(ctx context.Context, params app.WriteV _ = resp.Body.Close() }() - if resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusCreated { type response struct { Error string } @@ -163,3 +163,37 @@ func (bp *VaultsProvider) WriteVaultEvent(ctx context.Context, params app.WriteV return nil } + +// RetrieveEvent retrieves an event. +func (bp *VaultsProvider) RetrieveEvent(ctx context.Context, params app.RetrieveEventParams, w io.Writer) error { + req, err := http.NewRequestWithContext( + ctx, + http.MethodGet, + fmt.Sprintf("%s/events/%s", bp.provider, params.CID.String()), + nil, + ) + if err != nil { + return fmt.Errorf("could not create request: %s", err) + } + + client := &http.Client{ + Timeout: 0, + } + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("request to write vault event failed: %s", err) + } + defer func() { + _ = resp.Body.Close() + }() + + if resp.StatusCode == http.StatusNotFound { + return errors.New("not found") + } + + if _, err := io.Copy(w, resp.Body); err != nil { + return errors.New("failed copy response body") + } + return nil +} From 4c566d29989dd1f407c518f02118f79c277c60d2 Mon Sep 17 00:00:00 2001 From: Bruno Calza Date: Wed, 17 Jan 2024 15:50:01 -0300 Subject: [PATCH 2/2] adds cache flag and filename at retrieve command Signed-off-by: Bruno Calza --- cmd/vaults/commands.go | 24 +++++++++++++-- internal/app/retriever.go | 53 ++++++++++++++++++++++----------- internal/app/retriever_test.go | 12 ++++---- internal/app/streamer_test.go | 4 +-- internal/app/timestamp.go | 1 - internal/app/uploader.go | 8 +++++ internal/app/vaults_provider.go | 6 ++-- pkg/vaultsprovider/provider.go | 30 ++++++++++++++----- 8 files changed, 101 insertions(+), 37 deletions(-) diff --git a/cmd/vaults/commands.go b/cmd/vaults/commands.go index 634009d..4e943af 100644 --- a/cmd/vaults/commands.go +++ b/cmd/vaults/commands.go @@ -590,6 +590,8 @@ func newListEventsCommand() *cli.Command { func newRetrieveCommand() *cli.Command { var output, provider string + var cache bool + var timeout int64 return &cli.Command{ Name: "retrieve", @@ -616,6 +618,24 @@ func newRetrieveCommand() *cli.Command { Destination: &provider, Value: DefaultProviderHost, }, + &cli.BoolFlag{ + Name: "cache", + Aliases: []string{"c"}, + Category: "OPTIONAL:", + Usage: "Retrieves from cache by setting this flag", + DefaultText: "current directory", + Destination: &cache, + Value: true, + }, + &cli.Int64Flag{ + Name: "timeout", + Aliases: []string{"t"}, + Category: "OPTIONAL:", + Usage: "Timeout for retrieval operation (seconds)", + DefaultText: "no timeout", + Destination: &timeout, + Value: 0, + }, }, Action: func(cCtx *cli.Context) error { arg := cCtx.Args().Get(0) @@ -628,8 +648,8 @@ func newRetrieveCommand() *cli.Command { return errors.New("CID is invalid") } - retriever := app.NewRetriever(vaultsprovider.New(provider)) - if err := retriever.Retrieve(cCtx.Context, rootCid, output, arg); err != nil { + retriever := app.NewRetriever(vaultsprovider.New(provider), cache, timeout) + if err := retriever.Retrieve(cCtx.Context, rootCid, output); err != nil { return fmt.Errorf("failed to retrieve: %s", err) } diff --git a/internal/app/retriever.go b/internal/app/retriever.go index 0a7372a..281307b 100644 --- a/internal/app/retriever.go +++ b/internal/app/retriever.go @@ -16,37 +16,48 @@ import ( trustlessutils "github.com/ipld/go-trustless-utils" ) +type retriever interface { + retrieveStdout(context.Context, cid.Cid, int64) error + retrieveFile(context.Context, cid.Cid, string, int64) error +} + // Retriever is responsible for retrieving file from the network. type Retriever struct { - cacheStore *cacheStore - coldStore *coldStore // nolint + store retriever + timeout int64 } // NewRetriever creates a new Retriever. -func NewRetriever(provider VaultsProvider) *Retriever { - return &Retriever{ - cacheStore: &cacheStore{ - provider: provider, - }, +func NewRetriever(provider VaultsProvider, cache bool, timeout int64) *Retriever { + if cache { + return &Retriever{ + store: &cacheStore{ + provider: provider, + }, + timeout: timeout, + } } + + panic("cold store not implemented yet") } // Retrieve retrieves file from the network. -func (r *Retriever) Retrieve(ctx context.Context, c cid.Cid, output string, name string) error { +func (r *Retriever) Retrieve(ctx context.Context, c cid.Cid, output string) error { if output == "-" { - return r.cacheStore.retrieveStdout(ctx, c) + return r.store.retrieveStdout(ctx, c, r.timeout) } - return r.cacheStore.retrieveFile(ctx, c, output, name) + return r.store.retrieveFile(ctx, c, output, r.timeout) } type cacheStore struct { provider VaultsProvider } -func (cs *cacheStore) retrieveStdout(ctx context.Context, cid cid.Cid) error { - if err := cs.provider.RetrieveEvent(ctx, RetrieveEventParams{ - CID: cid, +func (cs *cacheStore) retrieveStdout(ctx context.Context, cid cid.Cid, timeout int64) error { + if _, err := cs.provider.RetrieveEvent(ctx, RetrieveEventParams{ + Timeout: timeout, + CID: cid, }, os.Stdout); err != nil { return fmt.Errorf("failed to retrieve to file: %s", err) } @@ -54,7 +65,7 @@ func (cs *cacheStore) retrieveStdout(ctx context.Context, cid cid.Cid) error { return nil } -func (cs *cacheStore) retrieveFile(ctx context.Context, cid cid.Cid, output string, name string) error { +func (cs *cacheStore) retrieveFile(ctx context.Context, cid cid.Cid, output string, timeout int64) error { // Write to the provided path or current directory if output == "" { output = "." // Default to current directory @@ -68,18 +79,24 @@ func (cs *cacheStore) retrieveFile(ctx context.Context, cid cid.Cid, output stri return fmt.Errorf("output path is not a directory: %s", output) } - f, err := os.OpenFile(path.Join(output, name), os.O_RDWR|os.O_CREATE, 0o666) + f, err := os.OpenFile(path.Join(output, cid.String()), os.O_RDWR|os.O_CREATE, 0o666) if err != nil { return fmt.Errorf("failed to open tmp file: %s", err) } _, _ = f.Seek(0, io.SeekStart) - if err := cs.provider.RetrieveEvent(ctx, RetrieveEventParams{ - CID: cid, - }, f); err != nil { + filename, err := cs.provider.RetrieveEvent(ctx, RetrieveEventParams{ + Timeout: timeout, + CID: cid, + }, f) + if err != nil { return fmt.Errorf("failed to retrieve to file: %s", err) } + if err := os.Rename(f.Name(), path.Join(output, fmt.Sprintf("%s-%s", cid.String(), filename))); err != nil { + return fmt.Errorf("failed renaming the file: %s", err) + } + return nil } diff --git a/internal/app/retriever_test.go b/internal/app/retriever_test.go index 4da5304..e0f04e1 100644 --- a/internal/app/retriever_test.go +++ b/internal/app/retriever_test.go @@ -2,6 +2,7 @@ package app import ( "context" + "fmt" "io" "os" "path" @@ -12,12 +13,13 @@ import ( ) func TestRetrieverFileOutput(t *testing.T) { - retriever := NewRetriever(&vaultsProviderMock{}) + retriever := NewRetriever(&vaultsProviderMock{}, true, 0) output := t.TempDir() - err := retriever.Retrieve(context.Background(), cid.Cid{}, output, "test.txt") + cid := cid.Cid{} + err := retriever.Retrieve(context.Background(), cid, output) require.NoError(t, err) - f, err := os.Open(path.Join(output, "test.txt")) + f, err := os.Open(path.Join(output, fmt.Sprintf("%s-%s", cid.String(), "sample.txt"))) require.NoError(t, err) data, err := io.ReadAll(f) @@ -31,9 +33,9 @@ func TestRetrieverStdoutOutput(t *testing.T) { r, w, _ := os.Pipe() os.Stdout = w // overwrite os.Stdout so we can read from it - retriever := NewRetriever(&vaultsProviderMock{}) + retriever := NewRetriever(&vaultsProviderMock{}, true, 0) - err := retriever.Retrieve(context.Background(), cid.Cid{}, "-", "test.txt") + err := retriever.Retrieve(context.Background(), cid.Cid{}, "-") require.NoError(t, err) _ = w.Close() diff --git a/internal/app/streamer_test.go b/internal/app/streamer_test.go index 10ce1df..1097ef0 100644 --- a/internal/app/streamer_test.go +++ b/internal/app/streamer_test.go @@ -285,7 +285,7 @@ func (bp *vaultsProviderMock) WriteVaultEvent( func (bp *vaultsProviderMock) RetrieveEvent( _ context.Context, _ RetrieveEventParams, w io.Writer, -) error { +) (string, error) { _, _ = w.Write([]byte("Hello")) - return nil + return "sample.txt", nil } diff --git a/internal/app/timestamp.go b/internal/app/timestamp.go index d8540e5..bc4b765 100644 --- a/internal/app/timestamp.go +++ b/internal/app/timestamp.go @@ -43,7 +43,6 @@ func ParseTimestamp(ts string) (Timestamp, error) { if t, err := time.Parse(time.RFC3339, ts); err == nil { return Timestamp{t.UTC()}, nil } - fmt.Println(time.Parse(time.RFC3339, ts)) return Timestamp{}, fmt.Errorf("could not parse %s", ts) } diff --git a/internal/app/uploader.go b/internal/app/uploader.go index 13facc0..078a736 100644 --- a/internal/app/uploader.go +++ b/internal/app/uploader.go @@ -9,6 +9,7 @@ import ( "io" "log" "os" + "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -53,10 +54,17 @@ func (bu *VaultsUploader) Upload( return fmt.Errorf("signing the file: %s", err) } + filename := filepath + if strings.Contains(filepath, "/") { + parts := strings.Split(filepath, "/") + filename = parts[len(parts)-1] + } + params := WriteVaultEventParams{ Vault: Vault(fmt.Sprintf("%s.%s", bu.namespace, bu.relation)), Timestamp: ts, Content: f, + Filename: filename, ProgressBar: progress, Signature: hex.EncodeToString(signature), Size: sz, diff --git a/internal/app/vaults_provider.go b/internal/app/vaults_provider.go index 2a48eda..8c65941 100644 --- a/internal/app/vaults_provider.go +++ b/internal/app/vaults_provider.go @@ -13,7 +13,7 @@ type VaultsProvider interface { ListVaults(context.Context, ListVaultsParams) ([]Vault, error) ListVaultEvents(context.Context, ListVaultEventsParams) ([]EventInfo, error) WriteVaultEvent(context.Context, WriteVaultEventParams) error - RetrieveEvent(context.Context, RetrieveEventParams, io.Writer) error + RetrieveEvent(context.Context, RetrieveEventParams, io.Writer) (string, error) } // CreateVaultParams ... @@ -41,6 +41,7 @@ type ListVaultEventsParams struct { type WriteVaultEventParams struct { Vault Vault Signature string + Filename string Timestamp Timestamp Content io.Reader ProgressBar io.Writer @@ -49,5 +50,6 @@ type WriteVaultEventParams struct { // RetrieveEventParams ... type RetrieveEventParams struct { - CID cid.Cid + Timeout int64 + CID cid.Cid } diff --git a/pkg/vaultsprovider/provider.go b/pkg/vaultsprovider/provider.go index e80aefa..44e08fe 100644 --- a/pkg/vaultsprovider/provider.go +++ b/pkg/vaultsprovider/provider.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "regexp" "strings" "time" @@ -131,6 +132,8 @@ func (bp *VaultsProvider) WriteVaultEvent(ctx context.Context, params app.WriteV return fmt.Errorf("could not create request: %s", err) } + req.Header.Add("filename", params.Filename) + q := req.URL.Query() q.Add("timestamp", fmt.Sprint(params.Timestamp.Seconds())) q.Add("signature", fmt.Sprint(params.Signature)) @@ -165,7 +168,9 @@ func (bp *VaultsProvider) WriteVaultEvent(ctx context.Context, params app.WriteV } // RetrieveEvent retrieves an event. -func (bp *VaultsProvider) RetrieveEvent(ctx context.Context, params app.RetrieveEventParams, w io.Writer) error { +func (bp *VaultsProvider) RetrieveEvent( + ctx context.Context, params app.RetrieveEventParams, w io.Writer, +) (string, error) { req, err := http.NewRequestWithContext( ctx, http.MethodGet, @@ -173,27 +178,38 @@ func (bp *VaultsProvider) RetrieveEvent(ctx context.Context, params app.Retrieve nil, ) if err != nil { - return fmt.Errorf("could not create request: %s", err) + return "", fmt.Errorf("could not create request: %s", err) } client := &http.Client{ - Timeout: 0, + Timeout: time.Duration(params.Timeout) * time.Second, } resp, err := client.Do(req) if err != nil { - return fmt.Errorf("request to write vault event failed: %s", err) + return "", fmt.Errorf("request to write vault event failed: %s", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode == http.StatusNotFound { - return errors.New("not found") + return "", errors.New("not found") + } + + re := regexp.MustCompile(`".+"`) + filename := re.FindString(resp.Header.Get("content-disposition")) + if len(filename) == 0 { + return "", errors.New("filename not found") + } + + parts := strings.Split(filename[1:len(filename)-1], "-") + if len(parts) != 2 { + return "", errors.New("filename format is not correct") } if _, err := io.Copy(w, resp.Body); err != nil { - return errors.New("failed copy response body") + return "", errors.New("failed copy response body") } - return nil + return parts[1], nil }