-
Notifications
You must be signed in to change notification settings - Fork 2.2k
feat: Add GitHub Enterprise App installation repository management APIs #3831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,116 @@ | ||||||||||
| // Copyright 2025 The go-github AUTHORS. All rights reserved. | ||||||||||
| // | ||||||||||
| // Use of this source code is governed by a BSD-style | ||||||||||
| // license that can be found in the LICENSE file. | ||||||||||
|
|
||||||||||
| package github | ||||||||||
|
|
||||||||||
| import ( | ||||||||||
| "context" | ||||||||||
| "fmt" | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| // EnterpriseInstallationRepositoriesOptions specifies the parameters for | ||||||||||
| // EnterpriseService.AddRepositoriesToInstallation and | ||||||||||
| // EnterpriseService.RemoveRepositoriesFromInstallation. | ||||||||||
| type EnterpriseInstallationRepositoriesOptions struct { | ||||||||||
| SelectedRepositoryIDs []int64 `json:"selected_repository_ids"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // EnterpriseInstallationRepositoriesToggleOptions specifies the parameters for | ||||||||||
| // EnterpriseService.ToggleInstallationRepositories. | ||||||||||
| type EnterpriseInstallationRepositoriesToggleOptions struct { | ||||||||||
| RepositorySelection *string `json:"repository_selection,omitempty"` // Can be "all" or "selected" | ||||||||||
| SelectedRepositoryIDs []int64 `json:"selected_repository_ids,omitempty"` | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // ListRepositoriesForOrgInstallation lists the repositories that an enterprise app installation | ||||||||||
| // has access to on an organization. | ||||||||||
| // | ||||||||||
| // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/organization-installations#get-the-repositories-accessible-to-a-given-github-app-installation | ||||||||||
| // | ||||||||||
| //meta:operation GET /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id}/repositories | ||||||||||
| func (s *EnterpriseService) ListRepositoriesForOrgInstallation(ctx context.Context, enterprise, org string, installationID int64, opts *ListOptions) (*ListRepositories, *Response, error) { | ||||||||||
| u := fmt.Sprintf("enterprises/%v/apps/organizations/%v/installations/%v/repositories", enterprise, org, installationID) | ||||||||||
| u, err := addOptions(u, opts) | ||||||||||
| if err != nil { | ||||||||||
| return nil, nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| req, err := s.client.NewRequest("GET", u, nil) | ||||||||||
| if err != nil { | ||||||||||
| return nil, nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| var r *ListRepositories | ||||||||||
| resp, err := s.client.Do(ctx, req, &r) | ||||||||||
| if err != nil { | ||||||||||
| return nil, resp, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return r, resp, nil | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // ToggleInstallationRepositories changes a GitHub App installation's repository access | ||||||||||
| // between all repositories and a selected set. | ||||||||||
| // | ||||||||||
| // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/organization-installations#toggle-installation-repository-access-between-selected-and-all-repositories | ||||||||||
| // | ||||||||||
| //meta:operation PATCH /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id}/repositories | ||||||||||
| func (s *EnterpriseService) ToggleInstallationRepositories(ctx context.Context, enterprise, org string, installationID int64, opts *EnterpriseInstallationRepositoriesToggleOptions) (*ListRepositories, *Response, error) { | ||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This endpoint does not return a schema similar to ListRepositories.
Suggested change
|
||||||||||
| u := fmt.Sprintf("enterprises/%v/apps/organizations/%v/installations/%v/repositories", enterprise, org, installationID) | ||||||||||
| req, err := s.client.NewRequest("PATCH", u, opts) | ||||||||||
| if err != nil { | ||||||||||
| return nil, nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| var r *ListRepositories | ||||||||||
| resp, err := s.client.Do(ctx, req, &r) | ||||||||||
| if err != nil { | ||||||||||
| return nil, resp, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return r, resp, nil | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // AddRepositoriesToInstallation grants repository access for a GitHub App installation. | ||||||||||
| // | ||||||||||
| // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/organization-installations#grant-repository-access-to-an-organization-installation | ||||||||||
| // | ||||||||||
| //meta:operation PATCH /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id}/repositories/add | ||||||||||
| func (s *EnterpriseService) AddRepositoriesToInstallation(ctx context.Context, enterprise, org string, installationID int64, opts EnterpriseInstallationRepositoriesOptions) (*ListRepositories, *Response, error) { | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, it should return |
||||||||||
| u := fmt.Sprintf("enterprises/%v/apps/organizations/%v/installations/%v/repositories/add", enterprise, org, installationID) | ||||||||||
| req, err := s.client.NewRequest("PATCH", u, opts) | ||||||||||
| if err != nil { | ||||||||||
| return nil, nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| var r *ListRepositories | ||||||||||
| resp, err := s.client.Do(ctx, req, &r) | ||||||||||
| if err != nil { | ||||||||||
| return nil, resp, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return r, resp, nil | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // RemoveRepositoriesFromInstallation revokes repository access from a GitHub App installation. | ||||||||||
| // | ||||||||||
| // GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/organization-installations#remove-repository-access-from-an-organization-installation | ||||||||||
| // | ||||||||||
| //meta:operation PATCH /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id}/repositories/remove | ||||||||||
| func (s *EnterpriseService) RemoveRepositoriesFromInstallation(ctx context.Context, enterprise, org string, installationID int64, opts EnterpriseInstallationRepositoriesOptions) (*ListRepositories, *Response, error) { | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, it should return |
||||||||||
| u := fmt.Sprintf("enterprises/%v/apps/organizations/%v/installations/%v/repositories/remove", enterprise, org, installationID) | ||||||||||
| req, err := s.client.NewRequest("PATCH", u, opts) | ||||||||||
| if err != nil { | ||||||||||
| return nil, nil, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| var r *ListRepositories | ||||||||||
| resp, err := s.client.Do(ctx, req, &r) | ||||||||||
| if err != nil { | ||||||||||
| return nil, resp, err | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return r, resp, nil | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| // Copyright 2025 The go-github AUTHORS. All rights reserved. | ||
| // | ||
| // Use of this source code is governed by a BSD-style | ||
| // license that can be found in the LICENSE file. | ||
|
|
||
| package github | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net/http" | ||
| "testing" | ||
|
|
||
| "github.com/google/go-cmp/cmp" | ||
| ) | ||
|
|
||
| func TestEnterpriseService_ListRepositoriesForOrgInstallation(t *testing.T) { | ||
| t.Parallel() | ||
| client, mux, _ := setup(t) | ||
|
|
||
| mux.HandleFunc("/enterprises/e/apps/organizations/o/installations/1/repositories", func(w http.ResponseWriter, r *http.Request) { | ||
| testMethod(t, r, "GET") | ||
| testFormValues(t, r, values{"page": "1"}) | ||
| fmt.Fprint(w, `{"total_count":1, "repositories":[{"id":1}]}`) | ||
| }) | ||
|
|
||
| ctx := t.Context() | ||
| repos, _, err := client.Enterprise.ListRepositoriesForOrgInstallation(ctx, "e", "o", 1, &ListOptions{Page: 1}) | ||
| if err != nil { | ||
| t.Errorf("Enterprise.ListRepositoriesForOrgInstallation returned error: %v", err) | ||
| } | ||
|
|
||
| want := &ListRepositories{TotalCount: Ptr(1), Repositories: []*Repository{{ID: Ptr(int64(1))}}} | ||
| if diff := cmp.Diff(repos, want); diff != "" { | ||
| t.Errorf("Enterprise.ListRepositoriesForOrgInstallation returned diff (-want +got):\n%v", diff) | ||
| } | ||
|
|
||
| const methodName = "ListRepositoriesForOrgInstallation" | ||
| testBadOptions(t, methodName, func() (err error) { | ||
| _, _, err = client.Enterprise.ListRepositoriesForOrgInstallation(ctx, "\n", "\n", -1, &ListOptions{}) | ||
| return err | ||
| }) | ||
|
|
||
| testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { | ||
| _, resp, err := client.Enterprise.ListRepositoriesForOrgInstallation(ctx, "e", "o", 1, &ListOptions{}) | ||
| return resp, err | ||
| }) | ||
| } | ||
|
|
||
| func TestEnterpriseService_ToggleInstallationRepositories(t *testing.T) { | ||
| t.Parallel() | ||
| client, mux, _ := setup(t) | ||
|
|
||
| input := &EnterpriseInstallationRepositoriesToggleOptions{ | ||
| RepositorySelection: String("selected"), | ||
| SelectedRepositoryIDs: []int64{1, 2}, | ||
| } | ||
|
|
||
| mux.HandleFunc("/enterprises/e/apps/organizations/o/installations/1/repositories", func(w http.ResponseWriter, r *http.Request) { | ||
| testMethod(t, r, "PATCH") | ||
| testBody(t, r, `{"repository_selection":"selected","selected_repository_ids":[1,2]}`+"\n") | ||
| fmt.Fprint(w, `{"total_count":2, "repositories":[{"id":1},{"id":2}]}`) | ||
| }) | ||
|
|
||
| ctx := t.Context() | ||
| repos, _, err := client.Enterprise.ToggleInstallationRepositories(ctx, "e", "o", 1, input) | ||
| if err != nil { | ||
| t.Errorf("Enterprise.ToggleInstallationRepositories returned error: %v", err) | ||
| } | ||
|
|
||
| want := &ListRepositories{TotalCount: Ptr(2), Repositories: []*Repository{{ID: Ptr(int64(1))}, {ID: Ptr(int64(2))}}} | ||
| if diff := cmp.Diff(repos, want); diff != "" { | ||
| t.Errorf("Enterprise.ToggleInstallationRepositories returned diff (-want +got):\n%v", diff) | ||
| } | ||
|
|
||
| const methodName = "ToggleInstallationRepositories" | ||
| testBadOptions(t, methodName, func() (err error) { | ||
| _, _, err = client.Enterprise.ToggleInstallationRepositories(ctx, "\n", "\n", -1, input) | ||
| return err | ||
| }) | ||
|
|
||
| testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { | ||
| _, resp, err := client.Enterprise.ToggleInstallationRepositories(ctx, "e", "o", 1, input) | ||
| return resp, err | ||
| }) | ||
| } | ||
|
|
||
| func TestEnterpriseService_AddRepositoriesToInstallation(t *testing.T) { | ||
| t.Parallel() | ||
| client, mux, _ := setup(t) | ||
|
|
||
| input := EnterpriseInstallationRepositoriesOptions{SelectedRepositoryIDs: []int64{1, 2}} | ||
|
|
||
| mux.HandleFunc("/enterprises/e/apps/organizations/o/installations/1/repositories/add", func(w http.ResponseWriter, r *http.Request) { | ||
| testMethod(t, r, "PATCH") | ||
| testBody(t, r, `{"selected_repository_ids":[1,2]}`+"\n") | ||
| fmt.Fprint(w, `{"total_count":2, "repositories":[{"id":1},{"id":2}]}`) | ||
| }) | ||
|
|
||
| ctx := t.Context() | ||
| repos, _, err := client.Enterprise.AddRepositoriesToInstallation(ctx, "e", "o", 1, input) | ||
| if err != nil { | ||
| t.Errorf("Enterprise.AddRepositoriesToInstallation returned error: %v", err) | ||
| } | ||
|
|
||
| want := &ListRepositories{TotalCount: Ptr(2), Repositories: []*Repository{{ID: Ptr(int64(1))}, {ID: Ptr(int64(2))}}} | ||
| if diff := cmp.Diff(repos, want); diff != "" { | ||
| t.Errorf("Enterprise.AddRepositoriesToInstallation returned diff (-want +got):\n%v", diff) | ||
| } | ||
|
|
||
| const methodName = "AddRepositoriesToInstallation" | ||
| testBadOptions(t, methodName, func() (err error) { | ||
| _, _, err = client.Enterprise.AddRepositoriesToInstallation(ctx, "\n", "\n", -1, input) | ||
| return err | ||
| }) | ||
|
|
||
| testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { | ||
| _, resp, err := client.Enterprise.AddRepositoriesToInstallation(ctx, "e", "o", 1, input) | ||
| return resp, err | ||
| }) | ||
| } | ||
|
|
||
| func TestEnterpriseService_RemoveRepositoriesFromInstallation(t *testing.T) { | ||
| t.Parallel() | ||
| client, mux, _ := setup(t) | ||
|
|
||
| input := EnterpriseInstallationRepositoriesOptions{SelectedRepositoryIDs: []int64{1, 2}} | ||
|
|
||
| mux.HandleFunc("/enterprises/e/apps/organizations/o/installations/1/repositories/remove", func(w http.ResponseWriter, r *http.Request) { | ||
| testMethod(t, r, "PATCH") | ||
| testBody(t, r, `{"selected_repository_ids":[1,2]}`+"\n") | ||
| fmt.Fprint(w, `{"total_count":2, "repositories":[{"id":1},{"id":2}]}`) | ||
| }) | ||
|
|
||
| ctx := t.Context() | ||
| repos, _, err := client.Enterprise.RemoveRepositoriesFromInstallation(ctx, "e", "o", 1, input) | ||
| if err != nil { | ||
| t.Errorf("Enterprise.RemoveRepositoriesFromInstallation returned error: %v", err) | ||
| } | ||
|
|
||
| want := &ListRepositories{TotalCount: Ptr(2), Repositories: []*Repository{{ID: Ptr(int64(1))}, {ID: Ptr(int64(2))}}} | ||
| if diff := cmp.Diff(repos, want); diff != "" { | ||
| t.Errorf("Enterprise.RemoveRepositoriesFromInstallation returned diff (-want +got):\n%v", diff) | ||
| } | ||
|
|
||
| const methodName = "RemoveRepositoriesFromInstallation" | ||
| testBadOptions(t, methodName, func() (err error) { | ||
| _, _, err = client.Enterprise.RemoveRepositoriesFromInstallation(ctx, "\n", "\n", -1, input) | ||
| return err | ||
| }) | ||
|
|
||
| testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { | ||
| _, resp, err := client.Enterprise.RemoveRepositoriesFromInstallation(ctx, "e", "o", 1, input) | ||
| return resp, err | ||
| }) | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This endpoint return this response
{ "type": "array", "items": { "title": "Accessible Repository", "description": "A repository that may be made accessible to a GitHub App.", "type": "object", "properties": { "id": { "description": "Unique identifier of the repository", "type": "integer", "format": "int64", "examples": [ 1 ] }, "name": { "description": "The name of the repository.", "type": "string", "examples": [ "Team Environment" ] }, "full_name": { "type": "string", "examples": [ "octocat/Hello-World" ] } }, "required": [ "full_name", "id", "name" ] } }But the function currently returns ListRepositories, which has this struct:
I made a struct named
AccessibleRepositoryin PR #3830, you can re-use it and update this function to return []*AccessibleRepository once my PR is merged.