Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
)

// 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) {
Copy link
Contributor

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:

type ListRepositories struct {
	TotalCount   *int          `json:"total_count,omitempty"`
	Repositories []*Repository `json:"repositories"`
}

I made a struct named AccessibleRepository in PR #3830, you can re-use it and update this function to return []*AccessibleRepository once my PR is merged.

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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (s *EnterpriseService) ToggleInstallationRepositories(ctx context.Context, enterprise, org string, installationID int64, opts *EnterpriseInstallationRepositoriesToggleOptions) (*ListRepositories, *Response, error) {
func (s *EnterpriseService) ToggleInstallationRepositories(ctx context.Context, enterprise, org string, installationID int64, opts EnterpriseInstallationRepositoriesToggleOptions) (*ListRepositories, *Response, error) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint does not return a schema similar to ListRepositories.

Suggested change
func (s *EnterpriseService) ToggleInstallationRepositories(ctx context.Context, enterprise, org string, installationID int64, opts *EnterpriseInstallationRepositoriesToggleOptions) (*ListRepositories, *Response, error) {
func (s *EnterpriseService) ToggleInstallationRepositories(ctx context.Context, enterprise, org string, installationID int64, opts *EnterpriseInstallationRepositoriesToggleOptions) (*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 *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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, it should return []*AccessibleRepository (the struct is in my PR, which hasn’t been merged yet).

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, it should return []*AccessibleRepository (the struct is in my PR, which hasn’t been merged yet).

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
}
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_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
})
}
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