Skip to content

feat: Add github_enterprise_app_installation resource - #3535

Open
slang25 wants to merge 8 commits into
integrations:mainfrom
slang25:slang25/github-issue-3460-feature
Open

feat: Add github_enterprise_app_installation resource#3535
slang25 wants to merge 8 commits into
integrations:mainfrom
slang25:slang25/github-issue-3460-feature

Conversation

@slang25

@slang25 slang25 commented Jul 13, 2026

Copy link
Copy Markdown

Resolves #3460


Before the change?

There is no way to install a GitHub App on an organization with access to all repositories via Terraform. github_app_installation_repositories only manages individual repositories on an existing installation, which is impractical for organizations with many repositories and doesn't model the "all repositories" desired state.

After the change?

A new github_enterprise_app_installation resource manages GitHub App installations on enterprise-owned organizations via the enterprise organization-installations API, available on GitHub Enterprise Cloud and GitHub Enterprise Server 3.19+ (verified against the GHES 3.19/3.20 OpenAPI descriptions). The APIs are already present in go-github v89, so no new dependencies.

resource "github_enterprise_app_installation" "example" {
  enterprise_slug      = "my-enterprise"
  organization         = "my-org"
  client_id            = "Iv1.abc123"
  repository_selection = "all" # or "selected" / "none"
}

Implementation notes:

  • Resource ID is <enterprise_slug>:<organization>:<client_id>, so imports need no installation ID lookup; installation_id and app_slug are computed.
  • all ↔ selected changes update in place via the toggle endpoint; repository set changes while staying selected use the grant/revoke endpoints (adding before removing so the selection is never empty). Transitions involving none force recreation, since the toggle endpoint only accepts all/selected.
  • Repository lists are chunked to respect the API's 50-repositories-per-request limit.
  • Plan-time validation requires selected_repositories exactly when repository_selection = "selected".
  • Acceptance tests are gated on a new GH_TEST_ENTERPRISE_APP_CLIENT_ID env var (the client ID of an installable app) alongside the existing enterprise test mode, so they skip cleanly where not configured.

I don't have access to an enterprise environment, so the CRUD paths have not been exercised against a live API — I'd appreciate a run of the enterprise acceptance tests. Happy to adjust naming/semantics if maintainers prefer a different shape; I noted #3460 is currently labeled blocked pending enterprise-scope work, but this follows the same enterprise_slug pattern as existing enterprise resources (e.g. github_enterprise_organization).

This PR was developed with AI assistance (Claude); I have reviewed the changes.

Pull request checklist

  • Schema migrations have been created if needed (example) — not needed, new resource
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes / features)

Does this introduce a breaking change?

  • Yes
  • No

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

👋 Hi, and thank you for this contribution!

This repo is maintained by GitHub and community members on a best-effort basis. We'll get to this as soon as we can.

You can help us prioritize by joining the discussion on open issues and PRs, sharing details on the changes you need, and reviewing other contributions.


🤖 This is an automated message.

@zry98

zry98 commented Jul 31, 2026

Copy link
Copy Markdown

Hi @slang25, I just found this PR after opening my #3591 yesterday, and it seems better than my implementation in several places!

I noticed these are not covered currently:

  1. It predates the max per page to owner struct refactor merged on July 21, so it references the removed global.
  2. data sources like github_enterprise_app_installations, github_enterprise_app_installable_organizations and github_enterprise_app_accessible_organization_repositories.

Would you update it?

slang25 and others added 3 commits July 31, 2026 23:39
Adds a new resource for installing a GitHub App on an enterprise-owned
organization via the enterprise organization-installations API, including
support for repository_selection = "all". Available on GitHub Enterprise
Cloud and GitHub Enterprise Server 3.19+.

Resolves integrations#3460

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Record the enterprise app installation in state as soon as the app is
installed, so a failure while granting the remaining (>50) repositories
leaves a recoverable resource rather than an orphaned installation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The global maxPerPage constant was moved to the Owner struct in integrations#3555.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@slang25
slang25 force-pushed the slang25/github-issue-3460-feature branch from 79ef8dc to 4708b77 Compare July 31, 2026 21:42
Adds github_enterprise_app_installations,
github_enterprise_app_installable_organizations and
github_enterprise_app_accessible_organization_repositories.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@deiga

