feat: Add github_enterprise_app_installation resource - #3535
Conversation
|
👋 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. |
|
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:
Would you update it? |
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>
79ef8dc to
4708b77
Compare
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>
|
…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>
There was a problem hiding this comment.
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_idpersistence. - 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/TestCheckResourceAttrAPI. Convert it toConfigStateCheckswithstatecheck/knownvalueto 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
CheckandTestCheckResourceAttrassertions. Replace them with equivalentConfigStateChecksusingstatecheck/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.
| 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) |
| 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) |
| if len(repositories) > maxInstallationRepositoriesPerRequest { | ||
| initial = repositories[:maxInstallationRepositoriesPerRequest] | ||
| remainder = repositories[maxInstallationRepositoriesPerRequest:] |
| } 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, |
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>
|
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>
|
That is a fair point! 🤦♂️ |
|
I've renamed the resources to avoid confusion, but happy to revert if we prefer terser names |
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_repositoriesonly 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_installationresource 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.Implementation notes:
<enterprise_slug>:<organization>:<client_id>, so imports need no installation ID lookup;installation_idandapp_slugare computed.all ↔ selectedchanges update in place via the toggle endpoint; repository set changes while stayingselecteduse the grant/revoke endpoints (adding before removing so the selection is never empty). Transitions involvingnoneforce recreation, since the toggle endpoint only acceptsall/selected.selected_repositoriesexactly whenrepository_selection = "selected".GH_TEST_ENTERPRISE_APP_CLIENT_IDenv 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_slugpattern 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
Does this introduce a breaking change?
🤖 Generated with Claude Code