diff --git a/github/enterprise_apps.go b/github/enterprise_apps.go new file mode 100644 index 00000000000..2e43a142305 --- /dev/null +++ b/github/enterprise_apps.go @@ -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) { + 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) { + 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) { + 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 +} diff --git a/github/enterprise_apps_test.go b/github/enterprise_apps_test.go new file mode 100644 index 00000000000..29a58e0439d --- /dev/null +++ b/github/enterprise_apps_test.go @@ -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 + }) +} diff --git a/github/github-accessors.go b/github/github-accessors.go index 2a1a9394f67..ef96f32570b 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -9254,6 +9254,14 @@ func (e *EnterpriseCustomPropertiesValues) GetOrganizationLogin() string { return *e.OrganizationLogin } +// GetRepositorySelection returns the RepositorySelection field if it's non-nil, zero value otherwise. +func (e *EnterpriseInstallationRepositoriesToggleOptions) GetRepositorySelection() string { + if e == nil || e.RepositorySelection == nil { + return "" + } + return *e.RepositorySelection +} + // GetEnterpriseServerUser returns the EnterpriseServerUser field if it's non-nil, zero value otherwise. func (e *EnterpriseLicensedUsers) GetEnterpriseServerUser() bool { if e == nil || e.EnterpriseServerUser == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index c926697944f..8e39eab64f3 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -11985,6 +11985,17 @@ func TestEnterpriseCustomPropertiesValues_GetOrganizationLogin(tt *testing.T) { e.GetOrganizationLogin() } +func TestEnterpriseInstallationRepositoriesToggleOptions_GetRepositorySelection(tt *testing.T) { + tt.Parallel() + var zeroValue string + e := &EnterpriseInstallationRepositoriesToggleOptions{RepositorySelection: &zeroValue} + e.GetRepositorySelection() + e = &EnterpriseInstallationRepositoriesToggleOptions{} + e.GetRepositorySelection() + e = nil + e.GetRepositorySelection() +} + func TestEnterpriseLicensedUsers_GetEnterpriseServerUser(tt *testing.T) { tt.Parallel() var zeroValue bool