Skip to content

Commit b480d82

Browse files
authored
feat: Add GitHub Enterprise App installation repository management APIs (#3831)
1 parent d14f4f3 commit b480d82

File tree

4 files changed

+290
-0
lines changed

4 files changed

+290
-0
lines changed

github/enterprise_apps.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2025 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"context"
10+
"fmt"
11+
)
12+
13+
// AppInstallationRepositoriesOptions specifies the parameters for
14+
// EnterpriseService.AddRepositoriesToAppInstallation and
15+
// EnterpriseService.RemoveRepositoriesFromAppInstallation.
16+
type AppInstallationRepositoriesOptions struct {
17+
SelectedRepositoryIDs []int64 `json:"selected_repository_ids"`
18+
}
19+
20+
// UpdateAppInstallationRepositoriesOptions specifies the parameters for
21+
// EnterpriseService.UpdateAppInstallationRepositories.
22+
type UpdateAppInstallationRepositoriesOptions struct {
23+
RepositorySelection *string `json:"repository_selection,omitempty"` // Can be "all" or "selected"
24+
SelectedRepositoryIDs []int64 `json:"selected_repository_ids,omitempty"`
25+
}
26+
27+
// ListRepositoriesForOrgAppInstallation lists the repositories that an enterprise app installation
28+
// has access to on an organization.
29+
//
30+
// 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
31+
//
32+
//meta:operation GET /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id}/repositories
33+
func (s *EnterpriseService) ListRepositoriesForOrgAppInstallation(ctx context.Context, enterprise, org string, installationID int64, opts *ListOptions) ([]*AccessibleRepository, *Response, error) {
34+
u := fmt.Sprintf("enterprises/%v/apps/organizations/%v/installations/%v/repositories", enterprise, org, installationID)
35+
u, err := addOptions(u, opts)
36+
if err != nil {
37+
return nil, nil, err
38+
}
39+
40+
req, err := s.client.NewRequest("GET", u, nil)
41+
if err != nil {
42+
return nil, nil, err
43+
}
44+
45+
var r []*AccessibleRepository
46+
resp, err := s.client.Do(ctx, req, &r)
47+
if err != nil {
48+
return nil, resp, err
49+
}
50+
51+
return r, resp, nil
52+
}
53+
54+
// UpdateAppInstallationRepositories changes a GitHub App installation's repository access
55+
// between all repositories and a selected set.
56+
//
57+
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/organization-installations#toggle-installation-repository-access-between-selected-and-all-repositories
58+
//
59+
//meta:operation PATCH /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id}/repositories
60+
func (s *EnterpriseService) UpdateAppInstallationRepositories(ctx context.Context, enterprise, org string, installationID int64, opts UpdateAppInstallationRepositoriesOptions) (*Installation, *Response, error) {
61+
u := fmt.Sprintf("enterprises/%v/apps/organizations/%v/installations/%v/repositories", enterprise, org, installationID)
62+
req, err := s.client.NewRequest("PATCH", u, opts)
63+
if err != nil {
64+
return nil, nil, err
65+
}
66+
67+
var r *Installation
68+
resp, err := s.client.Do(ctx, req, &r)
69+
if err != nil {
70+
return nil, resp, err
71+
}
72+
73+
return r, resp, nil
74+
}
75+
76+
// AddRepositoriesToAppInstallation grants repository access for a GitHub App installation.
77+
//
78+
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/organization-installations#grant-repository-access-to-an-organization-installation
79+
//
80+
//meta:operation PATCH /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id}/repositories/add
81+
func (s *EnterpriseService) AddRepositoriesToAppInstallation(ctx context.Context, enterprise, org string, installationID int64, opts AppInstallationRepositoriesOptions) ([]*AccessibleRepository, *Response, error) {
82+
u := fmt.Sprintf("enterprises/%v/apps/organizations/%v/installations/%v/repositories/add", enterprise, org, installationID)
83+
req, err := s.client.NewRequest("PATCH", u, opts)
84+
if err != nil {
85+
return nil, nil, err
86+
}
87+
88+
var r []*AccessibleRepository
89+
resp, err := s.client.Do(ctx, req, &r)
90+
if err != nil {
91+
return nil, resp, err
92+
}
93+
94+
return r, resp, nil
95+
}
96+
97+
// RemoveRepositoriesFromAppInstallation revokes repository access from a GitHub App installation.
98+
//
99+
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/organization-installations#remove-repository-access-from-an-organization-installation
100+
//
101+
//meta:operation PATCH /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id}/repositories/remove
102+
func (s *EnterpriseService) RemoveRepositoriesFromAppInstallation(ctx context.Context, enterprise, org string, installationID int64, opts AppInstallationRepositoriesOptions) ([]*AccessibleRepository, *Response, error) {
103+
u := fmt.Sprintf("enterprises/%v/apps/organizations/%v/installations/%v/repositories/remove", enterprise, org, installationID)
104+
req, err := s.client.NewRequest("PATCH", u, opts)
105+
if err != nil {
106+
return nil, nil, err
107+
}
108+
109+
var r []*AccessibleRepository
110+
resp, err := s.client.Do(ctx, req, &r)
111+
if err != nil {
112+
return nil, resp, err
113+
}
114+
115+
return r, resp, nil
116+
}

