Skip to content
116 changes: 116 additions & 0 deletions github/enterprise_apps.go
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"
)

// AppInstallationRepositoriesOptions specifies the parameters for
// EnterpriseService.AddRepositoriesToAppInstallation and
// EnterpriseService.RemoveRepositoriesFromAppInstallation.
type AppInstallationRepositoriesOptions struct {
SelectedRepositoryIDs []int64 `json:"selected_repository_ids"`
}

// UpdateAppInstallationRepositoriesOptions specifies the parameters for
// EnterpriseService.UpdateAppInstallationRepositories.
type UpdateAppInstallationRepositoriesOptions struct {
RepositorySelection *string `json:"repository_selection,omitempty"` // Can be "all" or "selected"
SelectedRepositoryIDs []int64 `json:"selected_repository_ids,omitempty"`
}

// ListRepositoriesForOrgAppInstallation 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) ListRepositoriesForOrgAppInstallation(ctx context.Context, enterprise, org string, installationID int64, opts *ListOptions) ([]*AccessibleRepository, *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 []*AccessibleRepository
resp, err := s.client.Do(ctx, req, &r)
if err != nil {
return nil, resp, err
}

return r, resp, nil
}

// UpdateAppInstallationRepositories 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) UpdateAppInstallationRepositories(ctx context.Context, enterprise, org string, installationID int64, opts UpdateAppInstallationRepositoriesOptions) (*Installation, *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 *Installation
resp, err := s.client.Do(ctx, req, &r)
if err != nil {
return nil, resp, err
}

return r, resp, nil
}

// AddRepositoriesToAppInstallation 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) AddRepositoriesToAppInstallation(ctx context.Context, enterprise, org string, installationID int64, opts AppInstallationRepositoriesOptions) ([]*AccessibleRepository, *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 []*AccessibleRepository
resp, err := s.client.Do(ctx, req, &r)
if err != nil {
return nil, resp, err
}

return r, resp, nil
}

// RemoveRepositoriesFromAppInstallation 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) RemoveRepositoriesFromAppInstallation(ctx context.Context, enterprise, org string, installationID int64, opts AppInstallationRepositoriesOptions) ([]*AccessibleRepository, *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 []*AccessibleRepository
resp, err := s.client.Do(ctx, req, &r)
if err != nil {
return nil, resp, err
}

return r, resp, nil
}
155 changes: 155 additions & 0 deletions github/enterprise_apps_test.go
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_ListRepositoriesForOrgAppInstallation(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, `[{"id":1}]`)
})

ctx := t.Context()
repos, _, err := client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, "e", "o", 1, &ListOptions{Page: 1})
if err != nil {
t.Errorf("Enterprise.ListRepositoriesForOrgAppInstallation returned error: %v", err)
}

want := []*AccessibleRepository{{ID: 1}}
if diff := cmp.Diff(repos, want); diff != "" {
t.Errorf("Enterprise.ListRepositoriesForOrgAppInstallation returned diff (-want +got):\n%v", diff)
}

const methodName = "ListRepositoriesForOrgAppInstallation"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, "\n", "\n", -1, &ListOptions{})
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
_, resp, err := client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, "e", "o", 1, &ListOptions{})
return resp, err
})
}

func TestEnterpriseService_UpdateAppInstallationRepositories(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

input := UpdateAppInstallationRepositoriesOptions{
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, `{"id":1, "repository_selection":"selected"}`)
})

ctx := t.Context()
inst, _, err := client.Enterprise.UpdateAppInstallationRepositories(ctx, "e", "o", 1, input)
if err != nil {
t.Errorf("Enterprise.UpdateAppInstallationRepositories returned error: %v", err)
}

want := &Installation{ID: Ptr(int64(1)), RepositorySelection: Ptr("selected")}
if diff := cmp.Diff(inst, want); diff != "" {
t.Errorf("Enterprise.UpdateAppInstallationRepositories returned diff (-want +got):\n%v", diff)
}

const methodName = "UpdateAppInstallationRepositories"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Enterprise.UpdateAppInstallationRepositories(ctx, "\n", "\n", -1, input)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
_, resp, err := client.Enterprise.UpdateAppInstallationRepositories(ctx, "e", "o", 1, input)
return resp, err
})
}

func TestEnterpriseService_AddRepositoriesToAppInstallation(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

input := AppInstallationRepositoriesOptions{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, `[{"id":1},{"id":2}]`)
})

ctx := t.Context()
repos, _, err := client.Enterprise.AddRepositoriesToAppInstallation(ctx, "e", "o", 1, input)
if err != nil {
t.Errorf("Enterprise.AddRepositoriesToAppInstallation returned error: %v", err)
}

want := []*AccessibleRepository{{ID: 1}, {ID: 2}}
if diff := cmp.Diff(repos, want); diff != "" {
t.Errorf("Enterprise.AddRepositoriesToAppInstallation returned diff (-want +got):\n%v", diff)
}

const methodName = "AddRepositoriesToAppInstallation"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Enterprise.AddRepositoriesToAppInstallation(ctx, "\n", "\n", -1, input)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
_, resp, err := client.Enterprise.AddRepositoriesToAppInstallation(ctx, "e", "o", 1, input)
return resp, err
})
}

func TestEnterpriseService_RemoveRepositoriesFromAppInstallation(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

input := AppInstallationRepositoriesOptions{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, `[{"id":1},{"id":2}]`)
})

ctx := t.Context()
repos, _, err := client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, "e", "o", 1, input)
if err != nil {
t.Errorf("Enterprise.RemoveRepositoriesFromAppInstallation returned error: %v", err)
}

want := []*AccessibleRepository{{ID: 1}, {ID: 2}}
if diff := cmp.Diff(repos, want); diff != "" {
t.Errorf("Enterprise.RemoveRepositoriesFromAppInstallation returned diff (-want +got):\n%v", diff)
}

const methodName = "RemoveRepositoriesFromAppInstallation"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, "\n", "\n", -1, input)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
_, resp, err := client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, "e", "o", 1, input)
return resp, err
})
}
8 changes: 8 additions & 0 deletions github/github-accessors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions github/github-accessors_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading