Skip to content

Commit a230f95

Browse files
authored
Add metrics to the Postman source (#4142)
* Add a couple of metrics to the Postman source * Add metrics file * Remove job ID from metrics * Use new metrics constructor name * Also use new metrics constructor arguments * Write a good comment for the metrics member in the API client struct * Log an error when we can't parse the value of RateLimit-Remaining-Month
1 parent 3e87718 commit a230f95

File tree

4 files changed

+78
-4
lines changed

4 files changed

+78
-4
lines changed

pkg/sources/postman/metrics.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package postman
2+
3+
import (
4+
"github.com/prometheus/client_golang/prometheus"
5+
"github.com/prometheus/client_golang/prometheus/promauto"
6+
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
7+
)
8+
9+
type metrics struct {
10+
apiRequests *prometheus.CounterVec
11+
apiMonthlyRequestsRemaining *prometheus.GaugeVec
12+
}
13+
14+
var (
15+
postmanAPIRequestsMetric = promauto.NewCounterVec(prometheus.CounterOpts{
16+
Namespace: common.MetricsNamespace,
17+
Subsystem: common.MetricsSubsystem,
18+
Name: "postman_api_requests",
19+
Help: "Total number of API requests made to Postman.",
20+
},
21+
[]string{"source_name", "endpoint"})
22+
23+
postmanAPIMonthlyRequestsRemaining = promauto.NewGaugeVec(prometheus.GaugeOpts{
24+
Namespace: common.MetricsNamespace,
25+
Subsystem: common.MetricsSubsystem,
26+
Name: "postman_api_monthly_requests_remaining",
27+
Help: "Total number Postman API requests remaining this month.",
28+
},
29+
[]string{"source_name"})
30+
)
31+
32+
func newMetrics(sourceName string) *metrics {
33+
return &metrics{
34+
apiRequests: postmanAPIRequestsMetric.MustCurryWith(map[string]string{
35+
"source_name": sourceName,
36+
}),
37+
apiMonthlyRequestsRemaining: postmanAPIMonthlyRequestsRemaining.MustCurryWith(map[string]string{
38+
"source_name": sourceName,
39+
}),
40+
}
41+
}

pkg/sources/postman/postman.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ type Source struct {
4949
keywords map[string]struct{}
5050
sub *Substitution
5151

52+
metrics *metrics
53+
5254
sources.Progress
5355
sources.CommonSourceUnitUnmarshaller
5456
}
@@ -102,6 +104,7 @@ func (s *Source) Init(ctx context.Context, name string, jobId sources.JobID, sou
102104
s.verify = verify
103105
s.keywords = make(map[string]struct{})
104106
s.sub = NewSubstitution()
107+
s.metrics = newMetrics(name)
105108

106109
var conn sourcespb.Postman
107110
if err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{}); err != nil {
@@ -115,7 +118,7 @@ func (s *Source) Init(ctx context.Context, name string, jobId sources.JobID, sou
115118
if conn.GetToken() == "" {
116119
return errors.New("Postman token is empty")
117120
}
118-
s.client = NewClient(conn.GetToken())
121+
s.client = NewClient(conn.GetToken(), s.metrics)
119122
s.client.HTTPClient = common.RetryableHTTPClientTimeout(10)
120123
log.RedactGlobally(conn.GetToken())
121124
case *sourcespb.Postman_Unauthenticated:

pkg/sources/postman/postman_client.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"net/http"
8+
"strconv"
89
"time"
910

1011
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
@@ -197,10 +198,15 @@ type Client struct {
197198

198199
// Rate limiter needed for Postman API. General rate limit is 300 requests per minute.
199200
GeneralRateLimiter *rate.Limiter
201+
202+
// Postman has a monthly rate limit, so we need to persist our API call
203+
// counts outside of individual scans to track it effectively. We currently
204+
// use metrics and alerts for this.
205+
Metrics *metrics
200206
}
201207

202208
// NewClient returns a new Postman API client.
203-
func NewClient(postmanToken string) *Client {
209+
func NewClient(postmanToken string, metrics *metrics) *Client {
204210
bh := map[string]string{
205211
"Content-Type": defaultContentType,
206212
"User-Agent": userAgent,
@@ -212,6 +218,7 @@ func NewClient(postmanToken string) *Client {
212218
Headers: bh,
213219
WorkspaceAndCollectionRateLimiter: rate.NewLimiter(rate.Every(time.Second), 1),
214220
GeneralRateLimiter: rate.NewLimiter(rate.Every(time.Second/5), 1),
221+
Metrics: metrics,
215222
}
216223

217224
return c
@@ -259,6 +266,26 @@ func (c *Client) getPostmanResponseBodyBytes(ctx context.Context, url string, he
259266
}
260267
defer resp.Body.Close()
261268

269+
c.Metrics.apiRequests.WithLabelValues(url).Inc()
270+
271+
rateLimitRemainingMonthValue := resp.Header.Get("RateLimit-Remaining-Month")
272+
if rateLimitRemainingMonthValue == "" {
273+
rateLimitRemainingMonthValue = resp.Header.Get("X-RateLimit-Remaining-Month")
274+
}
275+
276+
if rateLimitRemainingMonthValue != "" {
277+
rateLimitRemainingMonth, err := strconv.Atoi(rateLimitRemainingMonthValue)
278+
if err != nil {
279+
ctx.Logger().Error(err, "Couldn't convert RateLimit-Remaining-Month to an int",
280+
"header_value", rateLimitRemainingMonthValue,
281+
)
282+
} else {
283+
c.Metrics.apiMonthlyRequestsRemaining.WithLabelValues().Set(
284+
float64(rateLimitRemainingMonth),
285+
)
286+
}
287+
}
288+
262289
body, err := io.ReadAll(resp.Body)
263290
if err != nil {
264291
return nil, fmt.Errorf("could not read postman response body: %w", err)

pkg/sources/postman/postman_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package postman
22

33
import (
44
"fmt"
5-
"github.com/stretchr/testify/assert"
65
"reflect"
76
"sort"
87
"strings"
98
"testing"
109
"time"
1110

11+
"github.com/stretchr/testify/assert"
12+
1213
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
1314
"gopkg.in/h2non/gock.v1"
1415

@@ -18,7 +19,9 @@ import (
1819
)
1920

2021
func createTestSource(src *sourcespb.Postman) (*Source, *anypb.Any) {
21-
s := &Source{}
22+
s := &Source{
23+
metrics: newMetrics("Test Source"),
24+
}
2225
conn, err := anypb.New(src)
2326
if err != nil {
2427
panic(err)

0 commit comments

Comments
 (0)