@@ -11,6 +11,7 @@ import (
1111 "strconv"
1212
1313 "go.mondoo.com/mql/v13/llx"
14+ "go.mondoo.com/mql/v13/providers/grafana/connection"
1415)
1516
1617// grafanaServiceAccountJSON mirrors one element of the /api/serviceaccounts/search response.
@@ -26,10 +27,10 @@ type grafanaServiceAccountJSON struct {
2627
2728// grafanaServiceAccountsResponse wraps the paginated service accounts endpoint.
2829type grafanaServiceAccountsResponse struct {
29- TotalCount int `json:"totalCount"`
30- ServiceAccounts []grafanaServiceAccountJSON `json:"serviceAccounts"`
31- Page int `json:"page"`
32- PerPage int `json:"perPage"`
30+ TotalCount int `json:"totalCount"`
31+ ServiceAccounts []grafanaServiceAccountJSON `json:"serviceAccounts"`
32+ Page int `json:"page"`
33+ PerPage int `json:"perPage"`
3334}
3435
3536// grafanaTokenJSON mirrors one element of the /api/serviceaccounts/{id}/tokens response.
@@ -45,6 +46,27 @@ type grafanaTokenJSON struct {
4546
4647const serviceAccountPageSize = 1000
4748
49+ // fetchServiceAccountPage fetches a single page of service accounts and closes
50+ // the response body before returning, avoiding FD leaks in pagination loops.
51+ func fetchServiceAccountPage (conn * connection.GrafanaConnection , page int ) (* grafanaServiceAccountsResponse , error ) {
52+ path := fmt .Sprintf ("/api/serviceaccounts/search?perpage=%d&page=%d" , serviceAccountPageSize , page )
53+ resp , err := conn .Get (context .Background (), path )
54+ if err != nil {
55+ return nil , err
56+ }
57+ defer resp .Body .Close ()
58+
59+ if resp .StatusCode != http .StatusOK {
60+ return nil , fmt .Errorf ("grafana: GET /api/serviceaccounts/search returned status %d" , resp .StatusCode )
61+ }
62+
63+ var result grafanaServiceAccountsResponse
64+ if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
65+ return nil , fmt .Errorf ("grafana: decoding /api/serviceaccounts/search response: %w" , err )
66+ }
67+ return & result , nil
68+ }
69+
4870func (g * mqlGrafana ) serviceAccounts () ([]interface {}, error ) {
4971 conn , err := grafanaConnection (g .MqlRuntime )
5072 if err != nil {
@@ -56,20 +78,10 @@ func (g *mqlGrafana) serviceAccounts() ([]interface{}, error) {
5678 // page returns fewer results than requested.
5779 var allSAs []grafanaServiceAccountJSON
5880 for page := 1 ; ; page ++ {
59- path := fmt .Sprintf ("/api/serviceaccounts/search?perpage=%d&page=%d" , serviceAccountPageSize , page )
60- resp , err := conn .Get (context .Background (), path )
81+ result , err := fetchServiceAccountPage (conn , page )
6182 if err != nil {
6283 return nil , err
6384 }
64- defer resp .Body .Close ()
65- if resp .StatusCode != http .StatusOK {
66- return nil , fmt .Errorf ("grafana: GET /api/serviceaccounts/search returned status %d" , resp .StatusCode )
67- }
68-
69- var result grafanaServiceAccountsResponse
70- if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
71- return nil , fmt .Errorf ("grafana: decoding /api/serviceaccounts/search response: %w" , err )
72- }
7385
7486 allSAs = append (allSAs , result .ServiceAccounts ... )
7587
0 commit comments