diff --git a/github/code-scanning_test.go b/github/code-scanning_test.go index a081a6ba5f1..1edbebae0fa 100644 --- a/github/code-scanning_test.go +++ b/github/code-scanning_test.go @@ -93,7 +93,7 @@ func TestCodeScanningService_UploadSarif(t *testing.T) { return err }) - testNewRequestAndDoFailureCategory(t, methodName, client, codeScanningUploadCategory, func() (*Response, error) { + testNewRequestAndDoFailureCategory(t, methodName, client, CodeScanningUploadCategory, func() (*Response, error) { _, resp, err := client.CodeScanning.UploadSarif(ctx, "o", "r", sarifAnalysis) return resp, err }) diff --git a/github/enterprise_audit_log_test.go b/github/enterprise_audit_log_test.go index 0d9e44a3eb6..1ae94babb75 100644 --- a/github/enterprise_audit_log_test.go +++ b/github/enterprise_audit_log_test.go @@ -84,7 +84,7 @@ func TestEnterpriseService_GetAuditLog(t *testing.T) { return err }) - testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + testNewRequestAndDoFailureCategory(t, methodName, client, AuditLogCategory, func() (*Response, error) { got, resp, err := client.Enterprise.GetAuditLog(ctx, "o", &GetAuditLogOptions{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) diff --git a/github/github-accessors.go b/github/github-accessors.go index 89728d38df9..4555496c940 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -17534,6 +17534,14 @@ func (r *RateLimits) GetActionsRunnerRegistration() *Rate { return r.ActionsRunnerRegistration } +// GetAuditLog returns the AuditLog field. +func (r *RateLimits) GetAuditLog() *Rate { + if r == nil { + return nil + } + return r.AuditLog +} + // GetCodeScanningUpload returns the CodeScanningUpload field. func (r *RateLimits) GetCodeScanningUpload() *Rate { if r == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 1dcb4dc2bb9..4dd9b3f647a 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -20310,6 +20310,13 @@ func TestRateLimits_GetActionsRunnerRegistration(tt *testing.T) { r.GetActionsRunnerRegistration() } +func TestRateLimits_GetAuditLog(tt *testing.T) { + r := &RateLimits{} + r.GetAuditLog() + r = nil + r.GetAuditLog() +} + func TestRateLimits_GetCodeScanningUpload(tt *testing.T) { r := &RateLimits{} r.GetCodeScanningUpload() diff --git a/github/github.go b/github/github.go index 05c3acd55b7..bf0b5715a89 100644 --- a/github/github.go +++ b/github/github.go @@ -170,7 +170,7 @@ type Client struct { UserAgent string rateMu sync.Mutex - rateLimits [categories]Rate // Rate limits for the client as determined by the most recent API calls. + rateLimits [Categories]Rate // Rate limits for the client as determined by the most recent API calls. secondaryRateLimitReset time.Time // Secondary rate limit reset for the client as determined by the most recent API calls. common service // Reuse a single struct instead of allocating one for each service on the heap. @@ -821,7 +821,7 @@ func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, erro req = withContext(ctx, req) - rateLimitCategory := category(req.Method, req.URL.Path) + rateLimitCategory := GetRateLimitCategory(req.Method, req.URL.Path) if bypass := ctx.Value(bypassRateLimitCheck); bypass == nil { // If we've hit rate limit, don't make further requests before Reset time. @@ -937,7 +937,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res // current client state in order to quickly check if *RateLimitError can be immediately returned // from Client.Do, and if so, returns it so that Client.Do can skip making a network API call unnecessarily. // Otherwise it returns nil, and Client.Do should proceed normally. -func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory rateLimitCategory) *RateLimitError { +func (c *Client) checkRateLimitBeforeDo(req *http.Request, rateLimitCategory RateLimitCategory) *RateLimitError { c.rateMu.Lock() rate := c.rateLimits[rateLimitCategory] c.rateMu.Unlock() @@ -1303,65 +1303,70 @@ func parseBoolResponse(err error) (bool, error) { return false, err } -type rateLimitCategory uint8 +type RateLimitCategory uint8 const ( - coreCategory rateLimitCategory = iota - searchCategory - graphqlCategory - integrationManifestCategory - sourceImportCategory - codeScanningUploadCategory - actionsRunnerRegistrationCategory - scimCategory - dependencySnapshotsCategory - codeSearchCategory - - categories // An array of this length will be able to contain all rate limit categories. + CoreCategory RateLimitCategory = iota + SearchCategory + GraphqlCategory + IntegrationManifestCategory + SourceImportCategory + CodeScanningUploadCategory + ActionsRunnerRegistrationCategory + ScimCategory + DependencySnapshotsCategory + CodeSearchCategory + AuditLogCategory + + Categories // An array of this length will be able to contain all rate limit categories. ) -// category returns the rate limit category of the endpoint, determined by HTTP method and Request.URL.Path. -func category(method, path string) rateLimitCategory { +// GetRateLimitCategory returns the rate limit RateLimitCategory of the endpoint, determined by HTTP method and Request.URL.Path. +func GetRateLimitCategory(method, path string) RateLimitCategory { switch { // https://docs.github.com/rest/rate-limit#about-rate-limits default: // NOTE: coreCategory is returned for actionsRunnerRegistrationCategory too, // because no API found for this category. - return coreCategory + return CoreCategory // https://docs.github.com/en/rest/search/search#search-code case strings.HasPrefix(path, "/search/code") && method == http.MethodGet: - return codeSearchCategory + return CodeSearchCategory case strings.HasPrefix(path, "/search/"): - return searchCategory + return SearchCategory case path == "/graphql": - return graphqlCategory + return GraphqlCategory case strings.HasPrefix(path, "/app-manifests/") && strings.HasSuffix(path, "/conversions") && method == http.MethodPost: - return integrationManifestCategory + return IntegrationManifestCategory // https://docs.github.com/rest/migrations/source-imports#start-an-import case strings.HasPrefix(path, "/repos/") && strings.HasSuffix(path, "/import") && method == http.MethodPut: - return sourceImportCategory + return SourceImportCategory // https://docs.github.com/rest/code-scanning#upload-an-analysis-as-sarif-data case strings.HasSuffix(path, "/code-scanning/sarifs"): - return codeScanningUploadCategory + return CodeScanningUploadCategory // https://docs.github.com/enterprise-cloud@latest/rest/scim case strings.HasPrefix(path, "/scim/"): - return scimCategory + return ScimCategory // https://docs.github.com/en/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository case strings.HasPrefix(path, "/repos/") && strings.HasSuffix(path, "/dependency-graph/snapshots") && method == http.MethodPost: - return dependencySnapshotsCategory + return DependencySnapshotsCategory + + // https://docs.github.com/en/enterprise-cloud@latest/rest/orgs/orgs?apiVersion=2022-11-28#get-the-audit-log-for-an-organization + case strings.HasSuffix(path, "/audit-log"): + return AuditLogCategory } } diff --git a/github/github_test.go b/github/github_test.go index 9a369a25fd4..efc42d432fb 100644 --- a/github/github_test.go +++ b/github/github_test.go @@ -212,11 +212,11 @@ func testBadOptions(t *testing.T, methodName string, f func() error) { // Method f should be a regular call that would normally succeed, but // should return an error when NewRequest or s.client.Do fails. func testNewRequestAndDoFailure(t *testing.T, methodName string, client *Client, f func() (*Response, error)) { - testNewRequestAndDoFailureCategory(t, methodName, client, coreCategory, f) + testNewRequestAndDoFailureCategory(t, methodName, client, CoreCategory, f) } // testNewRequestAndDoFailureCategory works Like testNewRequestAndDoFailure, but allows setting the category -func testNewRequestAndDoFailureCategory(t *testing.T, methodName string, client *Client, category rateLimitCategory, f func() (*Response, error)) { +func testNewRequestAndDoFailureCategory(t *testing.T, methodName string, client *Client, category RateLimitCategory, f func() (*Response, error)) { t.Helper() if methodName == "" { t.Error("testNewRequestAndDoFailure: must supply method methodName") @@ -1132,68 +1132,73 @@ func TestDo_rateLimitCategory(t *testing.T) { tests := []struct { method string url string - category rateLimitCategory + category RateLimitCategory }{ { method: http.MethodGet, url: "/", - category: coreCategory, + category: CoreCategory, }, { method: http.MethodGet, url: "/search/issues?q=rate", - category: searchCategory, + category: SearchCategory, }, { method: http.MethodGet, url: "/graphql", - category: graphqlCategory, + category: GraphqlCategory, }, { method: http.MethodPost, url: "/app-manifests/code/conversions", - category: integrationManifestCategory, + category: IntegrationManifestCategory, }, { method: http.MethodGet, url: "/app-manifests/code/conversions", - category: coreCategory, // only POST requests are in the integration manifest category + category: CoreCategory, // only POST requests are in the integration manifest category }, { method: http.MethodPut, url: "/repos/google/go-github/import", - category: sourceImportCategory, + category: SourceImportCategory, }, { method: http.MethodGet, url: "/repos/google/go-github/import", - category: coreCategory, // only PUT requests are in the source import category + category: CoreCategory, // only PUT requests are in the source import category }, { method: http.MethodPost, url: "/repos/google/go-github/code-scanning/sarifs", - category: codeScanningUploadCategory, + category: CodeScanningUploadCategory, }, { method: http.MethodGet, url: "/scim/v2/organizations/ORG/Users", - category: scimCategory, + category: ScimCategory, }, { method: http.MethodPost, url: "/repos/google/go-github/dependency-graph/snapshots", - category: dependencySnapshotsCategory, + category: DependencySnapshotsCategory, }, { method: http.MethodGet, url: "/search/code?q=rate", - category: codeSearchCategory, + category: CodeSearchCategory, + }, + { + method: http.MethodGet, + url: "/orgs/google/audit-log", + category: AuditLogCategory, }, // missing a check for actionsRunnerRegistrationCategory: API not found } for _, tt := range tests { - if got, want := category(tt.method, tt.url), tt.category; got != want { + if got, want := GetRateLimitCategory(tt.method, tt.url), tt.category; got != want { t.Errorf("expecting category %v, found %v", got, want) } } diff --git a/github/orgs_audit_log_test.go b/github/orgs_audit_log_test.go index 7c8de74c650..feebd0affc7 100644 --- a/github/orgs_audit_log_test.go +++ b/github/orgs_audit_log_test.go @@ -164,7 +164,7 @@ func TestOrganizationService_GetAuditLog(t *testing.T) { return err }) - testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + testNewRequestAndDoFailureCategory(t, methodName, client, AuditLogCategory, func() (*Response, error) { got, resp, err := client.Organizations.GetAuditLog(ctx, "o", &GetAuditLogOptions{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) diff --git a/github/rate_limit.go b/github/rate_limit.go index febe5edccfb..5b01b573d8a 100644 --- a/github/rate_limit.go +++ b/github/rate_limit.go @@ -54,6 +54,7 @@ type RateLimits struct { SCIM *Rate `json:"scim"` DependencySnapshots *Rate `json:"dependency_snapshots"` CodeSearch *Rate `json:"code_search"` + AuditLog *Rate `json:"audit_log"` } func (r RateLimits) String() string { @@ -85,34 +86,37 @@ func (s *RateLimitService) Get(ctx context.Context) (*RateLimits, *Response, err if response.Resources != nil { s.client.rateMu.Lock() if response.Resources.Core != nil { - s.client.rateLimits[coreCategory] = *response.Resources.Core + s.client.rateLimits[CoreCategory] = *response.Resources.Core } if response.Resources.Search != nil { - s.client.rateLimits[searchCategory] = *response.Resources.Search + s.client.rateLimits[SearchCategory] = *response.Resources.Search } if response.Resources.GraphQL != nil { - s.client.rateLimits[graphqlCategory] = *response.Resources.GraphQL + s.client.rateLimits[GraphqlCategory] = *response.Resources.GraphQL } if response.Resources.IntegrationManifest != nil { - s.client.rateLimits[integrationManifestCategory] = *response.Resources.IntegrationManifest + s.client.rateLimits[IntegrationManifestCategory] = *response.Resources.IntegrationManifest } if response.Resources.SourceImport != nil { - s.client.rateLimits[sourceImportCategory] = *response.Resources.SourceImport + s.client.rateLimits[SourceImportCategory] = *response.Resources.SourceImport } if response.Resources.CodeScanningUpload != nil { - s.client.rateLimits[codeScanningUploadCategory] = *response.Resources.CodeScanningUpload + s.client.rateLimits[CodeScanningUploadCategory] = *response.Resources.CodeScanningUpload } if response.Resources.ActionsRunnerRegistration != nil { - s.client.rateLimits[actionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration + s.client.rateLimits[ActionsRunnerRegistrationCategory] = *response.Resources.ActionsRunnerRegistration } if response.Resources.SCIM != nil { - s.client.rateLimits[scimCategory] = *response.Resources.SCIM + s.client.rateLimits[ScimCategory] = *response.Resources.SCIM } if response.Resources.DependencySnapshots != nil { - s.client.rateLimits[dependencySnapshotsCategory] = *response.Resources.DependencySnapshots + s.client.rateLimits[DependencySnapshotsCategory] = *response.Resources.DependencySnapshots } if response.Resources.CodeSearch != nil { - s.client.rateLimits[codeSearchCategory] = *response.Resources.CodeSearch + s.client.rateLimits[CodeSearchCategory] = *response.Resources.CodeSearch + } + if response.Resources.AuditLog != nil { + s.client.rateLimits[AuditLogCategory] = *response.Resources.AuditLog } s.client.rateMu.Unlock() } diff --git a/github/rate_limit_test.go b/github/rate_limit_test.go index da8a68c9106..6ff87824dda 100644 --- a/github/rate_limit_test.go +++ b/github/rate_limit_test.go @@ -27,8 +27,9 @@ func TestRateLimits_String(t *testing.T) { SCIM: &Rate{}, DependencySnapshots: &Rate{}, CodeSearch: &Rate{}, + AuditLog: &Rate{}, } - want := `github.RateLimits{Core:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, Search:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, GraphQL:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, IntegrationManifest:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SourceImport:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeScanningUpload:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, ActionsRunnerRegistration:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SCIM:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, DependencySnapshots:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeSearch:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}}` + want := `github.RateLimits{Core:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, Search:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, GraphQL:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, IntegrationManifest:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SourceImport:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeScanningUpload:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, ActionsRunnerRegistration:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, SCIM:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, DependencySnapshots:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, CodeSearch:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}, AuditLog:github.Rate{Limit:0, Remaining:0, Reset:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}}` if got := v.String(); got != want { t.Errorf("RateLimits.String = %v, want %v", got, want) } @@ -50,7 +51,8 @@ func TestRateLimits(t *testing.T) { "actions_runner_registration": {"limit":8,"remaining":7,"reset":1372700879}, "scim": {"limit":9,"remaining":8,"reset":1372700880}, "dependency_snapshots": {"limit":10,"remaining":9,"reset":1372700881}, - "code_search": {"limit":11,"remaining":10,"reset":1372700882} + "code_search": {"limit":11,"remaining":10,"reset":1372700882}, + "audit_log": {"limit": 12,"remaining":11,"reset":1372700883} }}`) }) @@ -111,54 +113,63 @@ func TestRateLimits(t *testing.T) { Remaining: 10, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 2, 0, time.UTC).Local()}, }, + AuditLog: &Rate{ + Limit: 12, + Remaining: 11, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 3, 0, time.UTC).Local()}, + }, } if !cmp.Equal(rate, want) { t.Errorf("RateLimits returned %+v, want %+v", rate, want) } tests := []struct { - category rateLimitCategory + category RateLimitCategory rate *Rate }{ { - category: coreCategory, + category: CoreCategory, rate: want.Core, }, { - category: searchCategory, + category: SearchCategory, rate: want.Search, }, { - category: graphqlCategory, + category: GraphqlCategory, rate: want.GraphQL, }, { - category: integrationManifestCategory, + category: IntegrationManifestCategory, rate: want.IntegrationManifest, }, { - category: sourceImportCategory, + category: SourceImportCategory, rate: want.SourceImport, }, { - category: codeScanningUploadCategory, + category: CodeScanningUploadCategory, rate: want.CodeScanningUpload, }, { - category: actionsRunnerRegistrationCategory, + category: ActionsRunnerRegistrationCategory, rate: want.ActionsRunnerRegistration, }, { - category: scimCategory, + category: ScimCategory, rate: want.SCIM, }, { - category: dependencySnapshotsCategory, + category: DependencySnapshotsCategory, rate: want.DependencySnapshots, }, { - category: codeSearchCategory, + category: CodeSearchCategory, rate: want.CodeSearch, }, + { + category: AuditLogCategory, + rate: want.AuditLog, + }, } for _, tt := range tests { @@ -185,7 +196,7 @@ func TestRateLimits_overQuota(t *testing.T) { client, mux, _, teardown := setup() defer teardown() - client.rateLimits[coreCategory] = Rate{ + client.rateLimits[CoreCategory] = Rate{ Limit: 1, Remaining: 0, Reset: Timestamp{time.Now().Add(time.Hour).Local()}, @@ -201,7 +212,8 @@ func TestRateLimits_overQuota(t *testing.T) { "actions_runner_registration": {"limit":8,"remaining":7,"reset":1372700879}, "scim": {"limit":9,"remaining":8,"reset":1372700880}, "dependency_snapshots": {"limit":10,"remaining":9,"reset":1372700881}, - "code_search": {"limit":11,"remaining":10,"reset":1372700882} + "code_search": {"limit":11,"remaining":10,"reset":1372700882}, + "audit_log": {"limit":12,"remaining":11,"reset":1372700883} }}`) }) @@ -262,55 +274,64 @@ func TestRateLimits_overQuota(t *testing.T) { Remaining: 10, Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 2, 0, time.UTC).Local()}, }, + AuditLog: &Rate{ + Limit: 12, + Remaining: 11, + Reset: Timestamp{time.Date(2013, time.July, 1, 17, 48, 3, 0, time.UTC).Local()}, + }, } if !cmp.Equal(rate, want) { t.Errorf("RateLimits returned %+v, want %+v", rate, want) } tests := []struct { - category rateLimitCategory + category RateLimitCategory rate *Rate }{ { - category: coreCategory, + category: CoreCategory, rate: want.Core, }, { - category: searchCategory, + category: SearchCategory, rate: want.Search, }, { - category: graphqlCategory, + category: GraphqlCategory, rate: want.GraphQL, }, { - category: integrationManifestCategory, + category: IntegrationManifestCategory, rate: want.IntegrationManifest, }, { - category: sourceImportCategory, + category: SourceImportCategory, rate: want.SourceImport, }, { - category: codeScanningUploadCategory, + category: CodeScanningUploadCategory, rate: want.CodeScanningUpload, }, { - category: actionsRunnerRegistrationCategory, + category: ActionsRunnerRegistrationCategory, rate: want.ActionsRunnerRegistration, }, { - category: scimCategory, + category: ScimCategory, rate: want.SCIM, }, { - category: dependencySnapshotsCategory, + category: DependencySnapshotsCategory, rate: want.DependencySnapshots, }, { - category: codeSearchCategory, + category: CodeSearchCategory, rate: want.CodeSearch, }, + { + category: AuditLogCategory, + rate: want.AuditLog, + }, } for _, tt := range tests { if got, want := client.rateLimits[tt.category], *tt.rate; got != want { @@ -373,6 +394,11 @@ func TestRateLimits_Marshal(t *testing.T) { Remaining: 1, Reset: Timestamp{referenceTime}, }, + AuditLog: &Rate{ + Limit: 1, + Remaining: 1, + Reset: Timestamp{referenceTime}, + }, } want := `{ @@ -425,6 +451,11 @@ func TestRateLimits_Marshal(t *testing.T) { "limit": 1, "remaining": 1, "reset": ` + referenceTimeStr + ` + }, + "audit_log": { + "limit": 1, + "remaining": 1, + "reset": ` + referenceTimeStr + ` } }`