Skip to content
Draft
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
181 changes: 181 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,169 @@ provider "github" {
}
```

### Opt-In Enterprise App Lifecycle

The optional `enterprise_app_auth` block enables an App-only path for
`github_enterprise_organization`. It does not change default/PAT authentication
or route other organization resources dynamically. Use a separate normally
configured provider for repositories, teams, and other organization resources.
This feature requires a provider build containing this opt-in implementation.

Set `auth_mode = "app"`. The existing `app_auth` credentials are the enterprise
creation App; its installation must target the configured enterprise. `owner`
is not required or looked up in this mode. No PAT or GitHub CLI fallback is
allowed: remove `token` and the environment variable selected by `token_env_name`
(normally `GITHUB_TOKEN`). Clients renew installation tokens automatically.

```terraform
terraform {
required_providers {
github = {
source = "integrations/github"
}
}
}

provider "github" {
auth_mode = "app"

app_auth {
id = var.creation_app_id
installation_id = var.creation_installation_id
pem_file = var.creation_private_key
}

enterprise_app_auth {
enterprise_slug = var.enterprise_slug
installer_app_id = var.installer_app_id
installer_installation_id = var.installer_installation_id
installer_pem_file = var.installer_private_key
automation_app_id = var.automation_app_id
automation_client_id = var.automation_client_id
automation_pem_file = var.automation_private_key
}
}

data "github_enterprise" "selected" {
slug = var.enterprise_slug
}

resource "github_enterprise_organization" "managed" {
for_each = var.organizations

enterprise_id = data.github_enterprise.selected.id
name = each.key
billing_email = each.value.billing_email
admin_logins = each.value.admin_logins

lifecycle {
prevent_destroy = true
}
}

variable "enterprise_slug" {
type = string
description = "Enterprise managed by the creation and installer Apps."
}

variable "creation_app_id" {
type = string
description = "Enterprise creation App ID or client ID."
}

variable "creation_installation_id" {
type = string
description = "Enterprise creation App installation ID."
}

variable "creation_private_key" {
type = string
sensitive = true
description = "Creation App private key PEM content."
}

variable "installer_app_id" {
type = string
description = "Installer App ID or client ID."
}

variable "installer_installation_id" {
type = string
description = "Installer enterprise installation ID."
}

variable "installer_private_key" {
type = string
sensitive = true
description = "Installer App private key PEM content."
}

variable "automation_app_id" {
type = string
description = "Numeric automation App ID."
}

variable "automation_client_id" {
type = string
description = "Automation App client ID matching the numeric ID and private key."
}

variable "automation_private_key" {
type = string
sensitive = true
description = "Automation App private key PEM content."
}