deiga commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator
  • Please remove the docs template files and use the default template.
  • Your PR description talks about installing a GitHub App to an Organization, yet you only created a resource for enterprise level installations. That seems out-of-place

…late

Drop the hand-written per-resource templates in favour of the default
tfplugindocs template, so the docs stay in sync with the schema.

Move the resource example to the path tfplugindocs discovers
(examples/resources/github_enterprise_app_installation/resource.tf) and
add import.sh so the import section still renders; the previous path only
worked because the hand-written template referenced it by name.

Nested attribute descriptions on the data sources' computed lists are
lost in the process: SDKv2 collapses computed-only Elem resources to a
plain object type, so tfplugindocs renders them as "List of Object".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@deiga
deiga requested a balanced review from Copilot August 9, 2026 20:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

These provider review instructions are being used.

Findings

  • HIGH: Create can clear state or orphan an installation due to read-after-write and delayed installation_id persistence.
  • MEDIUM: Diff validation mishandles unknown values; update may persist stale state.
  • MEDIUM: Batching and selected-to-selected updates lack coverage; new resource tests use legacy assertion APIs.

Adds enterprise-level GitHub App installation management and related discovery data sources.

Changes:

  • Adds CRUD, import, repository-selection updates, and request chunking.
  • Adds three enterprise App discovery data sources.
  • Registers and documents the new APIs with examples and acceptance tests.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
RESOURCES.md Lists new resource and data sources.
github/resource_github_enterprise_app_installation.go Implements installation lifecycle.
github/resource_github_enterprise_app_installation_test.go Adds resource acceptance tests.
github/provider.go Registers new provider APIs.
github/data_source_github_enterprise_app_installations.go Lists App installations.
github/data_source_github_enterprise_app_installations_test.go Tests installation lookup.
github/data_source_github_enterprise_app_installable_organizations.go Lists installable organizations.
github/data_source_github_enterprise_app_installable_organizations_test.go Tests organization lookup.
github/data_source_github_enterprise_app_accessible_organization_repositories.go Lists accessible repositories.
github/data_source_github_enterprise_app_accessible_organization_repositories_test.go Tests repository lookup.
examples/resources/github_enterprise_app_installation/resource.tf Demonstrates installation configurations.
examples/resources/github_enterprise_app_installation/import.sh Demonstrates import syntax.
examples/data-sources/github_enterprise_app_installations/data-source_1.tf Adds installations example.
examples/data-sources/github_enterprise_app_installable_organizations/data-source_1.tf Adds organizations example.
examples/data-sources/github_enterprise_app_accessible_organization_repositories/data-source_1.tf Adds repositories example.
docs/resources/enterprise_app_installation.md Documents the resource.
docs/data-sources/enterprise_app_installations.md Documents installations data source.
docs/data-sources/enterprise_app_installable_organizations.md Documents organizations data source.
docs/data-sources/enterprise_app_accessible_organization_repositories.md Documents repositories data source.
Suppressed comments (2)

github/resource_github_enterprise_app_installation_test.go:111

  • This new TestStep also uses the legacy Check/TestCheckResourceAttr API. Convert it to ConfigStateChecks with statecheck/knownvalue to follow the repository's required convention for new tests.
					Check: resource.ComposeTestCheckFunc(
						resource.TestCheckResourceAttr("github_enterprise_app_installation.test", "repository_selection", "all"),
						resource.TestCheckResourceAttr("github_enterprise_app_installation.test", "selected_repositories.#", "0"),
					),

github/resource_github_enterprise_app_installation_test.go:104

  • This new TestStep uses legacy Check and TestCheckResourceAttr assertions. Replace them with equivalent ConfigStateChecks using statecheck/knownvalue, as required for newly added tests in this repository.
					Check: resource.ComposeTestCheckFunc(
						resource.TestCheckResourceAttr("github_enterprise_app_installation.test", "repository_selection", "selected"),
						resource.TestCheckResourceAttr("github_enterprise_app_installation.test", "selected_repositories.#", "1"),
					),

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +129 to +147
installation, _, err := client.Enterprise.InstallApp(ctx, enterpriseSlug, org, req)
if err != nil {
return diag.FromErr(err)
}

