From b0965847c2748861c6870c2547955ff422412745 Mon Sep 17 00:00:00 2001 From: zry98 Date: Sun, 14 Jun 2026 11:27:36 +0200 Subject: [PATCH 1/4] add resources and data sources --- ...pp_accessible_organization_repositories.go | 82 +++++ ...nterprise_app_installable_organizations.go | 79 +++++ ...rce_github_enterprise_app_installations.go | 119 ++++++++ github/provider.go | 5 + ...urce_github_enterprise_app_installation.go | 285 ++++++++++++++++++ ...nterprise_app_installation_repositories.go | 199 ++++++++++++ 6 files changed, 769 insertions(+) create mode 100644 github/data_source_github_enterprise_app_accessible_organization_repositories.go create mode 100644 github/data_source_github_enterprise_app_installable_organizations.go create mode 100644 github/data_source_github_enterprise_app_installations.go create mode 100644 github/resource_github_enterprise_app_installation.go create mode 100644 github/resource_github_enterprise_app_installation_repositories.go diff --git a/github/data_source_github_enterprise_app_accessible_organization_repositories.go b/github/data_source_github_enterprise_app_accessible_organization_repositories.go new file mode 100644 index 0000000000..253886fcf9 --- /dev/null +++ b/github/data_source_github_enterprise_app_accessible_organization_repositories.go @@ -0,0 +1,82 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v88/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubEnterpriseAppAccessibleOrganizationRepositories() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceGithubEnterpriseAppAccessibleOrganizationRepositoriesRead, + Description: "Use this data source to retrieve repositories of an enterprise-owned organization that a GitHub App can be granted access to.", + + Schema: map[string]*schema.Schema{ + "enterprise_slug": { + Type: schema.TypeString, + Required: true, + Description: "The slug of the enterprise.", + }, + "organization": { + Type: schema.TypeString, + Required: true, + Description: "The login of the enterprise-owned organization.", + }, + "repositories": { + Type: schema.TypeList, + Computed: true, + Description: "Repositories of the organization a GitHub App can access.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeInt, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "full_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceGithubEnterpriseAppAccessibleOrganizationRepositoriesRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + client := m.(*Owner).v3client + enterprise := d.Get("enterprise_slug").(string) + org := d.Get("organization").(string) + + opts := &github.ListOptions{PerPage: maxPerPage} + results := make([]map[string]any, 0) + for { + repos, resp, err := client.Enterprise.ListAppAccessibleOrganizationRepositories(ctx, enterprise, org, opts) + if err != nil { + return diag.FromErr(err) + } + for _, r := range repos { + results = append(results, map[string]any{ + "id": r.ID, + "name": r.Name, + "full_name": r.FullName, + }) + } + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + d.SetId(buildTwoPartID(enterprise, org)) + if err := d.Set("repositories", results); err != nil { + return diag.FromErr(err) + } + return nil +} 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..8ea1fdd5bd --- /dev/null +++ b/github/data_source_github_enterprise_app_installable_organizations.go @@ -0,0 +1,79 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v88/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 organizations in an enterprise that a GitHub App can be installed on.", + + 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: "Organizations in the enterprise that can have a GitHub App installed on them.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeInt, + Computed: true, + }, + "login": { + Type: schema.TypeString, + Computed: true, + }, + "accessible_repositories_url": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceGithubEnterpriseAppInstallableOrganizationsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + client := m.(*Owner).v3client + enterprise := d.Get("enterprise_slug").(string) + + opts := &github.ListOptions{PerPage: maxPerPage} + results := make([]map[string]any, 0) + for { + orgs, resp, err := client.Enterprise.ListAppInstallableOrganizations(ctx, enterprise, opts) + if err != nil { + return diag.FromErr(err) + } + for _, o := range orgs { + entry := map[string]any{ + "id": o.ID, + "login": o.Login, + } + if o.AccessibleRepositoriesURL != nil { + entry["accessible_repositories_url"] = *o.AccessibleRepositoriesURL + } + results = append(results, entry) + } + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + d.SetId(enterprise) + if err := d.Set("organizations", results); err != nil { + return diag.FromErr(err) + } + return nil +} diff --git a/github/data_source_github_enterprise_app_installations.go b/github/data_source_github_enterprise_app_installations.go new file mode 100644 index 0000000000..3eb39f952e --- /dev/null +++ b/github/data_source_github_enterprise_app_installations.go @@ -0,0 +1,119 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v88/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubEnterpriseAppInstallations() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceGithubEnterpriseAppInstallationsRead, + Description: "Use this data source to retrieve the GitHub App installations on an enterprise-owned organization.", + + 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, + }, + "app_id": { + Type: schema.TypeInt, + Computed: true, + }, + "app_slug": { + Type: schema.TypeString, + Computed: true, + }, + "client_id": { + Type: schema.TypeString, + Computed: true, + }, + "target_id": { + Type: schema.TypeInt, + Computed: true, + }, + "target_type": { + Type: schema.TypeString, + Computed: true, + }, + "repository_selection": { + Type: schema.TypeString, + Computed: true, + }, + "permissions": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "events": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "suspended": { + Type: schema.TypeBool, + Computed: true, + }, + "single_file_paths": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceGithubEnterpriseAppInstallationsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + client := m.(*Owner).v3client + enterprise := d.Get("enterprise_slug").(string) + org := d.Get("organization").(string) + + opts := &github.ListOptions{PerPage: maxPerPage} + results := make([]map[string]any, 0) + for { + installations, resp, err := client.Enterprise.ListAppInstallations(ctx, enterprise, org, opts) + if err != nil { + return diag.FromErr(err) + } + results = append(results, flattenGitHubAppInstallations(installations)...) + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + d.SetId(buildTwoPartID(enterprise, org)) + if err := d.Set("installations", results); err != nil { + return diag.FromErr(err) + } + return nil +} diff --git a/github/provider.go b/github/provider.go index 0bd2720b05..3107e94204 100644 --- a/github/provider.go +++ b/github/provider.go @@ -242,6 +242,8 @@ func NewProvider(version, commit string) func() *schema.Provider { "github_user_ssh_key": resourceGithubUserSshKey(), "github_enterprise_organization": resourceGithubEnterpriseOrganization(), "github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(), + "github_enterprise_app_installation": resourceGithubEnterpriseAppInstallation(), + "github_enterprise_app_installation_repositories": resourceGithubEnterpriseAppInstallationRepositories(), "github_enterprise_ip_allow_list_entry": resourceGithubEnterpriseIpAllowListEntry(), "github_enterprise_actions_workflow_permissions": resourceGithubEnterpriseActionsWorkflowPermissions(), "github_actions_organization_workflow_permissions": resourceGithubActionsOrganizationWorkflowPermissions(), @@ -328,6 +330,9 @@ func NewProvider(version, commit string) func() *schema.Provider { "github_user_external_identity": dataSourceGithubUserExternalIdentity(), "github_users": dataSourceGithubUsers(), "github_enterprise": dataSourceGithubEnterprise(), + "github_enterprise_app_installations": dataSourceGithubEnterpriseAppInstallations(), + "github_enterprise_app_installable_organizations": dataSourceGithubEnterpriseAppInstallableOrganizations(), + "github_enterprise_app_accessible_organization_repositories": dataSourceGithubEnterpriseAppAccessibleOrganizationRepositories(), "github_repository_environment_deployment_policies": dataSourceGithubRepositoryEnvironmentDeploymentPolicies(), }, diff --git a/github/resource_github_enterprise_app_installation.go b/github/resource_github_enterprise_app_installation.go new file mode 100644 index 0000000000..7c4d100cc3 --- /dev/null +++ b/github/resource_github_enterprise_app_installation.go @@ -0,0 +1,285 @@ +package github + +import ( + "context" + "errors" + "fmt" + "log" + "net/http" + "strconv" + + "github.com/google/go-github/v88/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceGithubEnterpriseAppInstallation() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubEnterpriseAppInstallationCreate, + Read: resourceGithubEnterpriseAppInstallationRead, + Update: resourceGithubEnterpriseAppInstallationUpdate, + Delete: resourceGithubEnterpriseAppInstallationDelete, + Importer: &schema.ResourceImporter{ + State: resourceGithubEnterpriseAppInstallationImport, + }, + + 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, + Description: "Which repositories the app can access. One of 'all', 'selected', or 'none'.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"all", "selected", "none"}, false)), + }, + "repositories": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Description: "Repository names the installation should have access to. Only used when repository_selection is 'selected'.", + }, + "installation_id": { + Type: schema.TypeInt, + Computed: true, + Description: "The ID of the GitHub App installation.", + }, + "app_id": { + Type: schema.TypeInt, + Computed: true, + Description: "The ID of the GitHub App.", + }, + "app_slug": { + Type: schema.TypeString, + Computed: true, + Description: "The URL-friendly name of the GitHub App.", + }, + }, + } +} + +func resourceGithubEnterpriseAppInstallationCreate(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + ctx := context.Background() + + enterprise := d.Get("enterprise_slug").(string) + org := d.Get("organization").(string) + selection := d.Get("repository_selection").(string) + + req := github.InstallAppRequest{ + ClientID: d.Get("client_id").(string), + RepositorySelection: selection, + } + if selection == "selected" { + req.Repositories = expandStringList(d.Get("repositories").(*schema.Set).List()) + } + + installation, _, err := client.Enterprise.InstallApp(ctx, enterprise, org, req) + if err != nil { + return fmt.Errorf("error installing GitHub App on %s/%s: %w", enterprise, org, err) + } + + d.SetId(buildThreePartID(enterprise, org, strconv.FormatInt(installation.GetID(), 10))) + return resourceGithubEnterpriseAppInstallationRead(d, meta) +} + +func resourceGithubEnterpriseAppInstallationRead(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + enterprise, org, idStr, err := parseID3(d.Id()) + if err != nil { + return err + } + installationID, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return unconvertibleIdErr(idStr, err) + } + + installation, err := findEnterpriseAppInstallation(ctx, client, enterprise, org, installationID) + if err != nil { + var ghErr *github.ErrorResponse + if errors.As(err, &ghErr) && ghErr.Response != nil && ghErr.Response.StatusCode == http.StatusNotFound { + log.Printf("[INFO] Removing enterprise app installation %s from state because it no longer exists in GitHub", d.Id()) + d.SetId("") + return nil + } + return err + } + if installation == nil { + log.Printf("[INFO] Removing enterprise app installation %s from state because it no longer exists in GitHub", d.Id()) + d.SetId("") + return nil + } + + if err = d.Set("enterprise_slug", enterprise); err != nil { + return err + } + if err = d.Set("organization", org); err != nil { + return err + } + if err = d.Set("installation_id", installation.GetID()); err != nil { + return err + } + if err = d.Set("app_id", installation.GetAppID()); err != nil { + return err + } + if err = d.Set("app_slug", installation.GetAppSlug()); err != nil { + return err + } + if v := installation.GetRepositorySelection(); v != "" { + if err = d.Set("repository_selection", v); err != nil { + return err + } + } + + if installation.GetRepositorySelection() == "selected" { + repos, err := listEnterpriseAppInstallationRepositories(ctx, client, enterprise, org, installationID) + if err != nil { + return err + } + names := make([]string, 0, len(repos)) + for _, r := range repos { + names = append(names, r.Name) + } + if err = d.Set("repositories", names); err != nil { + return err + } + } else { + if err = d.Set("repositories", []string{}); err != nil { + return err + } + } + + return nil +} + +func resourceGithubEnterpriseAppInstallationUpdate(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + enterprise, org, idStr, err := parseID3(d.Id()) + if err != nil { + return err + } + installationID, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return unconvertibleIdErr(idStr, err) + } + + selection := d.Get("repository_selection").(string) + + if d.HasChange("repository_selection") { + opts := github.UpdateAppInstallationRepositoriesRequest{ + RepositorySelection: &selection, + } + if selection == "selected" { + opts.Repositories = expandStringList(d.Get("repositories").(*schema.Set).List()) + } + if _, _, err := client.Enterprise.UpdateAppInstallationRepositories(ctx, enterprise, org, installationID, opts); err != nil { + return fmt.Errorf("error updating repository_selection for installation %d: %w", installationID, err) + } + } else if selection == "selected" && d.HasChange("repositories") { + oldVal, newVal := d.GetChange("repositories") + oldSet := oldVal.(*schema.Set) + newSet := newVal.(*schema.Set) + + toAdd := expandStringList(newSet.Difference(oldSet).List()) + toRemove := expandStringList(oldSet.Difference(newSet).List()) + + if len(toAdd) > 0 { + if _, _, err := client.Enterprise.AddRepositoriesToAppInstallation(ctx, enterprise, org, installationID, github.AppInstallationRepositoriesRequest{Repositories: toAdd}); err != nil { + return fmt.Errorf("error adding repositories to installation %d: %w", installationID, err) + } + } + if len(toRemove) > 0 { + if _, _, err := client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, enterprise, org, installationID, github.AppInstallationRepositoriesRequest{Repositories: toRemove}); err != nil { + return fmt.Errorf("error removing repositories from installation %d: %w", installationID, err) + } + } + } + + return resourceGithubEnterpriseAppInstallationRead(d, meta) +} + +func resourceGithubEnterpriseAppInstallationDelete(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + enterprise, org, idStr, err := parseID3(d.Id()) + if err != nil { + return err + } + installationID, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + return unconvertibleIdErr(idStr, err) + } + + _, err = client.Enterprise.UninstallApp(ctx, enterprise, org, installationID) + return err +} + +func resourceGithubEnterpriseAppInstallationImport(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + if _, _, _, err := parseID3(d.Id()); err != nil { + return nil, fmt.Errorf("invalid ID specified: supplied ID must be written as ::") + } + if err := resourceGithubEnterpriseAppInstallationRead(d, meta); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} + +// findEnterpriseAppInstallation walks the enterprise app installations on an organization +// to locate the installation matching the given ID. The REST API does not provide a direct +// GET endpoint for a single enterprise-owned org installation, so a list-and-filter is used. +func findEnterpriseAppInstallation(ctx context.Context, client *github.Client, enterprise, org string, installationID int64) (*github.Installation, error) { + opts := &github.ListOptions{PerPage: maxPerPage} + for { + installations, resp, err := client.Enterprise.ListAppInstallations(ctx, enterprise, org, opts) + if err != nil { + return nil, err + } + for _, inst := range installations { + if inst.GetID() == installationID { + return inst, nil + } + } + if resp.NextPage == 0 { + return nil, nil + } + opts.Page = resp.NextPage + } +} + +func listEnterpriseAppInstallationRepositories(ctx context.Context, client *github.Client, enterprise, org string, installationID int64) ([]*github.AccessibleRepository, error) { + var all []*github.AccessibleRepository + opts := &github.ListOptions{PerPage: maxPerPage} + for { + repos, resp, err := client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, enterprise, org, installationID, opts) + if err != nil { + return nil, err + } + all = append(all, repos...) + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + return all, nil +} diff --git a/github/resource_github_enterprise_app_installation_repositories.go b/github/resource_github_enterprise_app_installation_repositories.go new file mode 100644 index 0000000000..405cb0b643 --- /dev/null +++ b/github/resource_github_enterprise_app_installation_repositories.go @@ -0,0 +1,199 @@ +package github + +import ( + "context" + "errors" + "fmt" + "log" + "net/http" + "strconv" + + "github.com/google/go-github/v88/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceGithubEnterpriseAppInstallationRepositories() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubEnterpriseAppInstallationRepositoriesCreateOrUpdate, + Read: resourceGithubEnterpriseAppInstallationRepositoriesRead, + Update: resourceGithubEnterpriseAppInstallationRepositoriesCreateOrUpdate, + Delete: resourceGithubEnterpriseAppInstallationRepositoriesDelete, + Importer: &schema.ResourceImporter{ + State: resourceGithubEnterpriseAppInstallationRepositoriesImport, + }, + + 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 the app is installed on.", + }, + "installation_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The ID of the GitHub App installation.", + }, + "selected_repositories": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + Description: "A set of repository names the installation should have access to.", + }, + }, + } +} + +func resourceGithubEnterpriseAppInstallationRepositoriesCreateOrUpdate(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + ctx := context.Background() + + enterprise := d.Get("enterprise_slug").(string) + org := d.Get("organization").(string) + installationIDString := d.Get("installation_id").(string) + installationID, err := strconv.ParseInt(installationIDString, 10, 64) + if err != nil { + return unconvertibleIdErr(installationIDString, err) + } + + desired := stringSetFromAny(d.Get("selected_repositories").(*schema.Set).List()) + + current, err := listEnterpriseAppInstallationRepositories(ctx, client, enterprise, org, installationID) + if err != nil { + return err + } + currentSet := make(map[string]struct{}, len(current)) + for _, r := range current { + currentSet[r.Name] = struct{}{} + } + + var toAdd, toRemove []string + for name := range desired { + if _, ok := currentSet[name]; !ok { + toAdd = append(toAdd, name) + } + } + for name := range currentSet { + if _, ok := desired[name]; !ok { + toRemove = append(toRemove, name) + } + } + + if len(toAdd) > 0 { + log.Printf("[DEBUG] Adding %d repositories to enterprise app installation %d", len(toAdd), installationID) + if _, _, err := client.Enterprise.AddRepositoriesToAppInstallation(ctx, enterprise, org, installationID, github.AppInstallationRepositoriesRequest{Repositories: toAdd}); err != nil { + return fmt.Errorf("error adding repositories to enterprise app installation %d: %w", installationID, err) + } + } + if len(toRemove) > 0 { + log.Printf("[DEBUG] Removing %d repositories from enterprise app installation %d", len(toRemove), installationID) + if _, _, err := client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, enterprise, org, installationID, github.AppInstallationRepositoriesRequest{Repositories: toRemove}); err != nil { + return fmt.Errorf("error removing repositories from enterprise app installation %d: %w", installationID, err) + } + } + + d.SetId(buildThreePartID(enterprise, org, installationIDString)) + return resourceGithubEnterpriseAppInstallationRepositoriesRead(d, meta) +} + +func resourceGithubEnterpriseAppInstallationRepositoriesRead(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + enterprise, org, installationIDString, err := parseID3(d.Id()) + if err != nil { + return err + } + installationID, err := strconv.ParseInt(installationIDString, 10, 64) + if err != nil { + return unconvertibleIdErr(installationIDString, err) + } + + repos, err := listEnterpriseAppInstallationRepositories(ctx, client, enterprise, org, installationID) + if err != nil { + var ghErr *github.ErrorResponse + if errors.As(err, &ghErr) && ghErr.Response != nil && ghErr.Response.StatusCode == http.StatusNotFound { + log.Printf("[INFO] Removing enterprise app installation repositories %s from state because the installation no longer exists", d.Id()) + d.SetId("") + return nil + } + return err + } + + names := make([]string, 0, len(repos)) + for _, r := range repos { + names = append(names, r.Name) + } + + if err = d.Set("enterprise_slug", enterprise); err != nil { + return err + } + if err = d.Set("organization", org); err != nil { + return err + } + if err = d.Set("installation_id", installationIDString); err != nil { + return err + } + if err = d.Set("selected_repositories", names); err != nil { + return err + } + return nil +} + +func resourceGithubEnterpriseAppInstallationRepositoriesDelete(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + enterprise, org, installationIDString, err := parseID3(d.Id()) + if err != nil { + return err + } + installationID, err := strconv.ParseInt(installationIDString, 10, 64) + if err != nil { + return unconvertibleIdErr(installationIDString, err) + } + + current, err := listEnterpriseAppInstallationRepositories(ctx, client, enterprise, org, installationID) + if err != nil { + return err + } + if len(current) == 0 { + return nil + } + + names := make([]string, 0, len(current)) + for _, r := range current { + names = append(names, r.Name) + } + + _, _, err = client.Enterprise.RemoveRepositoriesFromAppInstallation(ctx, enterprise, org, installationID, github.AppInstallationRepositoriesRequest{Repositories: names}) + return err +} + +func resourceGithubEnterpriseAppInstallationRepositoriesImport(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + if _, _, _, err := parseID3(d.Id()); err != nil { + return nil, fmt.Errorf("invalid ID specified: supplied ID must be written as ::") + } + if err := resourceGithubEnterpriseAppInstallationRepositoriesRead(d, meta); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} + +func stringSetFromAny(vs []any) map[string]struct{} { + out := make(map[string]struct{}, len(vs)) + for _, v := range vs { + if s, ok := v.(string); ok && s != "" { + out[s] = struct{}{} + } + } + return out +} From 5fc47732cdc054c7afd9371b71e0d768e99e5990 Mon Sep 17 00:00:00 2001 From: zry98 Date: Sun, 14 Jun 2026 11:27:53 +0200 Subject: [PATCH 2/4] add docs templates and examples --- .../example_1.tf | 4 ++ .../example_1.tf | 3 ++ .../enterprise_app_installations/example_1.tf | 4 ++ .../enterprise_app_installation/example_1.tf | 7 +++ .../example_1.tf | 6 +++ ...cessible_organization_repositories.md.tmpl | 31 +++++++++++++ ...rise_app_installable_organizations.md.tmpl | 30 +++++++++++++ .../enterprise_app_installations.md.tmpl | 41 ++++++++++++++++++ .../enterprise_app_installation.md.tmpl | 43 +++++++++++++++++++ ...rise_app_installation_repositories.md.tmpl | 40 +++++++++++++++++ 10 files changed, 209 insertions(+) create mode 100644 examples/data-sources/enterprise_app_accessible_organization_repositories/example_1.tf create mode 100644 examples/data-sources/enterprise_app_installable_organizations/example_1.tf create mode 100644 examples/data-sources/enterprise_app_installations/example_1.tf create mode 100644 examples/resources/enterprise_app_installation/example_1.tf create mode 100644 examples/resources/enterprise_app_installation_repositories/example_1.tf create mode 100644 templates/data-sources/enterprise_app_accessible_organization_repositories.md.tmpl create mode 100644 templates/data-sources/enterprise_app_installable_organizations.md.tmpl create mode 100644 templates/data-sources/enterprise_app_installations.md.tmpl create mode 100644 templates/resources/enterprise_app_installation.md.tmpl create mode 100644 templates/resources/enterprise_app_installation_repositories.md.tmpl diff --git a/examples/data-sources/enterprise_app_accessible_organization_repositories/example_1.tf b/examples/data-sources/enterprise_app_accessible_organization_repositories/example_1.tf new file mode 100644 index 0000000000..29e387abf7 --- /dev/null +++ b/examples/data-sources/enterprise_app_accessible_organization_repositories/example_1.tf @@ -0,0 +1,4 @@ +data "github_enterprise_app_accessible_organization_repositories" "example" { + enterprise_slug = "my-enterprise" + organization = "my-org" +} diff --git a/examples/data-sources/enterprise_app_installable_organizations/example_1.tf b/examples/data-sources/enterprise_app_installable_organizations/example_1.tf new file mode 100644 index 0000000000..51fc4906fd --- /dev/null +++ b/examples/data-sources/enterprise_app_installable_organizations/example_1.tf @@ -0,0 +1,3 @@ +data "github_enterprise_app_installable_organizations" "example" { + enterprise_slug = "my-enterprise" +} diff --git a/examples/data-sources/enterprise_app_installations/example_1.tf b/examples/data-sources/enterprise_app_installations/example_1.tf new file mode 100644 index 0000000000..5879719cd6 --- /dev/null +++ b/examples/data-sources/enterprise_app_installations/example_1.tf @@ -0,0 +1,4 @@ +data "github_enterprise_app_installations" "example" { + enterprise_slug = "my-enterprise" + organization = "my-org" +} diff --git a/examples/resources/enterprise_app_installation/example_1.tf b/examples/resources/enterprise_app_installation/example_1.tf new file mode 100644 index 0000000000..503c5cf71a --- /dev/null +++ b/examples/resources/enterprise_app_installation/example_1.tf @@ -0,0 +1,7 @@ +resource "github_enterprise_app_installation" "example" { + enterprise_slug = "my-enterprise" + organization = "my-org" + client_id = "Iv23liABCDEFGH012345" + repository_selection = "selected" + repositories = ["repo-a", "repo-b"] +} diff --git a/examples/resources/enterprise_app_installation_repositories/example_1.tf b/examples/resources/enterprise_app_installation_repositories/example_1.tf new file mode 100644 index 0000000000..20ee8a8143 --- /dev/null +++ b/examples/resources/enterprise_app_installation_repositories/example_1.tf @@ -0,0 +1,6 @@ +resource "github_enterprise_app_installation_repositories" "example" { + enterprise_slug = "my-enterprise" + organization = "my-org" + installation_id = "12345678" + selected_repositories = ["repo-a", "repo-b"] +} diff --git a/templates/data-sources/enterprise_app_accessible_organization_repositories.md.tmpl b/templates/data-sources/enterprise_app_accessible_organization_repositories.md.tmpl new file mode 100644 index 0000000000..4f2f404673 --- /dev/null +++ b/templates/data-sources/enterprise_app_accessible_organization_repositories.md.tmpl @@ -0,0 +1,31 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Get the repositories of an enterprise-owned organization that a GitHub App can be granted access to. +--- + +# {{.Name}} ({{.Type}}) + +Use this data source to retrieve the repositories of an enterprise-owned organization +that a GitHub App can be granted access to. + +## Example Usage + +{{ tffile "examples/data-sources/enterprise_app_accessible_organization_repositories/example_1.tf" }} + +## Argument Reference + +- `enterprise_slug` - (Required) The slug of the enterprise. +- `organization` - (Required) The login of the enterprise-owned organization. + +## Attributes Reference + +- `repositories` - List of repositories. Each `repository` block consists of the fields documented below. + +--- + +The `repository` block consists of: + +- `id` - The ID of the repository. +- `name` - The name of the repository. +- `full_name` - The full name of the repository (`org/repo`). diff --git a/templates/data-sources/enterprise_app_installable_organizations.md.tmpl b/templates/data-sources/enterprise_app_installable_organizations.md.tmpl new file mode 100644 index 0000000000..3ad8756f2d --- /dev/null +++ b/templates/data-sources/enterprise_app_installable_organizations.md.tmpl @@ -0,0 +1,30 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Get the organizations in an enterprise that a GitHub App can be installed on. +--- + +# {{.Name}} ({{.Type}}) + +Use this data source to retrieve the enterprise-owned organizations that a GitHub +App can be installed on. + +## Example Usage + +{{ tffile "examples/data-sources/enterprise_app_installable_organizations/example_1.tf" }} + +## Argument Reference + +- `enterprise_slug` - (Required) The slug of the enterprise. + +## Attributes Reference + +- `organizations` - List of organizations. Each `organization` block consists of the fields documented below. + +--- + +The `organization` block consists of: + +- `id` - The ID of the organization. +- `login` - The login (slug) of the organization. +- `accessible_repositories_url` - The URL for the repositories the app can access on the organization. diff --git a/templates/data-sources/enterprise_app_installations.md.tmpl b/templates/data-sources/enterprise_app_installations.md.tmpl new file mode 100644 index 0000000000..5e29706a38 --- /dev/null +++ b/templates/data-sources/enterprise_app_installations.md.tmpl @@ -0,0 +1,41 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Get the GitHub App installations of an enterprise-owned organization. +--- + +# {{.Name}} ({{.Type}}) + +Use this data source to retrieve the GitHub App installations on an enterprise-owned +organization. + +## Example Usage + +{{ tffile "examples/data-sources/enterprise_app_installations/example_1.tf" }} + +## Argument Reference + +- `enterprise_slug` - (Required) The slug of the enterprise that owns the organization. +- `organization` - (Required) The login of the enterprise-owned organization. + +## Attributes Reference + +- `installations` - List of GitHub App installations on the organization. Each `installation` block consists of the fields documented below. + +--- + +The `installation` block consists of: + +- `id` - The ID of the GitHub App installation. +- `app_id` - The ID of the GitHub App. +- `app_slug` - The URL-friendly name of the GitHub App. +- `client_id` - The OAuth client ID of the GitHub App. +- `target_id` - The ID of the account the GitHub App is installed on. +- `target_type` - The type of account the GitHub App is installed on. +- `repository_selection` - Whether the installation has access to `all` repositories or only `selected` ones. +- `permissions` - A map of the permissions granted to the GitHub App installation. +- `events` - The list of events the GitHub App installation subscribes to. +- `suspended` - Whether the GitHub App installation is currently suspended. +- `single_file_paths` - The list of single file paths the GitHub App installation has access to. +- `created_at` - The date the GitHub App installation was created. +- `updated_at` - The date the GitHub App installation was last updated. diff --git a/templates/resources/enterprise_app_installation.md.tmpl b/templates/resources/enterprise_app_installation.md.tmpl new file mode 100644 index 0000000000..3b704346ad --- /dev/null +++ b/templates/resources/enterprise_app_installation.md.tmpl @@ -0,0 +1,43 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Install and manage a GitHub App on an enterprise-owned organization. +--- + +# {{.Name}} ({{.Type}}) + +This resource installs a GitHub App on an enterprise-owned organization and manages +the installation's repository access. Deleting the resource uninstalls the app from +the organization. + +The token used by the provider must have permission to administer the enterprise, +and the organization must be owned by the enterprise. + +## Example Usage + +{{ tffile "examples/resources/enterprise_app_installation/example_1.tf" }} + +## Argument Reference + +- `enterprise_slug` - (Required, ForceNew) The slug of the enterprise that owns the organization. +- `organization` - (Required, ForceNew) The login of the enterprise-owned organization to install the app on. +- `client_id` - (Required, ForceNew) The Client ID of the GitHub App to install. +- `repository_selection` - (Required) Which repositories the app can access. One of `all`, `selected`, or `none`. +- `repositories` - (Optional) Repository names the installation should have access to. Only used when `repository_selection` is `selected`. + +## Attributes Reference + +The following additional attributes are exported: + +- `installation_id` - The ID of the GitHub App installation. +- `app_id` - The ID of the installed GitHub App. +- `app_slug` - The URL-friendly name of the GitHub App. + +## Import + +Enterprise App Installations can be imported using a composite ID of +`::`: + +```shell +terraform import github_enterprise_app_installation.example my-enterprise:my-org:12345678 +``` diff --git a/templates/resources/enterprise_app_installation_repositories.md.tmpl b/templates/resources/enterprise_app_installation_repositories.md.tmpl new file mode 100644 index 0000000000..2fc4207978 --- /dev/null +++ b/templates/resources/enterprise_app_installation_repositories.md.tmpl @@ -0,0 +1,40 @@ +--- +page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}" +description: |- + Manage the repositories an enterprise-owned organization's GitHub App installation can access. +--- + +# {{.Name}} ({{.Type}}) + +This resource manages the set of repositories accessible to a GitHub App installation +on an enterprise-owned organization. It only applies when the installation's +`repository_selection` is `selected`. + +Use [`github_enterprise_app_installation`](enterprise_app_installation.md) to control +installation lifecycle and `repository_selection`; use this resource to drift-detect +and reconcile the repository list. + +## Example Usage + +{{ tffile "examples/resources/enterprise_app_installation_repositories/example_1.tf" }} + +## Argument Reference + +- `enterprise_slug` - (Required, ForceNew) The slug of the enterprise that owns the organization. +- `organization` - (Required, ForceNew) The login of the enterprise-owned organization the app is installed on. +- `installation_id` - (Required, ForceNew) The ID of the GitHub App installation. +- `selected_repositories` - (Required) The set of repository names the installation should have access to. + +~> **Note**: Deleting this resource removes every repository currently selected for +the installation, which leaves the installation with no accessible repositories. +Either uninstall the app via the parent `github_enterprise_app_installation` resource +or switch the installation's `repository_selection` to `all` instead. + +## Import + +Enterprise App Installation Repositories can be imported using a composite ID of +`::`: + +```shell +terraform import github_enterprise_app_installation_repositories.example my-enterprise:my-org:12345678 +``` From 47d404c77544eed9e6ac2b64a7a967209e3c7445 Mon Sep 17 00:00:00 2001 From: zry98 Date: Sun, 14 Jun 2026 11:30:25 +0200 Subject: [PATCH 3/4] add generated docs --- ...pp_accessible_organization_repositories.md | 36 +++++++++++++ ...nterprise_app_installable_organizations.md | 34 +++++++++++++ .../enterprise_app_installations.md | 46 +++++++++++++++++ docs/resources/enterprise_app_installation.md | 51 +++++++++++++++++++ ...nterprise_app_installation_repositories.md | 47 +++++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 docs/data-sources/enterprise_app_accessible_organization_repositories.md create mode 100644 docs/data-sources/enterprise_app_installable_organizations.md create mode 100644 docs/data-sources/enterprise_app_installations.md create mode 100644 docs/resources/enterprise_app_installation.md create mode 100644 docs/resources/enterprise_app_installation_repositories.md diff --git a/docs/data-sources/enterprise_app_accessible_organization_repositories.md b/docs/data-sources/enterprise_app_accessible_organization_repositories.md new file mode 100644 index 0000000000..df140abf1c --- /dev/null +++ b/docs/data-sources/enterprise_app_accessible_organization_repositories.md @@ -0,0 +1,36 @@ +--- +page_title: "github_enterprise_app_accessible_organization_repositories (Data Source) - GitHub" +description: |- + Get the repositories of an enterprise-owned organization that a GitHub App can be granted access to. +--- + +# github_enterprise_app_accessible_organization_repositories (Data Source) + +Use this data source to retrieve the repositories of an enterprise-owned organization +that a GitHub App can be granted access to. + +## Example Usage + +```terraform +data "github_enterprise_app_accessible_organization_repositories" "example" { + enterprise_slug = "my-enterprise" + organization = "my-org" +} +``` + +## Argument Reference + +- `enterprise_slug` - (Required) The slug of the enterprise. +- `organization` - (Required) The login of the enterprise-owned organization. + +## Attributes Reference + +- `repositories` - List of repositories. Each `repository` block consists of the fields documented below. + +--- + +The `repository` block consists of: + +- `id` - The ID of the repository. +- `name` - The name of the repository. +- `full_name` - The full name of the repository (`org/repo`). 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..915391b610 --- /dev/null +++ b/docs/data-sources/enterprise_app_installable_organizations.md @@ -0,0 +1,34 @@ +--- +page_title: "github_enterprise_app_installable_organizations (Data Source) - GitHub" +description: |- + Get the organizations in an enterprise that a GitHub App can be installed on. +--- + +# github_enterprise_app_installable_organizations (Data Source) + +Use this data source to retrieve the enterprise-owned organizations that a GitHub +App can be installed on. + +## Example Usage + +```terraform +data "github_enterprise_app_installable_organizations" "example" { + enterprise_slug = "my-enterprise" +} +``` + +## Argument Reference + +- `enterprise_slug` - (Required) The slug of the enterprise. + +## Attributes Reference + +- `organizations` - List of organizations. Each `organization` block consists of the fields documented below. + +--- + +The `organization` block consists of: + +- `id` - The ID of the organization. +- `login` - The login (slug) of the organization. +- `accessible_repositories_url` - The URL for the repositories the app can access on the organization. diff --git a/docs/data-sources/enterprise_app_installations.md b/docs/data-sources/enterprise_app_installations.md new file mode 100644 index 0000000000..c853028e37 --- /dev/null +++ b/docs/data-sources/enterprise_app_installations.md @@ -0,0 +1,46 @@ +--- +page_title: "github_enterprise_app_installations (Data Source) - GitHub" +description: |- + Get the GitHub App installations of an enterprise-owned organization. +--- + +# github_enterprise_app_installations (Data Source) + +Use this data source to retrieve the GitHub App installations on an enterprise-owned +organization. + +## Example Usage + +```terraform +data "github_enterprise_app_installations" "example" { + enterprise_slug = "my-enterprise" + organization = "my-org" +} +``` + +## Argument Reference + +- `enterprise_slug` - (Required) The slug of the enterprise that owns the organization. +- `organization` - (Required) The login of the enterprise-owned organization. + +## Attributes Reference + +- `installations` - List of GitHub App installations on the organization. Each `installation` block consists of the fields documented below. + +--- + +The `installation` block consists of: + +- `id` - The ID of the GitHub App installation. +- `app_id` - The ID of the GitHub App. +- `app_slug` - The URL-friendly name of the GitHub App. +- `client_id` - The OAuth client ID of the GitHub App. +- `target_id` - The ID of the account the GitHub App is installed on. +- `target_type` - The type of account the GitHub App is installed on. +- `repository_selection` - Whether the installation has access to `all` repositories or only `selected` ones. +- `permissions` - A map of the permissions granted to the GitHub App installation. +- `events` - The list of events the GitHub App installation subscribes to. +- `suspended` - Whether the GitHub App installation is currently suspended. +- `single_file_paths` - The list of single file paths the GitHub App installation has access to. +- `created_at` - The date the GitHub App installation was created. +- `updated_at` - The date the GitHub App installation was last updated. diff --git a/docs/resources/enterprise_app_installation.md b/docs/resources/enterprise_app_installation.md new file mode 100644 index 0000000000..257ceda908 --- /dev/null +++ b/docs/resources/enterprise_app_installation.md @@ -0,0 +1,51 @@ +--- +page_title: "github_enterprise_app_installation (Resource) - GitHub" +description: |- + Install and manage a GitHub App on an enterprise-owned organization. +--- + +# github_enterprise_app_installation (Resource) + +This resource installs a GitHub App on an enterprise-owned organization and manages +the installation's repository access. Deleting the resource uninstalls the app from +the organization. + +The token used by the provider must have permission to administer the enterprise, +and the organization must be owned by the enterprise. + +## Example Usage + +```terraform +resource "github_enterprise_app_installation" "example" { + enterprise_slug = "my-enterprise" + organization = "my-org" + client_id = "Iv23liABCDEFGH012345" + repository_selection = "selected" + repositories = ["repo-a", "repo-b"] +} +``` + +## Argument Reference + +- `enterprise_slug` - (Required, ForceNew) The slug of the enterprise that owns the organization. +- `organization` - (Required, ForceNew) The login of the enterprise-owned organization to install the app on. +- `client_id` - (Required, ForceNew) The Client ID of the GitHub App to install. +- `repository_selection` - (Required) Which repositories the app can access. One of `all`, `selected`, or `none`. +- `repositories` - (Optional) Repository names the installation should have access to. Only used when `repository_selection` is `selected`. + +## Attributes Reference + +The following additional attributes are exported: + +- `installation_id` - The ID of the GitHub App installation. +- `app_id` - The ID of the installed GitHub App. +- `app_slug` - The URL-friendly name of the GitHub App. + +## Import + +Enterprise App Installations can be imported using a composite ID of +`::`: + +```shell +terraform import github_enterprise_app_installation.example my-enterprise:my-org:12345678 +``` diff --git a/docs/resources/enterprise_app_installation_repositories.md b/docs/resources/enterprise_app_installation_repositories.md new file mode 100644 index 0000000000..93916cc510 --- /dev/null +++ b/docs/resources/enterprise_app_installation_repositories.md @@ -0,0 +1,47 @@ +--- +page_title: "github_enterprise_app_installation_repositories (Resource) - GitHub" +description: |- + Manage the repositories an enterprise-owned organization's GitHub App installation can access. +--- + +# github_enterprise_app_installation_repositories (Resource) + +This resource manages the set of repositories accessible to a GitHub App installation +on an enterprise-owned organization. It only applies when the installation's +`repository_selection` is `selected`. + +Use [`github_enterprise_app_installation`](enterprise_app_installation.md) to control +installation lifecycle and `repository_selection`; use this resource to drift-detect +and reconcile the repository list. + +## Example Usage + +```terraform +resource "github_enterprise_app_installation_repositories" "example" { + enterprise_slug = "my-enterprise" + organization = "my-org" + installation_id = "12345678" + selected_repositories = ["repo-a", "repo-b"] +} +``` + +## Argument Reference + +- `enterprise_slug` - (Required, ForceNew) The slug of the enterprise that owns the organization. +- `organization` - (Required, ForceNew) The login of the enterprise-owned organization the app is installed on. +- `installation_id` - (Required, ForceNew) The ID of the GitHub App installation. +- `selected_repositories` - (Required) The set of repository names the installation should have access to. + +~> **Note**: Deleting this resource removes every repository currently selected for +the installation, which leaves the installation with no accessible repositories. +Either uninstall the app via the parent `github_enterprise_app_installation` resource +or switch the installation's `repository_selection` to `all` instead. + +## Import + +Enterprise App Installation Repositories can be imported using a composite ID of +`::`: + +```shell +terraform import github_enterprise_app_installation_repositories.example my-enterprise:my-org:12345678 +``` From 5f8d47de2bbd06f1413026131ab556fbf8e1ef42 Mon Sep 17 00:00:00 2001 From: zry98 Date: Thu, 30 Jul 2026 21:16:48 +0200 Subject: [PATCH 4/4] fixup! add resources and data sources --- ...pp_accessible_organization_repositories.go | 11 ++-- ...nterprise_app_installable_organizations.go | 9 +-- ...rce_github_enterprise_app_installations.go | 11 ++-- ...urce_github_enterprise_app_installation.go | 62 ++++++++++--------- ...nterprise_app_installation_repositories.go | 35 ++++++----- 5 files changed, 69 insertions(+), 59 deletions(-) diff --git a/github/data_source_github_enterprise_app_accessible_organization_repositories.go b/github/data_source_github_enterprise_app_accessible_organization_repositories.go index 253886fcf9..822ede7391 100644 --- a/github/data_source_github_enterprise_app_accessible_organization_repositories.go +++ b/github/data_source_github_enterprise_app_accessible_organization_repositories.go @@ -3,7 +3,7 @@ package github import ( "context" - "github.com/google/go-github/v88/github" + "github.com/google/go-github/v89/github" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -50,11 +50,12 @@ func dataSourceGithubEnterpriseAppAccessibleOrganizationRepositories() *schema.R } func dataSourceGithubEnterpriseAppAccessibleOrganizationRepositoriesRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - client := m.(*Owner).v3client - enterprise := d.Get("enterprise_slug").(string) - org := d.Get("organization").(string) + meta, _ := m.(*Owner) + client := meta.v3client + enterprise, _ := d.Get("enterprise_slug").(string) + org, _ := d.Get("organization").(string) - opts := &github.ListOptions{PerPage: maxPerPage} + opts := &github.ListOptions{PerPage: meta.maxPerPage} results := make([]map[string]any, 0) for { repos, resp, err := client.Enterprise.ListAppAccessibleOrganizationRepositories(ctx, enterprise, org, opts) diff --git a/github/data_source_github_enterprise_app_installable_organizations.go b/github/data_source_github_enterprise_app_installable_organizations.go index 8ea1fdd5bd..bee291ebb7 100644 --- a/github/data_source_github_enterprise_app_installable_organizations.go +++ b/github/data_source_github_enterprise_app_installable_organizations.go @@ -3,7 +3,7 @@ package github import ( "context" - "github.com/google/go-github/v88/github" + "github.com/google/go-github/v89/github" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -45,10 +45,11 @@ func dataSourceGithubEnterpriseAppInstallableOrganizations() *schema.Resource { } func dataSourceGithubEnterpriseAppInstallableOrganizationsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - client := m.(*Owner).v3client - enterprise := d.Get("enterprise_slug").(string) + meta, _ := m.(*Owner) + client := meta.v3client + enterprise, _ := d.Get("enterprise_slug").(string) - opts := &github.ListOptions{PerPage: maxPerPage} + opts := &github.ListOptions{PerPage: meta.maxPerPage} results := make([]map[string]any, 0) for { orgs, resp, err := client.Enterprise.ListAppInstallableOrganizations(ctx, enterprise, opts) diff --git a/github/data_source_github_enterprise_app_installations.go b/github/data_source_github_enterprise_app_installations.go index 3eb39f952e..8b7b2aa443 100644 --- a/github/data_source_github_enterprise_app_installations.go +++ b/github/data_source_github_enterprise_app_installations.go @@ -3,7 +3,7 @@ package github import ( "context" - "github.com/google/go-github/v88/github" + "github.com/google/go-github/v89/github" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -93,11 +93,12 @@ func dataSourceGithubEnterpriseAppInstallations() *schema.Resource { } func dataSourceGithubEnterpriseAppInstallationsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - client := m.(*Owner).v3client - enterprise := d.Get("enterprise_slug").(string) - org := d.Get("organization").(string) + meta, _ := m.(*Owner) + client := meta.v3client + enterprise, _ := d.Get("enterprise_slug").(string) + org, _ := d.Get("organization").(string) - opts := &github.ListOptions{PerPage: maxPerPage} + opts := &github.ListOptions{PerPage: meta.maxPerPage} results := make([]map[string]any, 0) for { installations, resp, err := client.Enterprise.ListAppInstallations(ctx, enterprise, org, opts) diff --git a/github/resource_github_enterprise_app_installation.go b/github/resource_github_enterprise_app_installation.go index 7c4d100cc3..e3b9c20919 100644 --- a/github/resource_github_enterprise_app_installation.go +++ b/github/resource_github_enterprise_app_installation.go @@ -8,7 +8,7 @@ import ( "net/http" "strconv" - "github.com/google/go-github/v88/github" + "github.com/google/go-github/v89/github" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) @@ -74,20 +74,23 @@ func resourceGithubEnterpriseAppInstallation() *schema.Resource { } } -func resourceGithubEnterpriseAppInstallationCreate(d *schema.ResourceData, meta any) error { - client := meta.(*Owner).v3client +func resourceGithubEnterpriseAppInstallationCreate(d *schema.ResourceData, m any) error { + meta, _ := m.(*Owner) + client := meta.v3client ctx := context.Background() - enterprise := d.Get("enterprise_slug").(string) - org := d.Get("organization").(string) - selection := d.Get("repository_selection").(string) + enterprise, _ := d.Get("enterprise_slug").(string) + org, _ := d.Get("organization").(string) + selection, _ := d.Get("repository_selection").(string) + clientID, _ := d.Get("client_id").(string) req := github.InstallAppRequest{ - ClientID: d.Get("client_id").(string), + ClientID: clientID, RepositorySelection: selection, } if selection == "selected" { - req.Repositories = expandStringList(d.Get("repositories").(*schema.Set).List()) + repositories, _ := d.Get("repositories").(*schema.Set) + req.Repositories = expandStringList(repositories.List()) } installation, _, err := client.Enterprise.InstallApp(ctx, enterprise, org, req) @@ -99,8 +102,8 @@ func resourceGithubEnterpriseAppInstallationCreate(d *schema.ResourceData, meta return resourceGithubEnterpriseAppInstallationRead(d, meta) } -func resourceGithubEnterpriseAppInstallationRead(d *schema.ResourceData, meta any) error { - client := meta.(*Owner).v3client +func resourceGithubEnterpriseAppInstallationRead(d *schema.ResourceData, m any) error { + meta, _ := m.(*Owner) ctx := context.WithValue(context.Background(), ctxId, d.Id()) enterprise, org, idStr, err := parseID3(d.Id()) @@ -112,7 +115,7 @@ func resourceGithubEnterpriseAppInstallationRead(d *schema.ResourceData, meta an return unconvertibleIdErr(idStr, err) } - installation, err := findEnterpriseAppInstallation(ctx, client, enterprise, org, installationID) + installation, err := findEnterpriseAppInstallation(ctx, meta, enterprise, org, installationID) if err != nil { var ghErr *github.ErrorResponse if errors.As(err, &ghErr) && ghErr.Response != nil && ghErr.Response.StatusCode == http.StatusNotFound { @@ -150,7 +153,7 @@ func resourceGithubEnterpriseAppInstallationRead(d *schema.ResourceData, meta an } if installation.GetRepositorySelection() == "selected" { - repos, err := listEnterpriseAppInstallationRepositories(ctx, client, enterprise, org, installationID) + repos, err := listEnterpriseAppInstallationRepositories(ctx, meta, enterprise, org, installationID) if err != nil { return err } @@ -170,8 +173,9 @@ func resourceGithubEnterpriseAppInstallationRead(d *schema.ResourceData, meta an return nil } -func resourceGithubEnterpriseAppInstallationUpdate(d *schema.ResourceData, meta any) error { - client := meta.(*Owner).v3client +func resourceGithubEnterpriseAppInstallationUpdate(d *schema.ResourceData, m any) error { + meta, _ := m.(*Owner) + client := meta.v3client ctx := context.WithValue(context.Background(), ctxId, d.Id()) enterprise, org, idStr, err := parseID3(d.Id()) @@ -183,22 +187,21 @@ func resourceGithubEnterpriseAppInstallationUpdate(d *schema.ResourceData, meta return unconvertibleIdErr(idStr, err) } - selection := d.Get("repository_selection").(string) + selection, _ := d.Get("repository_selection").(string) if d.HasChange("repository_selection") { opts := github.UpdateAppInstallationRepositoriesRequest{ RepositorySelection: &selection, } if selection == "selected" { - opts.Repositories = expandStringList(d.Get("repositories").(*schema.Set).List()) + repositories, _ := d.Get("repositories").(*schema.Set) + opts.Repositories = expandStringList(repositories.List()) } if _, _, err := client.Enterprise.UpdateAppInstallationRepositories(ctx, enterprise, org, installationID, opts); err != nil { return fmt.Errorf("error updating repository_selection for installation %d: %w", installationID, err) } } else if selection == "selected" && d.HasChange("repositories") { - oldVal, newVal := d.GetChange("repositories") - oldSet := oldVal.(*schema.Set) - newSet := newVal.(*schema.Set) + oldSet, newSet := setChanges(d.GetChange("repositories")) toAdd := expandStringList(newSet.Difference(oldSet).List()) toRemove := expandStringList(oldSet.Difference(newSet).List()) @@ -218,8 +221,9 @@ func resourceGithubEnterpriseAppInstallationUpdate(d *schema.ResourceData, meta return resourceGithubEnterpriseAppInstallationRead(d, meta) } -func resourceGithubEnterpriseAppInstallationDelete(d *schema.ResourceData, meta any) error { - client := meta.(*Owner).v3client +func resourceGithubEnterpriseAppInstallationDelete(d *schema.ResourceData, m any) error { + meta, _ := m.(*Owner) + client := meta.v3client ctx := context.WithValue(context.Background(), ctxId, d.Id()) enterprise, org, idStr, err := parseID3(d.Id()) @@ -235,11 +239,11 @@ func resourceGithubEnterpriseAppInstallationDelete(d *schema.ResourceData, meta return err } -func resourceGithubEnterpriseAppInstallationImport(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { +func resourceGithubEnterpriseAppInstallationImport(d *schema.ResourceData, m any) ([]*schema.ResourceData, error) { if _, _, _, err := parseID3(d.Id()); err != nil { return nil, fmt.Errorf("invalid ID specified: supplied ID must be written as ::") } - if err := resourceGithubEnterpriseAppInstallationRead(d, meta); err != nil { + if err := resourceGithubEnterpriseAppInstallationRead(d, m); err != nil { return nil, err } return []*schema.ResourceData{d}, nil @@ -248,10 +252,10 @@ func resourceGithubEnterpriseAppInstallationImport(d *schema.ResourceData, meta // findEnterpriseAppInstallation walks the enterprise app installations on an organization // to locate the installation matching the given ID. The REST API does not provide a direct // GET endpoint for a single enterprise-owned org installation, so a list-and-filter is used. -func findEnterpriseAppInstallation(ctx context.Context, client *github.Client, enterprise, org string, installationID int64) (*github.Installation, error) { - opts := &github.ListOptions{PerPage: maxPerPage} +func findEnterpriseAppInstallation(ctx context.Context, meta *Owner, enterprise, org string, installationID int64) (*github.Installation, error) { + opts := &github.ListOptions{PerPage: meta.maxPerPage} for { - installations, resp, err := client.Enterprise.ListAppInstallations(ctx, enterprise, org, opts) + installations, resp, err := meta.v3client.Enterprise.ListAppInstallations(ctx, enterprise, org, opts) if err != nil { return nil, err } @@ -267,11 +271,11 @@ func findEnterpriseAppInstallation(ctx context.Context, client *github.Client, e } } -func listEnterpriseAppInstallationRepositories(ctx context.Context, client *github.Client, enterprise, org string, installationID int64) ([]*github.AccessibleRepository, error) { +func listEnterpriseAppInstallationRepositories(ctx context.Context, meta *Owner, enterprise, org string, installationID int64) ([]*github.AccessibleRepository, error) { var all []*github.AccessibleRepository - opts := &github.ListOptions{PerPage: maxPerPage} + opts := &github.ListOptions{PerPage: meta.maxPerPage} for { - repos, resp, err := client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, enterprise, org, installationID, opts) + repos, resp, err := meta.v3client.Enterprise.ListRepositoriesForOrgAppInstallation(ctx, enterprise, org, installationID, opts) if err != nil { return nil, err } diff --git a/github/resource_github_enterprise_app_installation_repositories.go b/github/resource_github_enterprise_app_installation_repositories.go index 405cb0b643..a0bc625fef 100644 --- a/github/resource_github_enterprise_app_installation_repositories.go +++ b/github/resource_github_enterprise_app_installation_repositories.go @@ -8,7 +8,7 @@ import ( "net/http" "strconv" - "github.com/google/go-github/v88/github" + "github.com/google/go-github/v89/github" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -52,21 +52,23 @@ func resourceGithubEnterpriseAppInstallationRepositories() *schema.Resource { } } -func resourceGithubEnterpriseAppInstallationRepositoriesCreateOrUpdate(d *schema.ResourceData, meta any) error { - client := meta.(*Owner).v3client +func resourceGithubEnterpriseAppInstallationRepositoriesCreateOrUpdate(d *schema.ResourceData, m any) error { + meta, _ := m.(*Owner) + client := meta.v3client ctx := context.Background() - enterprise := d.Get("enterprise_slug").(string) - org := d.Get("organization").(string) - installationIDString := d.Get("installation_id").(string) + enterprise, _ := d.Get("enterprise_slug").(string) + org, _ := d.Get("organization").(string) + installationIDString, _ := d.Get("installation_id").(string) installationID, err := strconv.ParseInt(installationIDString, 10, 64) if err != nil { return unconvertibleIdErr(installationIDString, err) } - desired := stringSetFromAny(d.Get("selected_repositories").(*schema.Set).List()) + selectedRepositories, _ := d.Get("selected_repositories").(*schema.Set) + desired := stringSetFromAny(selectedRepositories.List()) - current, err := listEnterpriseAppInstallationRepositories(ctx, client, enterprise, org, installationID) + current, err := listEnterpriseAppInstallationRepositories(ctx, meta, enterprise, org, installationID) if err != nil { return err } @@ -104,8 +106,8 @@ func resourceGithubEnterpriseAppInstallationRepositoriesCreateOrUpdate(d *schema return resourceGithubEnterpriseAppInstallationRepositoriesRead(d, meta) } -func resourceGithubEnterpriseAppInstallationRepositoriesRead(d *schema.ResourceData, meta any) error { - client := meta.(*Owner).v3client +func resourceGithubEnterpriseAppInstallationRepositoriesRead(d *schema.ResourceData, m any) error { + meta, _ := m.(*Owner) ctx := context.WithValue(context.Background(), ctxId, d.Id()) enterprise, org, installationIDString, err := parseID3(d.Id()) @@ -117,7 +119,7 @@ func resourceGithubEnterpriseAppInstallationRepositoriesRead(d *schema.ResourceD return unconvertibleIdErr(installationIDString, err) } - repos, err := listEnterpriseAppInstallationRepositories(ctx, client, enterprise, org, installationID) + repos, err := listEnterpriseAppInstallationRepositories(ctx, meta, enterprise, org, installationID) if err != nil { var ghErr *github.ErrorResponse if errors.As(err, &ghErr) && ghErr.Response != nil && ghErr.Response.StatusCode == http.StatusNotFound { @@ -148,8 +150,9 @@ func resourceGithubEnterpriseAppInstallationRepositoriesRead(d *schema.ResourceD return nil } -func resourceGithubEnterpriseAppInstallationRepositoriesDelete(d *schema.ResourceData, meta any) error { - client := meta.(*Owner).v3client +func resourceGithubEnterpriseAppInstallationRepositoriesDelete(d *schema.ResourceData, m any) error { + meta, _ := m.(*Owner) + client := meta.v3client ctx := context.WithValue(context.Background(), ctxId, d.Id()) enterprise, org, installationIDString, err := parseID3(d.Id()) @@ -161,7 +164,7 @@ func resourceGithubEnterpriseAppInstallationRepositoriesDelete(d *schema.Resourc return unconvertibleIdErr(installationIDString, err) } - current, err := listEnterpriseAppInstallationRepositories(ctx, client, enterprise, org, installationID) + current, err := listEnterpriseAppInstallationRepositories(ctx, meta, enterprise, org, installationID) if err != nil { return err } @@ -178,11 +181,11 @@ func resourceGithubEnterpriseAppInstallationRepositoriesDelete(d *schema.Resourc return err } -func resourceGithubEnterpriseAppInstallationRepositoriesImport(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { +func resourceGithubEnterpriseAppInstallationRepositoriesImport(d *schema.ResourceData, m any) ([]*schema.ResourceData, error) { if _, _, _, err := parseID3(d.Id()); err != nil { return nil, fmt.Errorf("invalid ID specified: supplied ID must be written as ::") } - if err := resourceGithubEnterpriseAppInstallationRepositoriesRead(d, meta); err != nil { + if err := resourceGithubEnterpriseAppInstallationRepositoriesRead(d, m); err != nil { return nil, err } return []*schema.ResourceData{d}, nil