variable "organizations" {
type = map(object({
billing_email = string
admin_logins = set(string)
}))
description = "Organizations to create or import into the configured enterprise."
}
```

Supply the new block's credentials as attributes or with these dedicated
environment defaults. Environment variables alone do not enable the feature;
the block with `enterprise_slug` must be present.

| Attribute | Environment Default |
| --- | --- |
| `installer_app_id` | `INSTALLER_APP_CLIENT_ID` |
| `installer_installation_id` | `INSTALLER_APP_INSTALLATION_ID` |
| `installer_pem_file` | `INSTALLER_APP_PRIVATE_KEY` |
| `automation_app_id` | `ORG_APP_ID` |
| `automation_client_id` | `ORG_APP_CLIENT_ID` |
| `automation_pem_file` | `ORG_APP_PRIVATE_KEY` |

`installer_app_id` accepts the numeric App ID or client ID JWT issuer.
`automation_app_id` is the numeric App ID; `automation_client_id` must identify
the same App. PEM attributes/environment variables contain private key content,
not file paths. Keep all private key inputs sensitive in HCP Terraform.
All installer and automation credentials are required, whether supplied as
attributes or environment variables. Missing credentials fail configuration.

The installer must have access to the enterprise's installable organization
inventory and permission to install organization Apps. The automation App must
have `organization_administration = write` and `members = read` (or `write`).
Repository permissions other than metadata are rejected because bootstrap uses
`repository_selection = "none"`. The creation App needs the enterprise
organization-creation permission. See the GitHub
[enterprise organization APIs](https://docs.github.com/en/rest/enterprise-admin/organizations)
and [App installation APIs](https://docs.github.com/en/rest/apps/installations)
for endpoint permission requirements.

Read/import never install, update, or unsuspend Apps. Creation installs the
automation App only if its organization installation is absent. Existing
installations must be active and already have the required permissions; this
provider does not upgrade them. The `automation_installation_id` resource
attribute exposes the discovered installation ID, but does not configure other
provider instances in the same run.

Updates and deletion of enterprise organizations are explicitly blocked in
this mode. For partial-create recovery and import behavior, see
[github_enterprise_organization](resources/enterprise_organization).

### OAuth or Personal Access Token (PAT)

To authenticate using OAuth tokens, ensure that the `token` argument or the `GITHUB_TOKEN` environment variable is set.
Expand Down Expand Up @@ -155,6 +318,7 @@ provider "github" {
- `auth_mode` (String) The authentication mode to use; this can be one of `auto`, `app`, `token` or `none` and defaults to `auto` which will detect the highest priority authentication mode available (`app` -> `token` -> `none`). This can also be set by the `GITHUB_AUTH_MODE` environment variable.
- `base_url` (String) The base URL for the GitHub API; this defaults to the GitHub API URL. If you are using GitHub Enterprise Server (GHES) or GitHub Enterprise Cloud with Data Residency (GHEC-DR), this is required. This can also be set by the `GITHUB_BASE_URL` environment variable.
- `cache_path` (String) The path to the cache directory for persisting GitHub API requests between runs; if not set there will be no caching between runs. This can also be set by the `GITHUB_CACHE_PATH` environment variable.
- `enterprise_app_auth` (Block List, Max: 1) Opt-in enterprise App lifecycle credentials. All credentials must be supplied here or through their dedicated environment variables. (see [below for nested schema](#nestedblock--enterprise_app_auth))
- `insecure` (Boolean, Deprecated) Allow insecure server connections when using SSL.
- `legacy_client` (Boolean) Use the legacy GitHub client implementation; if set to `false`, the new client implementation is used. This can also be set by the `GITHUB_LEGACY_CLIENT` environment variable.
- `max_per_page` (Number) The maximum number of results per page for paginated API requests; this defaults to `100`. This can also be set by the `GITHUB_MAX_PER_PAGE` environment variable.
Expand All @@ -177,3 +341,20 @@ Optional:
- `id` (String) The GitHub App's identifier. This can also be set by the `GITHUB_APP_ID` environment variable when `app_auth_env_prefix` is `GITHUB_APP_` (modify the prefix as needed).
- `installation_id` (String) The GitHub App's installation identifier. This can also be set by the `GITHUB_APP_INSTALLATION_ID` environment variable when `app_auth_env_prefix` is `GITHUB_APP_` (modify the prefix as needed).
- `pem_file` (String, Sensitive) The GitHub App's PEM file content; `\n` can be used for newlines. This can also be set by the `GITHUB_APP_PEM_FILE` environment variable when `app_auth_env_prefix` is `GITHUB_APP_` (modify the prefix as needed).


<a id="nestedblock--enterprise_app_auth"></a>
### Nested Schema for `enterprise_app_auth`

Required:

- `enterprise_slug` (String) Enterprise slug to manage. Opts into App-only enterprise organization lifecycle; deletion and updates are blocked.

Optional:

- `automation_app_id` (String) Automation App numeric ID. Defaults to ORG_APP_ID.
- `automation_client_id` (String) Automation App client ID. Defaults to ORG_APP_CLIENT_ID.
- `automation_pem_file` (String, Sensitive) Automation private key PEM content, not a filename. Defaults to ORG_APP_PRIVATE_KEY.
- `installer_app_id` (String) Installer App numeric ID or client ID. Defaults to INSTALLER_APP_CLIENT_ID.
- `installer_installation_id` (String) Installer enterprise installation ID. Defaults to INSTALLER_APP_INSTALLATION_ID.
- `installer_pem_file` (String, Sensitive) Installer private key PEM content, not a filename. Defaults to INSTALLER_APP_PRIVATE_KEY.
45 changes: 45 additions & 0 deletions docs/resources/enterprise_organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,46 @@ The following additional attributes are exported:

- `id` - The node ID of the organization for use with the v4 API.
- `database_id` - The ID of the organization.
- `automation_installation_id` - The automation App installation ID discovered
when the provider opts into `enterprise_app_auth`. Unset in legacy mode.

## Opt-In App Lifecycle

With the provider's `enterprise_app_auth` block, this resource creates an
organization with the existing enterprise creation App and bootstraps the
automation App with the enterprise installer. The enterprise ID must match the
configured enterprise slug. Node IDs and numeric organization IDs are checked
against enterprise inventory and the organization APIs before accepting state.
Propagation retries are bounded by the create timeout and stop on permission,
suspension, and identity errors. A failed installation POST is not retried.

Refresh and import use the installer's paginated enterprise inventory plus
organization-scoped automation clients. Billing comes from REST `billing_email`,
not GraphQL `organizationBillingEmail`. Missing/null billing is an error; existing
billing state is not replaced with an empty value. Owners are paginated and only
`ADMIN` memberships are retained. Denied, ambiguous, missing-installation, or
identity-mismatch reads return errors and retain the resource ID, rather than
silently planning a replacement. Organizations renamed outside Terraform require
explicit reconciliation/import; refresh does not guess an identity from a new login.

Updates are not supported in this opt-in path, including changes to owners,
billing, display name, and description. Restore the previous configuration or
perform approved external changes and reconcile configuration before refreshing.
Deletion is blocked before any API request until an approved permission and
deletion contract is available. `lifecycle { prevent_destroy = true }` is
recommended. Neither this mode nor the installation ID output dynamically routes
ordinary organization resources such as repositories or teams.

If creation succeeds but installation/profile bootstrap fails, the error reports
the enterprise/org import ID and retains the returned node/database IDs, including
in the SDK apply response. Terraform can mark a failed create as tainted; do not
blindly retry or replace it. Inspect the organization and App installation,
correct the missing permissions/installation through approved administration,
then untaint the existing object if state is complete or explicitly reconcile
state and import it. Import and refresh will not finish a missing installation.
Deletion remains blocked even for a tainted object. If the creation API response
itself is lost, check enterprise inventory and import any created organization
before retrying, because a trustworthy node ID may not have been returned.

## Import

Expand All @@ -46,3 +86,8 @@ GitHub Enterprise Organization can be imported using the `slug` of the enterpris
```shell
terraform import github_enterprise_organization.org enterp/some-awesome-org
```

In `enterprise_app_auth` mode the enterprise slug in the import ID must match
the configured slug. The automation App must already be installed, active, and
authorized for organization administration and member reads. Import makes no
installation writes and fails on missing permissions instead of discarding state.
105 changes: 105 additions & 0 deletions examples/provider/enterprise_app_auth/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
terraform {
required_providers {
github = {
source = "integrations/github"
}
}
}

provider "github" {
auth_mode = "app"

app_auth {
id = var.creation_app_id
installation_id = var.creation_installation_id
pem_file = var.creation_private_key
}

enterprise_app_auth {
enterprise_slug = var.enterprise_slug
installer_app_id = var.installer_app_id
installer_installation_id = var.installer_installation_id
installer_pem_file = var.installer_private_key
automation_app_id = var.automation_app_id
automation_client_id = var.automation_client_id
automation_pem_file = var.automation_private_key
}
}

data "github_enterprise" "selected" {
slug = var.enterprise_slug
}

resource "github_enterprise_organization" "managed" {
for_each = var.organizations

enterprise_id = data.github_enterprise.selected.id
name = each.key
billing_email = each.value.billing_email
admin_logins = each.value.admin_logins

lifecycle {
prevent_destroy = true
}
}

variable "enterprise_slug" {
type = string
description = "Enterprise managed by the creation and installer Apps."
}

variable "creation_app_id" {
type = string
description = "Enterprise creation App ID or client ID."
}

variable "creation_installation_id" {
type = string
description = "Enterprise creation App installation ID."
}

variable "creation_private_key" {
type = string
sensitive = true
description = "Creation App private key PEM content."
}

variable "installer_app_id" {
type = string
description = "Installer App ID or client ID."
}

variable "installer_installation_id" {
type = string
description = "Installer enterprise installation ID."
}

variable "installer_private_key" {
type = string
sensitive = true
description = "Installer App private key PEM content."
}

variable "automation_app_id" {
type = string
description = "Numeric automation App ID."
}

variable "automation_client_id" {
type = string
description = "Automation App client ID matching the numeric ID and private key."
}

variable "automation_private_key" {
type = string
sensitive = true
description = "Automation App private key PEM content."
}

variable "organizations" {
type = map(object({
billing_email = string
admin_logins = set(string)
}))
description = "Organizations to create or import into the configured enterprise."
}
2 changes: 2 additions & 0 deletions github/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
)

type Config struct {
EnterpriseApp *enterpriseAppConfig
AppID *string
AppInstallationID *string
AppPEM []byte
Expand All @@ -36,6 +37,7 @@ type Config struct {
}

type Owner struct {
enterpriseApp *enterpriseAppLifecycle
name string
id int64
v3client *github.Client
Expand Down
Loading
Loading