// Record the resource in state as soon as the app is installed, so a
// failure while granting the remaining repositories leaves a recoverable
// resource rather than an orphaned installation.
id, err := buildID(enterpriseSlug, org, clientID)
if err != nil {
return diag.FromErr(err)
}
d.SetId(id)

if err := addEnterpriseAppInstallationRepositories(ctx, client, enterpriseSlug, org, installation.GetID(), remainder); err != nil {
return diag.FromErr(err)
}

return resourceGithubEnterpriseAppInstallationRead(ctx, d, meta)
Comment on lines +85 to +87
func(ctx context.Context, d *schema.ResourceDiff, meta any) error {
selection := d.Get("repository_selection").(string)
repoCount := d.Get("selected_repositories").(*schema.Set).Len()
}
}

return resourceGithubEnterpriseAppInstallationRead(ctx, d, meta)
Comment on lines +113 to +115
if len(repositories) > maxInstallationRepositoriesPerRequest {
initial = repositories[:maxInstallationRepositoriesPerRequest]
remainder = repositories[maxInstallationRepositoriesPerRequest:]
Comment on lines +267 to +270
} else if d.HasChange("selected_repositories") {
oldRepos, newRepos := d.GetChange("selected_repositories")
oldSet := oldRepos.(*schema.Set)
newSet := newRepos.(*schema.Set)
Steps: []resource.TestStep{
{
Config: config,
Check: check,
slang25 and others added 2 commits August 10, 2026 13:38
Create ended by calling the read function, which resolves the
installation through a list endpoint. An eventually consistent list
that does not yet include the new installation would clear the resource
ID, leaving the installation orphaned outside state. Update had the
same problem in reverse, writing a stale repository selection back over
the value that was just applied.

Both endpoints return the installation, so set the computed fields from
the install response and return without a read, in line with the
provider's no-read-after-write convention (integrations#2892). 'installation_id' is
now written before the remaining repositories are granted, so a failure
there still leaves a resource that Delete can uninstall.

Also skip the repository selection diff validation while either value
is unknown; unknown values read back as their zero value, so a valid
'selected_repositories' computed from another resource failed planning
with "must be set".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New tests are expected to assert with ConfigStateChecks rather than the
legacy Check and TestCheckResourceAttr pattern.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@deiga

deiga commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

I still think there needs to be a distinction for "installation of an enterprise app to an Organisation" and "installation of an enterprise app to an Enterprise" and both of those should be covered in this PR.

@zry98

zry98 commented Aug 10, 2026

Copy link
Copy Markdown

I still think there needs to be a distinction for "installation of an enterprise app to an Organisation" and "installation of an enterprise app to an Enterprise" and both of those should be covered in this PR.

@deiga As far as I know, GitHub's current API doesn't have "installation of an enterprise app to an Enterprise"-related endpoints. Only installation to organizations owned by an enterprise.

'github_enterprise_app_installation' reads as though it installs an app
on an enterprise account. It does not: every endpoint behind it is
scoped to an organization the enterprise owns, and GitHub has no API
for installing an app on the enterprise account itself. The only
enterprise-level operation GitHub exposes is
'GET /enterprises/{enterprise}/installation', which lets an app find
its own installation, so the distinction cannot be resolved by adding
a second resource.

Name the target instead:

  github_enterprise_app_installation
    -> github_enterprise_organization_app_installation
  github_enterprise_app_installations
    -> github_enterprise_organization_app_installations
  github_enterprise_app_accessible_organization_repositories
    -> github_enterprise_organization_app_accessible_repositories

'github_enterprise_app_installable_organizations' keeps its name; it is
the one API here that is genuinely enterprise-scoped.

The resource description now states that the app is installed on the
organization rather than the enterprise account. Nothing here has been
released, so no state migration or 'moved' block is required.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@deiga

deiga commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

That is a fair point! 🤦‍♂️

@slang25

slang25 commented Aug 10, 2026

Copy link
Copy Markdown
Author

I've renamed the resources to avoid confusion, but happy to revert if we prefer terser names

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT]: Add support for installing GitHub Apps on all repositories in an organization

5 participants