diff --git a/.github/workflows/acceptance-tests.yaml b/.github/workflows/acceptance-tests.yaml
index 118887cc19..c1217ce056 100644
--- a/.github/workflows/acceptance-tests.yaml
+++ b/.github/workflows/acceptance-tests.yaml
@@ -216,6 +216,7 @@ jobs:
GITHUB_BASE_URL: https://api.github.com/
GITHUB_OWNER: ${{ case(matrix.mode == 'anonymous', '', matrix.mode == 'individual', vars.GH_TEST_LOGIN, vars.GH_TEST_ORG_NAME) }}
GITHUB_ENTERPRISE_SLUG: ${{ case(matrix.mode == 'enterprise', vars.GH_TEST_ENTERPRISE_SLUG, '') }}
+ GH_TEST_ENTERPRISE_APP_CLIENT_ID: ${{ vars.GH_TEST_ENTERPRISE_APP_CLIENT_ID }}
GITHUB_LEGACY_CLIENT: "false"
GH_TEST_AUTH_MODE: ${{ matrix.mode }}
GH_TEST_ORG_USER1: ${{ vars.GH_TEST_ORG_USER1 }}
diff --git a/RESOURCES.md b/RESOURCES.md
index b1b1c3e2b0..f0b3fa8283 100644
--- a/RESOURCES.md
+++ b/RESOURCES.md
@@ -69,6 +69,9 @@ The overall status of each resource or data source is captured in this document
| `github_dependabot_public_key` | ⚠️ | ✅ | ❓ | ❓ | ❓ | ❓ |
| `github_dependabot_secrets` | ⚠️ | ✅ | ❓ | ❓ | ❓ | ❓ |
| `github_enterprise` | ⚠️ | ✅ | ❓ | ❓ | ❓ | ❓ |
+| `github_enterprise_app_installable_organizations` | ✅ | ✅ | ✅ | ✅ | ❓ | ✅ |
+| `github_enterprise_organization_app_accessible_repositories` | ✅ | ✅ | ✅ | ✅ | ❓ | ✅ |
+| `github_enterprise_organization_app_installations` | ✅ | ✅ | ✅ | ✅ | ❓ | ✅ |
| `github_external_groups` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `github_ip_ranges` | ⚠️ | ✅ | ❓ | ❓ | ❓ | ❓ |
| `github_issue_labels` | ⚠️ | ✅ | ❓ | ❓ | ❓ | ❓ |
@@ -162,6 +165,7 @@ The overall status of each resource or data source is captured in this document
| `github_enterprise_actions_workflow_permissions` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_enterprise_ip_allow_list_entry` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_enterprise_organization` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
+| `github_enterprise_organization_app_installation` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_enterprise_security_analysis_settings` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_issue` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_issue_label` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
diff --git a/docs/data-sources/enterprise_app_installable_organizations.md b/docs/data-sources/enterprise_app_installable_organizations.md
new file mode 100644
index 0000000000..e0ce62e545
--- /dev/null
+++ b/docs/data-sources/enterprise_app_installable_organizations.md
@@ -0,0 +1,39 @@
+---
+page_title: "github_enterprise_app_installable_organizations (Data Source) - GitHub"
+subcategory: ""
+description: |-
+ Use this data source to retrieve the enterprise-owned organizations that GitHub Apps can be installed on. This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.
+---
+
+# github_enterprise_app_installable_organizations (Data Source)
+
+Use this data source to retrieve the enterprise-owned organizations that GitHub Apps can be installed on. This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.
+
+## Example Usage
+
+```terraform
+data "github_enterprise_app_installable_organizations" "example" {
+ enterprise_slug = "my-enterprise"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `enterprise_slug` (String) The slug of the enterprise.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `organizations` (List of Object) List of organizations in the enterprise that GitHub Apps can be installed on. (see [below for nested schema](#nestedatt--organizations))
+
+
+### Nested Schema for `organizations`
+
+Read-Only:
+
+- `accessible_repositories_url` (String)
+- `id` (Number)
+- `login` (String)
diff --git a/docs/data-sources/enterprise_organization_app_accessible_repositories.md b/docs/data-sources/enterprise_organization_app_accessible_repositories.md
new file mode 100644
index 0000000000..11d33a6250
--- /dev/null
+++ b/docs/data-sources/enterprise_organization_app_accessible_repositories.md
@@ -0,0 +1,41 @@
+---
+page_title: "github_enterprise_organization_app_accessible_repositories (Data Source) - GitHub"
+subcategory: ""
+description: |-
+ Use this data source to retrieve the repositories of an enterprise-owned organization that GitHub Apps can be granted access to. This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.
+---
+
+# github_enterprise_organization_app_accessible_repositories (Data Source)
+
+Use this data source to retrieve the repositories of an enterprise-owned organization that GitHub Apps can be granted access to. This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.
+
+## Example Usage
+
+```terraform
+data "github_enterprise_organization_app_accessible_repositories" "example" {
+ enterprise_slug = "my-enterprise"
+ organization = "my-org"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `enterprise_slug` (String) The slug of the enterprise that owns the organization.
+- `organization` (String) The login of the enterprise-owned organization.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `repositories` (List of Object) List of repositories of the organization that GitHub Apps can be granted access to. (see [below for nested schema](#nestedatt--repositories))
+
+
+### Nested Schema for `repositories`
+
+Read-Only:
+
+- `full_name` (String)
+- `id` (Number)
+- `name` (String)
diff --git a/docs/data-sources/enterprise_organization_app_installations.md b/docs/data-sources/enterprise_organization_app_installations.md
new file mode 100644
index 0000000000..0bdc35ab8e
--- /dev/null
+++ b/docs/data-sources/enterprise_organization_app_installations.md
@@ -0,0 +1,51 @@
+---
+page_title: "github_enterprise_organization_app_installations (Data Source) - GitHub"
+subcategory: ""
+description: |-
+ Use this data source to retrieve the GitHub App installations on an enterprise-owned organization. This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.
+---
+
+# github_enterprise_organization_app_installations (Data Source)
+
+Use this data source to retrieve the GitHub App installations on an enterprise-owned organization. This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.
+
+## Example Usage
+
+```terraform
+data "github_enterprise_organization_app_installations" "example" {
+ enterprise_slug = "my-enterprise"
+ organization = "my-org"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `enterprise_slug` (String) The slug of the enterprise that owns the organization.
+- `organization` (String) The login of the enterprise-owned organization.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `installations` (List of Object) List of GitHub App installations on the organization. (see [below for nested schema](#nestedatt--installations))
+
+
+### Nested Schema for `installations`
+
+Read-Only:
+
+- `app_id` (Number)
+- `app_slug` (String)
+- `client_id` (String)
+- `created_at` (String)
+- `events` (List of String)
+- `id` (Number)
+- `permissions` (Map of String)
+- `repository_selection` (String)
+- `single_file_paths` (List of String)
+- `suspended` (Boolean)
+- `target_id` (Number)
+- `target_type` (String)
+- `updated_at` (String)
diff --git a/docs/resources/enterprise_organization_app_installation.md b/docs/resources/enterprise_organization_app_installation.md
new file mode 100644
index 0000000000..6fc6d382fd
--- /dev/null
+++ b/docs/resources/enterprise_organization_app_installation.md
@@ -0,0 +1,59 @@
+---
+page_title: "github_enterprise_organization_app_installation (Resource) - GitHub"
+subcategory: ""
+description: |-
+ Manage the installation of a GitHub App on an organization owned by an enterprise. The app is installed on the organization, not on the enterprise account itself; GitHub offers no API for the latter. This resource requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.
+---
+
+# github_enterprise_organization_app_installation (Resource)
+
+Manage the installation of a GitHub App on an organization owned by an enterprise. The app is installed on the organization, not on the enterprise account itself; GitHub offers no API for the latter. This resource requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.
+
+## Example Usage
+
+```terraform
+resource "github_enterprise_organization_app_installation" "all_repos" {
+ enterprise_slug = "my-enterprise"
+ organization = "my-org"
+ client_id = "Iv1.abc123def456"
+ repository_selection = "all"
+}
+
+resource "github_enterprise_organization_app_installation" "selected_repos" {
+ enterprise_slug = "my-enterprise"
+ organization = "my-org"
+ client_id = "Iv1.789ghi012jkl"
+ repository_selection = "selected"
+ selected_repositories = ["my-repo-1", "my-repo-2"]
+}
+```
+
+
+## Schema
+
+### Required
+
+- `client_id` (String) The client ID of the GitHub App to install.
+- `enterprise_slug` (String) The slug of the enterprise that owns the organization.
+- `organization` (String) The login of the enterprise-owned organization to install the app on.
+- `repository_selection` (String) The repositories the installation can access. Can be one of 'all', 'selected' or 'none'.
+
+### Optional
+
+- `selected_repositories` (Set of String) The names of the repositories the installation can access when 'repository_selection' is 'selected'.
+
+### Read-Only
+
+- `app_slug` (String) The slug of the installed app.
+- `id` (String) The ID of this resource.
+- `installation_id` (String) The ID of the installation.
+
+## Import
+
+Import is supported using the following syntax:
+
+The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
+
+```shell
+terraform import github_enterprise_organization_app_installation.all_repos my-enterprise:my-org:Iv1.abc123def456
+```
diff --git a/examples/data-sources/github_enterprise_app_installable_organizations/data-source_1.tf b/examples/data-sources/github_enterprise_app_installable_organizations/data-source_1.tf
new file mode 100644
index 0000000000..51fc4906fd
--- /dev/null
+++ b/examples/data-sources/github_enterprise_app_installable_organizations/data-source_1.tf
@@ -0,0 +1,3 @@
+data "github_enterprise_app_installable_organizations" "example" {
+ enterprise_slug = "my-enterprise"
+}
diff --git a/examples/data-sources/github_enterprise_organization_app_accessible_repositories/data-source_1.tf b/examples/data-sources/github_enterprise_organization_app_accessible_repositories/data-source_1.tf
new file mode 100644
index 0000000000..3c0860968a
--- /dev/null
+++ b/examples/data-sources/github_enterprise_organization_app_accessible_repositories/data-source_1.tf
@@ -0,0 +1,4 @@
+data "github_enterprise_organization_app_accessible_repositories" "example" {
+ enterprise_slug = "my-enterprise"
+ organization = "my-org"
+}
diff --git a/examples/data-sources/github_enterprise_organization_app_installations/data-source_1.tf b/examples/data-sources/github_enterprise_organization_app_installations/data-source_1.tf
new file mode 100644
index 0000000000..a8ea83f3a0
--- /dev/null
+++ b/examples/data-sources/github_enterprise_organization_app_installations/data-source_1.tf
@@ -0,0 +1,4 @@
+data "github_enterprise_organization_app_installations" "example" {
+ enterprise_slug = "my-enterprise"
+ organization = "my-org"
+}
diff --git a/examples/resources/github_enterprise_organization_app_installation/import.sh b/examples/resources/github_enterprise_organization_app_installation/import.sh
new file mode 100644
index 0000000000..1c9a0d0f2a
--- /dev/null
+++ b/examples/resources/github_enterprise_organization_app_installation/import.sh
@@ -0,0 +1 @@
+terraform import github_enterprise_organization_app_installation.all_repos my-enterprise:my-org:Iv1.abc123def456
diff --git a/examples/resources/github_enterprise_organization_app_installation/resource.tf b/examples/resources/github_enterprise_organization_app_installation/resource.tf
new file mode 100644
index 0000000000..a29a80a233
--- /dev/null
+++ b/examples/resources/github_enterprise_organization_app_installation/resource.tf
@@ -0,0 +1,14 @@
+resource "github_enterprise_organization_app_installation" "all_repos" {
+ enterprise_slug = "my-enterprise"
+ organization = "my-org"
+ client_id = "Iv1.abc123def456"
+ repository_selection = "all"
+}
+
+resource "github_enterprise_organization_app_installation" "selected_repos" {
+ enterprise_slug = "my-enterprise"
+ organization = "my-org"
+ client_id = "Iv1.789ghi012jkl"
+ repository_selection = "selected"
+ selected_repositories = ["my-repo-1", "my-repo-2"]
+}
diff --git a/github/data_source_github_enterprise_app_installable_organizations.go b/github/data_source_github_enterprise_app_installable_organizations.go
new file mode 100644
index 0000000000..7e9a6a46fb
--- /dev/null
+++ b/github/data_source_github_enterprise_app_installable_organizations.go
@@ -0,0 +1,80 @@
+package github
+
+import (
+ "context"
+
+ "github.com/google/go-github/v89/github"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceGithubEnterpriseAppInstallableOrganizations() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceGithubEnterpriseAppInstallableOrganizationsRead,
+ Description: "Use this data source to retrieve the enterprise-owned organizations that GitHub Apps can be installed on. " +
+ "This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.",
+
+ Schema: map[string]*schema.Schema{
+ "enterprise_slug": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The slug of the enterprise.",
+ },
+ "organizations": {
+ Type: schema.TypeList,
+ Computed: true,
+ Description: "List of organizations in the enterprise that GitHub Apps can be installed on.",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeInt,
+ Computed: true,
+ Description: "The ID of the organization.",
+ },
+ "login": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The login of the organization.",
+ },
+ "accessible_repositories_url": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The API URL listing the repositories that can be made accessible to a GitHub App installed on the organization.",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func dataSourceGithubEnterpriseAppInstallableOrganizationsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
+ meta, _ := m.(*Owner)
+ client := meta.v3client
+
+ enterpriseSlug := d.Get("enterprise_slug").(string)
+
+ opts := &github.ListOptions{
+ PerPage: meta.maxPerPage,
+ }
+
+ results := make([]map[string]any, 0)
+ for organization, err := range client.Enterprise.ListAppInstallableOrganizationsIter(ctx, enterpriseSlug, opts) {
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ results = append(results, map[string]any{
+ "id": organization.ID,
+ "login": organization.Login,
+ "accessible_repositories_url": organization.GetAccessibleRepositoriesURL(),
+ })
+ }
+
+ d.SetId(enterpriseSlug)
+ if err := d.Set("organizations", results); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
diff --git a/github/data_source_github_enterprise_app_installable_organizations_test.go b/github/data_source_github_enterprise_app_installable_organizations_test.go
new file mode 100644
index 0000000000..12555ce6bc
--- /dev/null
+++ b/github/data_source_github_enterprise_app_installable_organizations_test.go
@@ -0,0 +1,43 @@
+package github
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/knownvalue"
+ "github.com/hashicorp/terraform-plugin-testing/statecheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
+)
+
+func TestAccDataSourceGithubEnterpriseAppInstallableOrganizations(t *testing.T) {
+ t.Parallel()
+
+ skipUnlessEnterprise(t)
+
+ t.Run("queries_installable_organizations", func(t *testing.T) {
+ t.Parallel()
+
+ config := fmt.Sprintf(`
+data "github_enterprise_app_installable_organizations" "test" {
+ enterprise_slug = "%s"
+}
+`, testAccConf.enterpriseSlug)
+
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("data.github_enterprise_app_installable_organizations.test", tfjsonpath.New("organizations"), knownvalue.SetPartial([]knownvalue.Check{
+ knownvalue.MapPartial(map[string]knownvalue.Check{
+ "login": knownvalue.StringExact(testAccConf.owner),
+ }),
+ })),
+ },
+ },
+ },
+ })
+ })
+}
diff --git a/github/data_source_github_enterprise_organization_app_accessible_repositories.go b/github/data_source_github_enterprise_organization_app_accessible_repositories.go
new file mode 100644
index 0000000000..7faf10e452
--- /dev/null
+++ b/github/data_source_github_enterprise_organization_app_accessible_repositories.go
@@ -0,0 +1,86 @@
+package github
+
+import (
+ "context"
+
+ "github.com/google/go-github/v89/github"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceGithubEnterpriseOrganizationAppAccessibleRepositories() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceGithubEnterpriseOrganizationAppAccessibleRepositoriesRead,
+ Description: "Use this data source to retrieve the repositories of an enterprise-owned organization that GitHub Apps can be granted access to. " +
+ "This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.",
+
+ Schema: map[string]*schema.Schema{
+ "enterprise_slug": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The slug of the enterprise that owns the organization.",
+ },
+ "organization": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The login of the enterprise-owned organization.",
+ },
+ "repositories": {
+ Type: schema.TypeList,
+ Computed: true,
+ Description: "List of repositories of the organization that GitHub Apps can be granted access to.",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeInt,
+ Computed: true,
+ Description: "The ID of the repository.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The name of the repository.",
+ },
+ "full_name": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The full name of the repository, in the format `/`.",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func dataSourceGithubEnterpriseOrganizationAppAccessibleRepositoriesRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
+ meta, _ := m.(*Owner)
+ client := meta.v3client
+
+ enterpriseSlug := d.Get("enterprise_slug").(string)
+ org := d.Get("organization").(string)
+
+ opts := &github.ListOptions{
+ PerPage: meta.maxPerPage,
+ }
+
+ results := make([]map[string]any, 0)
+ for repository, err := range client.Enterprise.ListAppAccessibleOrganizationRepositoriesIter(ctx, enterpriseSlug, org, opts) {
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ results = append(results, map[string]any{
+ "id": repository.ID,
+ "name": repository.Name,
+ "full_name": repository.FullName,
+ })
+ }
+
+ d.SetId(buildTwoPartID(enterpriseSlug, org))
+ if err := d.Set("repositories", results); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
diff --git a/github/data_source_github_enterprise_organization_app_accessible_repositories_test.go b/github/data_source_github_enterprise_organization_app_accessible_repositories_test.go
new file mode 100644
index 0000000000..321e6cc819
--- /dev/null
+++ b/github/data_source_github_enterprise_organization_app_accessible_repositories_test.go
@@ -0,0 +1,55 @@
+package github
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/knownvalue"
+ "github.com/hashicorp/terraform-plugin-testing/statecheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
+)
+
+func TestAccDataSourceGithubEnterpriseOrganizationAppAccessibleRepositories(t *testing.T) {
+ t.Parallel()
+
+ skipUnlessEnterprise(t)
+
+ t.Run("queries_accessible_repositories", func(t *testing.T) {
+ t.Parallel()
+
+ randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
+ repoName := fmt.Sprintf("tf-acc-test-eaaor-%s", randomID)
+
+ config := fmt.Sprintf(`
+resource "github_repository" "test" {
+ name = "%s"
+ auto_init = true
+}
+
+data "github_enterprise_organization_app_accessible_repositories" "test" {
+ enterprise_slug = "%s"
+ organization = "%s"
+
+ depends_on = [github_repository.test]
+}
+`, repoName, testAccConf.enterpriseSlug, testAccConf.owner)
+
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("data.github_enterprise_organization_app_accessible_repositories.test", tfjsonpath.New("repositories"), knownvalue.SetPartial([]knownvalue.Check{
+ knownvalue.MapPartial(map[string]knownvalue.Check{
+ "name": knownvalue.StringExact(repoName),
+ }),
+ })),
+ },
+ },
+ },
+ })
+ })
+}
diff --git a/github/data_source_github_enterprise_organization_app_installations.go b/github/data_source_github_enterprise_organization_app_installations.go
new file mode 100644
index 0000000000..8cb8baeec6
--- /dev/null
+++ b/github/data_source_github_enterprise_organization_app_installations.go
@@ -0,0 +1,136 @@
+package github
+
+import (
+ "context"
+
+ "github.com/google/go-github/v89/github"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceGithubEnterpriseOrganizationAppInstallations() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceGithubEnterpriseOrganizationAppInstallationsRead,
+ Description: "Use this data source to retrieve the GitHub App installations on an enterprise-owned organization. " +
+ "This data source requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.",
+
+ Schema: map[string]*schema.Schema{
+ "enterprise_slug": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The slug of the enterprise that owns the organization.",
+ },
+ "organization": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The login of the enterprise-owned organization.",
+ },
+ "installations": {
+ Type: schema.TypeList,
+ Computed: true,
+ Description: "List of GitHub App installations on the organization.",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeInt,
+ Computed: true,
+ Description: "The ID of the GitHub App installation.",
+ },
+ "app_slug": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The URL-friendly name of the GitHub App.",
+ },
+ "app_id": {
+ Type: schema.TypeInt,
+ Computed: true,
+ Description: "The ID of the GitHub App.",
+ },
+ "repository_selection": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Whether the installation has access to all repositories or only selected ones. Possible values are 'all' or 'selected'.",
+ },
+ "permissions": {
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Description: "The permissions granted to the GitHub App installation.",
+ },
+ "events": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Description: "The list of events the GitHub App installation subscribes to.",
+ },
+ "client_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The OAuth client ID of the GitHub App.",
+ },
+ "target_id": {
+ Type: schema.TypeInt,
+ Computed: true,
+ Description: "The ID of the account the GitHub App is installed on.",
+ },
+ "target_type": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The type of account the GitHub App is installed on. Possible values are 'Organization' or 'User'.",
+ },
+ "suspended": {
+ Type: schema.TypeBool,
+ Computed: true,
+ Description: "Whether the GitHub App installation is currently suspended.",
+ },
+ "single_file_paths": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Description: "The list of single file paths the GitHub App installation has access to.",
+ },
+ "created_at": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The date the GitHub App installation was created.",
+ },
+ "updated_at": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The date the GitHub App installation was last updated.",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func dataSourceGithubEnterpriseOrganizationAppInstallationsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
+ meta, _ := m.(*Owner)
+ client := meta.v3client
+
+ enterpriseSlug := d.Get("enterprise_slug").(string)
+ org := d.Get("organization").(string)
+
+ opts := &github.ListOptions{
+ PerPage: meta.maxPerPage,
+ }
+
+ installations := make([]*github.Installation, 0)
+ for installation, err := range client.Enterprise.ListAppInstallationsIter(ctx, enterpriseSlug, org, opts) {
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ installations = append(installations, installation)
+ }
+ results := flattenGitHubAppInstallations(installations)
+
+ d.SetId(buildTwoPartID(enterpriseSlug, org))
+ if err := d.Set("installations", results); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
diff --git a/github/data_source_github_enterprise_organization_app_installations_test.go b/github/data_source_github_enterprise_organization_app_installations_test.go
new file mode 100644
index 0000000000..e7dfecfe5c
--- /dev/null
+++ b/github/data_source_github_enterprise_organization_app_installations_test.go
@@ -0,0 +1,40 @@
+package github
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/knownvalue"
+ "github.com/hashicorp/terraform-plugin-testing/statecheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
+)
+
+func TestAccDataSourceGithubEnterpriseOrganizationAppInstallations(t *testing.T) {
+ t.Parallel()
+
+ skipUnlessEnterprise(t)
+
+ t.Run("queries_app_installations", func(t *testing.T) {
+ t.Parallel()
+
+ config := fmt.Sprintf(`
+data "github_enterprise_organization_app_installations" "test" {
+ enterprise_slug = "%s"
+ organization = "%s"
+}
+`, testAccConf.enterpriseSlug, testAccConf.owner)
+
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("data.github_enterprise_organization_app_installations.test", tfjsonpath.New("installations"), knownvalue.NotNull()),
+ },
+ },
+ },
+ })
+ })
+}
diff --git a/github/provider.go b/github/provider.go
index 0bd2720b05..fb0f146855 100644
--- a/github/provider.go
+++ b/github/provider.go
@@ -240,6 +240,7 @@ func NewProvider(version, commit string) func() *schema.Provider {
"github_user_gpg_key": resourceGithubUserGpgKey(),
"github_user_invitation_accepter": resourceGithubUserInvitationAccepter(),
"github_user_ssh_key": resourceGithubUserSshKey(),
+ "github_enterprise_organization_app_installation": resourceGithubEnterpriseOrganizationAppInstallation(),
"github_enterprise_organization": resourceGithubEnterpriseOrganization(),
"github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(),
"github_enterprise_ip_allow_list_entry": resourceGithubEnterpriseIpAllowListEntry(),
@@ -328,6 +329,9 @@ func NewProvider(version, commit string) func() *schema.Provider {
"github_user_external_identity": dataSourceGithubUserExternalIdentity(),
"github_users": dataSourceGithubUsers(),
"github_enterprise": dataSourceGithubEnterprise(),
+ "github_enterprise_organization_app_accessible_repositories": dataSourceGithubEnterpriseOrganizationAppAccessibleRepositories(),
+ "github_enterprise_app_installable_organizations": dataSourceGithubEnterpriseAppInstallableOrganizations(),
+ "github_enterprise_organization_app_installations": dataSourceGithubEnterpriseOrganizationAppInstallations(),
"github_repository_environment_deployment_policies": dataSourceGithubRepositoryEnvironmentDeploymentPolicies(),
},
diff --git a/github/resource_github_enterprise_organization_app_installation.go b/github/resource_github_enterprise_organization_app_installation.go
new file mode 100644
index 0000000000..98230d1f61
--- /dev/null
+++ b/github/resource_github_enterprise_organization_app_installation.go
@@ -0,0 +1,375 @@
+package github
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "slices"
+ "strconv"
+
+ "github.com/google/go-github/v89/github"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+// maxInstallationRepositoriesPerRequest is the maximum number of repositories
+// the enterprise organization-installations endpoints accept per request.
+const maxInstallationRepositoriesPerRequest = 50
+
+func resourceGithubEnterpriseOrganizationAppInstallation() *schema.Resource {
+ return &schema.Resource{
+ Description: "Manage the installation of a GitHub App on an organization owned by an enterprise. " +
+ "The app is installed on the organization, not on the enterprise account itself; GitHub offers no API for the latter. " +
+ "This resource requires GitHub Enterprise Cloud or GitHub Enterprise Server 3.19+ and an authenticated user that is an enterprise owner.",
+ CreateContext: resourceGithubEnterpriseOrganizationAppInstallationCreate,
+ ReadContext: resourceGithubEnterpriseOrganizationAppInstallationRead,
+ UpdateContext: resourceGithubEnterpriseOrganizationAppInstallationUpdate,
+ DeleteContext: resourceGithubEnterpriseOrganizationAppInstallationDelete,
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+
+ Schema: map[string]*schema.Schema{
+ "enterprise_slug": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "The slug of the enterprise that owns the organization.",
+ },
+ "organization": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "The login of the enterprise-owned organization to install the app on.",
+ },
+ "client_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "The client ID of the GitHub App to install.",
+ },
+ "repository_selection": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateDiagFunc: validateValueFunc([]string{"all", "selected", "none"}),
+ Description: "The repositories the installation can access. Can be one of 'all', 'selected' or 'none'.",
+ },
+ "selected_repositories": {
+ Type: schema.TypeSet,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Optional: true,
+ Description: "The names of the repositories the installation can access when 'repository_selection' is 'selected'.",
+ },
+ "installation_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The ID of the installation.",
+ },
+ "app_slug": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The slug of the installed app.",
+ },
+ },
+
+ CustomizeDiff: customdiff.All(
+ // The API can only toggle an existing installation between 'all' and
+ // 'selected'; transitions involving 'none' require a reinstall. An
+ // unknown new value could resolve to 'none' during apply, so it must
+ // also force replacement, or the expanded plan would disagree with
+ // the initial one and Terraform would abort the apply.
+ customdiff.ForceNewIf("repository_selection", func(ctx context.Context, d *schema.ResourceDiff, meta any) bool {
+ if !d.NewValueKnown("repository_selection") {
+ return true
+ }
+ oldValue, newValue := d.GetChange("repository_selection")
+ return oldValue.(string) == "none" || newValue.(string) == "none"
+ }),
+ func(ctx context.Context, d *schema.ResourceDiff, meta any) error {
+ // Unknown values read back as their zero value, which would
+ // fail this check for configurations that are still valid.
+ if !d.NewValueKnown("repository_selection") || !d.NewValueKnown("selected_repositories") {
+ return nil
+ }
+
+ selection := d.Get("repository_selection").(string)
+ repoCount := d.Get("selected_repositories").(*schema.Set).Len()
+ if selection == "selected" && repoCount == 0 {
+ return fmt.Errorf("'selected_repositories' must be set when 'repository_selection' is 'selected'")
+ }
+ if selection != "selected" && repoCount > 0 {
+ return fmt.Errorf("'selected_repositories' can only be set when 'repository_selection' is 'selected'")
+ }
+ return nil
+ },
+ ),
+ }
+}
+
+func resourceGithubEnterpriseOrganizationAppInstallationCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ client := meta.(*Owner).v3client
+
+ enterpriseSlug := d.Get("enterprise_slug").(string)
+ org := d.Get("organization").(string)
+ clientID := d.Get("client_id").(string)
+
+ repositories := expandStringList(d.Get("selected_repositories").(*schema.Set).List())
+
+ // The install endpoint accepts at most maxInstallationRepositoriesPerRequest
+ // repositories; any remainder is granted with follow-up requests.
+ initial := repositories
+ var remainder []string
+ if len(repositories) > maxInstallationRepositoriesPerRequest {
+ initial = repositories[:maxInstallationRepositoriesPerRequest]
+ remainder = repositories[maxInstallationRepositoriesPerRequest:]
+ }
+
+ req := github.InstallAppRequest{
+ ClientID: clientID,
+ RepositorySelection: d.Get("repository_selection").(string),
+ Repositories: initial,
+ }
+
+ tflog.Debug(ctx, "Installing app on enterprise-owned organization", map[string]any{
+ "client_id": clientID,
+ "org": org,
+ "enterprise": enterpriseSlug,
+ })
+ installation, _, err := client.Enterprise.InstallApp(ctx, enterpriseSlug, org, req)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ // Record the installation 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. Delete resolves the
+ // installation through 'installation_id', so it has to be set here too.
+ id, err := buildID(enterpriseSlug, org, clientID)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ d.SetId(id)
+ if err := d.Set("installation_id", strconv.FormatInt(installation.GetID(), 10)); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("app_slug", installation.GetAppSlug()); err != nil {
+ return diag.FromErr(err)
+ }
+
+ if err := addEnterpriseOrganizationAppInstallationRepositories(ctx, client, enterpriseSlug, org, installation.GetID(), remainder); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
+
+func resourceGithubEnterpriseOrganizationAppInstallationRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ owner := meta.(*Owner)
+ client := owner.v3client
+
+ enterpriseSlug, org, clientID, err := parseID3(d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ installation, err := findEnterpriseOrganizationAppInstallation(ctx, owner, enterpriseSlug, org, clientID)
+ if err != nil {
+ var ghErr *github.ErrorResponse
+ if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusNotFound {
+ tflog.Info(ctx, "Removing enterprise app installation from state because it no longer exists in GitHub", map[string]any{
+ "id": d.Id(),
+ })
+ d.SetId("")
+ return nil
+ }
+ return diag.FromErr(err)
+ }
+ if installation == nil {
+ tflog.Info(ctx, "Removing enterprise app installation from state because it no longer exists in GitHub", map[string]any{
+ "id": d.Id(),
+ })
+ d.SetId("")
+ return nil
+ }
+
+ if err := d.Set("enterprise_slug", enterpriseSlug); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("organization", org); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("client_id", clientID); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("repository_selection", installation.GetRepositorySelection()); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("installation_id", strconv.FormatInt(installation.GetID(), 10)); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("app_slug", installation.GetAppSlug()); err != nil {
+ return diag.FromErr(err)
+ }
+
+ selectedRepositories := []string{}
+ if installation.GetRepositorySelection() == "selected" {
+ opts := &github.ListOptions{PerPage: owner.maxPerPage}
+ for repo, err := range client.Enterprise.ListRepositoriesForOrgAppInstallationIter(ctx, enterpriseSlug, org, installation.GetID(), opts) {
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ selectedRepositories = append(selectedRepositories, repo.GetName())
+ }
+ }
+ if err := d.Set("selected_repositories", flattenStringList(selectedRepositories)); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
+
+func resourceGithubEnterpriseOrganizationAppInstallationUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ client := meta.(*Owner).v3client
+
+ enterpriseSlug, org, _, err := parseID3(d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ installationID, err := strconv.ParseInt(d.Get("installation_id").(string), 10, 64)
+ if err != nil {
+ return diag.FromErr(unconvertibleIdErr(d.Get("installation_id").(string), err))
+ }
+
+ if d.HasChange("repository_selection") {
+ selection := d.Get("repository_selection").(string)
+ req := github.UpdateAppInstallationRepositoriesRequest{
+ RepositorySelection: new(selection),
+ }
+ var remainder []string
+ if selection == "selected" {
+ repositories := expandStringList(d.Get("selected_repositories").(*schema.Set).List())
+ // The toggle endpoint accepts at most
+ // maxInstallationRepositoriesPerRequest repositories; any remainder
+ // is granted with follow-up requests.
+ req.Repositories = repositories
+ if len(repositories) > maxInstallationRepositoriesPerRequest {
+ req.Repositories = repositories[:maxInstallationRepositoriesPerRequest]
+ remainder = repositories[maxInstallationRepositoriesPerRequest:]
+ }
+ }
+
+ tflog.Debug(ctx, "Updating repository selection for enterprise app installation", map[string]any{
+ "selection": selection,
+ "installation_id": installationID,
+ "org": org,
+ "enterprise": enterpriseSlug,
+ })
+ _, _, err := client.Enterprise.UpdateAppInstallationRepositories(ctx, enterpriseSlug, org, installationID, req)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ if err := addEnterpriseOrganizationAppInstallationRepositories(ctx, client, enterpriseSlug, org, installationID, remainder); err != nil {
+ return diag.FromErr(err)
+ }
+ } else if d.HasChange("selected_repositories") {
+ oldRepos, newRepos := d.GetChange("selected_repositories")
+ oldSet := oldRepos.(*schema.Set)
+ newSet := newRepos.(*schema.Set)
+
+ // Add before removing so the installation never has an empty selection.
+ toAdd := expandStringList(newSet.Difference(oldSet).List())
+ if err := addEnterpriseOrganizationAppInstallationRepositories(ctx, client, enterpriseSlug, org, installationID, toAdd); err != nil {
+ return diag.FromErr(err)
+ }
+
+ toRemove := expandStringList(oldSet.Difference(newSet).List())
+ for chunk := range slices.Chunk(toRemove, maxInstallationRepositoriesPerRequest) {
+ tflog.Debug(ctx, "Revoking enterprise app installation access to repositories", map[string]any{
+ "installation_id": installationID,
+ "org": org,
+ "enterprise": enterpriseSlug,
+ "repositories": chunk,
+ })
+ _, _, err := client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, enterpriseSlug, org, installationID, github.AppInstallationRepositoriesRequest{Repositories: chunk})
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func resourceGithubEnterpriseOrganizationAppInstallationDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ client := meta.(*Owner).v3client
+
+ enterpriseSlug, org, clientID, err := parseID3(d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ installationID, err := strconv.ParseInt(d.Get("installation_id").(string), 10, 64)
+ if err != nil {
+ return diag.FromErr(unconvertibleIdErr(d.Get("installation_id").(string), err))
+ }
+
+ tflog.Debug(ctx, "Uninstalling app from enterprise-owned organization", map[string]any{
+ "client_id": clientID,
+ "installation_id": installationID,
+ "org": org,
+ "enterprise": enterpriseSlug,
+ })
+ _, err = client.Enterprise.UninstallApp(ctx, enterpriseSlug, org, installationID)
+ if err != nil {
+ var ghErr *github.ErrorResponse
+ if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusNotFound {
+ return nil
+ }
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
+
+// addEnterpriseOrganizationAppInstallationRepositories grants an installation
+// access to the given repositories, in chunks the API accepts.
+func addEnterpriseOrganizationAppInstallationRepositories(ctx context.Context, client *github.Client, enterpriseSlug, org string, installationID int64, repositories []string) error {
+ for chunk := range slices.Chunk(repositories, maxInstallationRepositoriesPerRequest) {
+ tflog.Debug(ctx, "Granting enterprise app installation access to repositories", map[string]any{
+ "installation_id": installationID,
+ "org": org,
+ "enterprise": enterpriseSlug,
+ "repositories": chunk,
+ })
+ _, _, err := client.Enterprise.AddRepositoriesToAppInstallation(ctx, enterpriseSlug, org, installationID, github.AppInstallationRepositoriesRequest{Repositories: chunk})
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// findEnterpriseOrganizationAppInstallation returns the installation of the app
+// with the given client ID on an enterprise-owned organization, or nil if the
+// app is not installed.
+func findEnterpriseOrganizationAppInstallation(ctx context.Context, owner *Owner, enterpriseSlug, org, clientID string) (*github.Installation, error) {
+ opts := &github.ListOptions{PerPage: owner.maxPerPage}
+ for installation, err := range owner.v3client.Enterprise.ListAppInstallationsIter(ctx, enterpriseSlug, org, opts) {
+ if err != nil {
+ return nil, err
+ }
+ if installation.GetClientID() == clientID {
+ return installation, nil
+ }
+ }
+
+ return nil, nil
+}
diff --git a/github/resource_github_enterprise_organization_app_installation_test.go b/github/resource_github_enterprise_organization_app_installation_test.go
new file mode 100644
index 0000000000..d2270db3a3
--- /dev/null
+++ b/github/resource_github_enterprise_organization_app_installation_test.go
@@ -0,0 +1,117 @@
+package github
+
+import (
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/knownvalue"
+ "github.com/hashicorp/terraform-plugin-testing/statecheck"
+ "github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
+)
+
+func TestAccGithubEnterpriseOrganizationAppInstallation(t *testing.T) {
+ appClientID := os.Getenv("GH_TEST_ENTERPRISE_APP_CLIENT_ID")
+ skipUnlessEnterpriseAppClientID := func(t *testing.T) {
+ t.Helper()
+ if appClientID == "" {
+ t.Skip("Skipping because GH_TEST_ENTERPRISE_APP_CLIENT_ID is not set")
+ }
+ }
+
+ t.Run("installs an app on all repositories", func(t *testing.T) {
+ config := fmt.Sprintf(`
+ resource "github_enterprise_organization_app_installation" "test" {
+ enterprise_slug = "%s"
+ organization = "%s"
+ client_id = "%s"
+ repository_selection = "all"
+ }
+ `, testAccConf.enterpriseSlug, testAccConf.owner, appClientID)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ skipUnlessEnterprise(t)
+ skipUnlessEnterpriseAppClientID(t)
+ },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("enterprise_slug"), knownvalue.StringExact(testAccConf.enterpriseSlug)),
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("organization"), knownvalue.StringExact(testAccConf.owner)),
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("client_id"), knownvalue.StringExact(appClientID)),
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("repository_selection"), knownvalue.StringExact("all")),
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("installation_id"), knownvalue.NotNull()),
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("app_slug"), knownvalue.NotNull()),
+ },
+ },
+ {
+ ResourceName: "github_enterprise_organization_app_installation.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+ })
+
+ t.Run("installs an app on selected repositories and toggles to all", func(t *testing.T) {
+ randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
+
+ configSelected := fmt.Sprintf(`
+ resource "github_repository" "test" {
+ name = "tf-acc-test-eai-%[4]s"
+ auto_init = true
+ }
+
+ resource "github_enterprise_organization_app_installation" "test" {
+ enterprise_slug = "%[1]s"
+ organization = "%[2]s"
+ client_id = "%[3]s"
+ repository_selection = "selected"
+ selected_repositories = [github_repository.test.name]
+ }
+ `, testAccConf.enterpriseSlug, testAccConf.owner, appClientID, randomID)
+
+ configAll := fmt.Sprintf(`
+ resource "github_repository" "test" {
+ name = "tf-acc-test-eai-%[4]s"
+ auto_init = true
+ }
+
+ resource "github_enterprise_organization_app_installation" "test" {
+ enterprise_slug = "%[1]s"
+ organization = "%[2]s"
+ client_id = "%[3]s"
+ repository_selection = "all"
+ }
+ `, testAccConf.enterpriseSlug, testAccConf.owner, appClientID, randomID)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ skipUnlessEnterprise(t)
+ skipUnlessEnterpriseAppClientID(t)
+ },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: configSelected,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("repository_selection"), knownvalue.StringExact("selected")),
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("selected_repositories"), knownvalue.SetSizeExact(1)),
+ },
+ },
+ {
+ Config: configAll,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("repository_selection"), knownvalue.StringExact("all")),
+ statecheck.ExpectKnownValue("github_enterprise_organization_app_installation.test", tfjsonpath.New("selected_repositories"), knownvalue.SetSizeExact(0)),
+ },
+ },
+ },
+ })
+ })
+}