Skip to content

Commit 3f7e4c5

Browse files
Don't die on workspace request failure to Postman API (#4095)
Right now when the Postman source is scanning the postman API, if it gets *any* error back from the API it stops the whole scan. We should probably just log and continue if this happens, so this makes that change for requests to the *workspace* endpoint This issue popped up when investigating a logged problem with making requests to the Postman API and (likely) sending either a bad collection/workspace id or (also possible) sending a collection or workspace id that we don't have permission to explore to the Postman API. Still worth looking more into that, but I think that making these errors less visible is worth the tradeoff for still getting _something_ out of a scan.
1 parent 4fd8aa8 commit 3f7e4c5

File tree

3 files changed

+113
-2
lines changed

3 files changed

+113
-2
lines changed

pkg/sources/postman/postman.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ .
182182
for _, workspaceID := range s.conn.Workspaces {
183183
w, err := s.client.GetWorkspace(ctx, workspaceID)
184184
if err != nil {
185-
return fmt.Errorf("error getting workspace %s: %w", workspaceID, err)
185+
// Log and move on, because sometimes the Postman API seems to give us workspace IDs
186+
// that we don't have access to, so we don't want to kill the scan because of it.
187+
ctx.Logger().Error(err, "error getting workspace %s", workspaceID)
188+
continue
186189
}
187190
s.SetProgressOngoing(fmt.Sprintf("Scanning workspace %s", workspaceID), "")
188191
ctx.Logger().V(2).Info("scanning workspace from workspaces given", "workspace", workspaceID)

pkg/sources/postman/postman_client.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,10 @@ func (c *Client) EnumerateWorkspaces(ctx context.Context) ([]Workspace, error) {
294294
for i, workspace := range workspacesObj.Workspaces {
295295
tempWorkspace, err := c.GetWorkspace(ctx, workspace.ID)
296296
if err != nil {
297-
return nil, fmt.Errorf("could not get workspace %q (%s) during enumeration: %w", workspace.Name, workspace.ID, err)
297+
// Log and move on, because sometimes the Postman API seems to give us workspace IDs
298+
// that we don't have access to, so we don't want to kill the scan because of it.
299+
ctx.Logger().Error(err, "could not get workspace %q (%s) during enumeration", workspace.Name, workspace.ID)
300+
continue
298301
}
299302
workspacesObj.Workspaces[i] = tempWorkspace
300303

pkg/sources/postman/postman_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package postman
22

33
import (
44
"fmt"
5+
"github.com/stretchr/testify/assert"
56
"reflect"
67
"sort"
78
"strings"
@@ -334,6 +335,110 @@ func TestSource_ScanGeneralRateLimit(t *testing.T) {
334335
}
335336
}
336337

338+
func TestSource_BadPostmanWorkspaceApiResponseDoesntEndScan(t *testing.T) {
339+
// The goal here is to make sure that, if we get a bad ID (or other issue) for a workspace and the Postman API
340+
// gives us a non 200 responses, it doesn't stop the whole scan. To do that we're going to have it get a set
341+
// of 3 workspaces from /workspaces/ and then mock all but the last as a bad request. Then we'll check that the
342+
// third one was properly requested.
343+
defer gock.Off()
344+
345+
// We'll use the IDs later in a couple of places
346+
id1WorkspaceBadRequest := "1f0df51a-8658-4ee8-a2a1-d2567dfa09a9"
347+
id2WorkspaceBadId := "a0f46158-1529-11ee-be56-0242ac120002"
348+
id3WorkspaceGood := "f8801e9e-03a4-4c7b-b31e-5db5cd771696"
349+
350+
// Mock the workspace list response. This gives EnumerateWorkspaces what it needs
351+
// to make calls for the individual workspaces details
352+
gock.New("https://api.getpostman.com").
353+
Get("/workspaces").
354+
Reply(200).
355+
JSON(map[string]interface{}{
356+
"workspaces": []map[string]interface{}{
357+
{
358+
"id": id1WorkspaceBadRequest,
359+
"name": "My Workspace",
360+
"createdBy": "12345678",
361+
"type": "personal",
362+
"visibility": "personal",
363+
},
364+
{
365+
"id": id2WorkspaceBadId,
366+
"name": "Private Workspace",
367+
"createdBy": "12345678",
368+
"type": "team",
369+
"visibility": "private",
370+
},
371+
{
372+
"id": id3WorkspaceGood,
373+
"name": "Team Workspace",
374+
"createdBy": "12345678",
375+
"type": "team",
376+
"visibility": "team",
377+
},
378+
},
379+
})
380+
381+
// Make a call for the first workspace respond with a malformed response
382+
gock.New("https://api.getpostman.com").
383+
Get(fmt.Sprintf("/workspaces/%s", id1WorkspaceBadRequest)).
384+
Reply(200).
385+
BodyString("INTENTIONALLY MALFORMED RESPONSE BODY")
386+
// Make a call for the second workspace respond not found
387+
gock.New("https://api.getpostman.com").
388+
Get(fmt.Sprintf("/workspaces/%s", id2WorkspaceBadId)).
389+
Reply(404).
390+
JSON(map[string]interface{}{
391+
"error": map[string]interface{}{
392+
"name": "workspaceNotFoundError",
393+
"mesage": "workspace not found",
394+
"statusCode": 404,
395+
},
396+
})
397+
// Make a call for the third workspace succeed
398+
gock.New("https://api.getpostman.com").
399+
Get(fmt.Sprintf("/workspaces/%s", id3WorkspaceGood)).
400+
Reply(200).
401+
JSON(map[string]interface{}{
402+
"workspace": map[string]interface{}{
403+
"id": id3WorkspaceGood,
404+
"name": "Team Workspace",
405+
"type": "team",
406+
"description": "This is a team workspace.",
407+
"visibility": "team",
408+
"createdBy": "12345678",
409+
"updatedBy": "12345678",
410+
"createdAt": "2022-07-06T16:18:32.000Z",
411+
"updatedAt": "2022-07-06T20:55:13.000Z",
412+
"collections": []map[string]interface{}{},
413+
"environments": []map[string]interface{}{},
414+
"mocks": []map[string]interface{}{},
415+
"monitors": []map[string]interface{}{},
416+
"apis": []map[string]interface{}{},
417+
},
418+
})
419+
420+
// Set up the source and inject the mocks
421+
ctx := context.Background()
422+
s, conn := createTestSource(&sourcespb.Postman{
423+
Credential: &sourcespb.Postman_Token{
424+
Token: "super-secret-token",
425+
},
426+
})
427+
err := s.Init(ctx, "test - postman", 0, 1, false, conn, 1)
428+
if err != nil {
429+
t.Fatalf("init error: %v", err)
430+
}
431+
gock.InterceptClient(s.client.HTTPClient)
432+
defer gock.RestoreClient(s.client.HTTPClient)
433+
434+
// Do the thing
435+
_, _ = s.client.EnumerateWorkspaces(ctx)
436+
437+
// If all the calls were made, then we know the one bad request didn't cause explosions
438+
assert.True(t, gock.IsDone())
439+
440+
}
441+
337442
func TestSource_UnmarshalMultipleHeaderTypes(t *testing.T) {
338443
defer gock.Off()
339444
// Mock a collection with request and response headers of KeyValue type

0 commit comments

Comments
 (0)