github/enterprise_apps_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright 2025 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"fmt"
10+
"net/http"
11+
"testing"
12+
13+
"github.com/google/go-cmp/cmp"
14+
)
15+
16+
func TestEnterpriseService_ListRepositoriesForOrgAppInstallation(t *testing.T) {
17+
t.Parallel()
18+
client, mux, _ := setup(t)
19+
20+
mux.HandleFunc("/enterprises/e/apps/organizations/o/installations/1/repositories", func(w http.ResponseWriter, r *http.Request) {
21+
testMethod(t, r, "GET")
22+
testFormValues(t, r, values{"page": "1"})
23+
fmt.Fprint(w, `[{"id":1}]`)
24+
})
25+
26+
ctx := t.Context()
27+
repos, _, err := client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, "e", "o", 1, &ListOptions{Page: 1})
28+
if err != nil {
29+
t.Errorf("Enterprise.ListRepositoriesForOrgAppInstallation returned error: %v", err)
30+
}
31+
32+
want := []*AccessibleRepository{{ID: 1}}
33+
if diff := cmp.Diff(repos, want); diff != "" {
34+
t.Errorf("Enterprise.ListRepositoriesForOrgAppInstallation returned diff (-want +got):\n%v", diff)
35+
}
36+
37+
const methodName = "ListRepositoriesForOrgAppInstallation"
38+
testBadOptions(t, methodName, func() (err error) {
39+
_, _, err = client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, "\n", "\n", -1, &ListOptions{})
40+
return err
41+
})
42+
43+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
44+
_, resp, err := client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, "e", "o", 1, &ListOptions{})
45+
return resp, err
46+
})
47+
}
48+
49+
func TestEnterpriseService_UpdateAppInstallationRepositories(t *testing.T) {
50+
t.Parallel()
51+
client, mux, _ := setup(t)
52+
53+
input := UpdateAppInstallationRepositoriesOptions{
54+
RepositorySelection: String("selected"),
55+
SelectedRepositoryIDs: []int64{1, 2},
56+
}
57+
58+
mux.HandleFunc("/enterprises/e/apps/organizations/o/installations/1/repositories", func(w http.ResponseWriter, r *http.Request) {
59+
testMethod(t, r, "PATCH")
60+
testBody(t, r, `{"repository_selection":"selected","selected_repository_ids":[1,2]}`+"\n")
61+
fmt.Fprint(w, `{"id":1, "repository_selection":"selected"}`)
62+
})
63+
64+
ctx := t.Context()
65+
inst, _, err := client.Enterprise.UpdateAppInstallationRepositories(ctx, "e", "o", 1, input)
66+
if err != nil {
67+
t.Errorf("Enterprise.UpdateAppInstallationRepositories returned error: %v", err)
68+
}
69+
70+
want := &Installation{ID: Ptr(int64(1)), RepositorySelection: Ptr("selected")}
71+
if diff := cmp.Diff(inst, want); diff != "" {
72+
t.Errorf("Enterprise.UpdateAppInstallationRepositories returned diff (-want +got):\n%v", diff)
73+
}
74+
75+
const methodName = "UpdateAppInstallationRepositories"
76+
testBadOptions(t, methodName, func() (err error) {
77+
_, _, err = client.Enterprise.UpdateAppInstallationRepositories(ctx, "\n", "\n", -1, input)
78+
return err
79+
})
80+
81+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
82+
_, resp, err := client.Enterprise.UpdateAppInstallationRepositories(ctx, "e", "o", 1, input)
83+
return resp, err
84+
})
85+
}
86+
87+
func TestEnterpriseService_AddRepositoriesToAppInstallation(t *testing.T) {
88+
t.Parallel()
89+
client, mux, _ := setup(t)
90+
91+
input := AppInstallationRepositoriesOptions{SelectedRepositoryIDs: []int64{1, 2}}
92+
93+
mux.HandleFunc("/enterprises/e/apps/organizations/o/installations/1/repositories/add", func(w http.ResponseWriter, r *http.Request) {
94+
testMethod(t, r, "PATCH")
95+
testBody(t, r, `{"selected_repository_ids":[1,2]}`+"\n")
96+
fmt.Fprint(w, `[{"id":1},{"id":2}]`)
97+
})
98+
99+
ctx := t.Context()
100+
repos, _, err := client.Enterprise.AddRepositoriesToAppInstallation(ctx, "e", "o", 1, input)
101+
if err != nil {
102+
t.Errorf("Enterprise.AddRepositoriesToAppInstallation returned error: %v", err)
103+
}
104+
105+
want := []*AccessibleRepository{{ID: 1}, {ID: 2}}
106+
if diff := cmp.Diff(repos, want); diff != "" {
107+
t.Errorf("Enterprise.AddRepositoriesToAppInstallation returned diff (-want +got):\n%v", diff)
108+
}
109+
110+
const methodName = "AddRepositoriesToAppInstallation"
111+
testBadOptions(t, methodName, func() (err error) {
112+
_, _, err = client.Enterprise.AddRepositoriesToAppInstallation(ctx, "\n", "\n", -1, input)
113+
return err
114+
})
115+
116+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
117+
_, resp, err := client.Enterprise.AddRepositoriesToAppInstallation(ctx, "e", "o", 1, input)
118+
return resp, err
119+
})
120+
}
121+
122+
func TestEnterpriseService_RemoveRepositoriesFromAppInstallation(t *testing.T) {
123+
t.Parallel()
124+
client, mux, _ := setup(t)
125+
126+
input := AppInstallationRepositoriesOptions{SelectedRepositoryIDs: []int64{1, 2}}
127+
128+
mux.HandleFunc("/enterprises/e/apps/organizations/o/installations/1/repositories/remove", func(w http.ResponseWriter, r *http.Request) {
129+
testMethod(t, r, "PATCH")
130+
testBody(t, r, `{"selected_repository_ids":[1,2]}`+"\n")
131+
fmt.Fprint(w, `[{"id":1},{"id":2}]`)
132+
})
133+
134+
ctx := t.Context()
135+
repos, _, err := client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, "e", "o", 1, input)
136+
if err != nil {
137+
t.Errorf("Enterprise.RemoveRepositoriesFromAppInstallation returned error: %v", err)
138+
}
139+
140+
want := []*AccessibleRepository{{ID: 1}, {ID: 2}}
141+
if diff := cmp.Diff(repos, want); diff != "" {
142+
t.Errorf("Enterprise.RemoveRepositoriesFromAppInstallation returned diff (-want +got):\n%v", diff)
143+
}
144+
145+
const methodName = "RemoveRepositoriesFromAppInstallation"
146+
testBadOptions(t, methodName, func() (err error) {
147+
_, _, err = client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, "\n", "\n", -1, input)
148+
return err
149+
})
150+
151+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
152+
_, resp, err := client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, "e", "o", 1, input)
153+
return resp, err
154+
})
155+
}

github/github-accessors.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

github/github-accessors_test.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)