Skip to content

Commit 8646f3c

Browse files
feat: Add data source for listing GitHub App installations in an organization (#2573)
1 parent 46682cf commit 8646f3c

File tree

5 files changed

+359
-0
lines changed

5 files changed

+359
-0
lines changed

github/acc_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,24 @@ func skipUnlessEnterprise(t *testing.T) {
300300
}
301301
}
302302

303+
func skipUnlessHasAppInstallations(t *testing.T) {
304+
t.Helper()
305+
306+
meta, err := getTestMeta()
307+
if err != nil {
308+
t.Fatalf("failed to get test meta: %s", err)
309+
}
310+
311+
installations, _, err := meta.v3client.Organizations.ListInstallations(context.Background(), meta.name, nil)
312+
if err != nil {
313+
t.Fatalf("failed to list app installations: %s", err)
314+
}
315+
316+
if len(installations.Installations) == 0 {
317+
t.Skip("Skipping because no GitHub App installations found in the test organization")
318+
}
319+
}
320+
303321
func skipUnlessMode(t *testing.T, testModes ...testMode) {
304322
if !slices.Contains(testModes, testAccConf.authMode) {
305323
t.Skip("Skipping as not supported test mode")
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
package github
2+
3+
import (
4+
"context"
5+
6+
"github.com/google/go-github/v82/github"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
)
10+
11+
func dataSourceGithubOrganizationAppInstallations() *schema.Resource {
12+
return &schema.Resource{
13+
ReadContext: dataSourceGithubOrganizationAppInstallationsRead,
14+
Description: "Use this data source to retrieve all GitHub App installations of the organization.",
15+
16+
Schema: map[string]*schema.Schema{
17+
"installations": {
18+
Type: schema.TypeList,
19+
Computed: true,
20+
Description: "List of GitHub App installations in the organization.",
21+
Elem: &schema.Resource{
22+
Schema: map[string]*schema.Schema{
23+
"id": {
24+
Type: schema.TypeInt,
25+
Computed: true,
26+
Description: "The ID of the GitHub App installation.",
27+
},
28+
"app_slug": {
29+
Type: schema.TypeString,
30+
Computed: true,
31+
Description: "The URL-friendly name of the GitHub App.",
32+
},
33+
"app_id": {
34+
Type: schema.TypeInt,
35+
Computed: true,
36+
Description: "The ID of the GitHub App.",
37+
},
38+
"repository_selection": {
39+
Type: schema.TypeString,
40+
Computed: true,
41+
Description: "Whether the installation has access to all repositories or only selected ones. Possible values are 'all' or 'selected'.",
42+
},
43+
"permissions": {
44+
Type: schema.TypeMap,
45+
Computed: true,
46+
Elem: &schema.Schema{Type: schema.TypeString},
47+
Description: "The permissions granted to the GitHub App installation.",
48+
},
49+
"events": {
50+
Type: schema.TypeList,
51+
Computed: true,
52+
Elem: &schema.Schema{Type: schema.TypeString},
53+
Description: "The list of events the GitHub App installation subscribes to.",
54+
},
55+
"client_id": {
56+
Type: schema.TypeString,
57+
Computed: true,
58+
Description: "The OAuth client ID of the GitHub App.",
59+
},
60+
"target_id": {
61+
Type: schema.TypeInt,
62+
Computed: true,
63+
Description: "The ID of the account the GitHub App is installed on.",
64+
},
65+
"target_type": {
66+
Type: schema.TypeString,
67+
Computed: true,
68+
Description: "The type of account the GitHub App is installed on. Possible values are 'Organization' or 'User'.",
69+
},
70+
"suspended": {
71+
Type: schema.TypeBool,
72+
Computed: true,
73+
Description: "Whether the GitHub App installation is currently suspended.",
74+
},
75+
"single_file_paths": {
76+
Type: schema.TypeList,
77+
Computed: true,
78+
Elem: &schema.Schema{Type: schema.TypeString},
79+
Description: "The list of single file paths the GitHub App installation has access to.",
80+
},
81+
"created_at": {
82+
Type: schema.TypeString,
83+
Computed: true,
84+
Description: "The date the GitHub App installation was created.",
85+
},
86+
"updated_at": {
87+
Type: schema.TypeString,
88+
Computed: true,
89+
Description: "The date the GitHub App installation was last updated.",
90+
},
91+
},
92+
},
93+
},
94+
},
95+
}
96+
}
97+
98+
func dataSourceGithubOrganizationAppInstallationsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
99+
meta := m.(*Owner)
100+
owner := meta.name
101+
client := meta.v3client
102+
103+
options := &github.ListOptions{
104+
PerPage: maxPerPage,
105+
}
106+
107+
results := make([]map[string]any, 0)
108+
for {
109+
appInstallations, resp, err := client.Organizations.ListInstallations(ctx, owner, options)
110+
if err != nil {
111+
return diag.FromErr(err)
112+
}
113+
114+
results = append(results, flattenGitHubAppInstallations(appInstallations.Installations)...)
115+
if resp.NextPage == 0 {
116+
break
117+
}
118+
119+
options.Page = resp.NextPage
120+
}
121+
122+
d.SetId(owner)
123+
if err := d.Set("installations", results); err != nil {
124+
return diag.FromErr(err)
125+
}
126+
127+
return nil
128+
}
129+
130+
func flattenGitHubAppInstallations(orgAppInstallations []*github.Installation) []map[string]any {
131+
results := make([]map[string]any, 0)
132+
133+
if orgAppInstallations == nil {
134+
return results
135+
}
136+
137+
for _, appInstallation := range orgAppInstallations {
138+
result := make(map[string]any)
139+
140+
result["id"] = appInstallation.GetID()
141+
result["app_slug"] = appInstallation.GetAppSlug()
142+
result["app_id"] = appInstallation.GetAppID()
143+
result["repository_selection"] = appInstallation.GetRepositorySelection()
144+
result["client_id"] = appInstallation.GetClientID()
145+
result["target_id"] = appInstallation.GetTargetID()
146+
result["target_type"] = appInstallation.GetTargetType()
147+
result["suspended"] = !appInstallation.GetSuspendedAt().IsZero()
148+
if appInstallation.Events != nil {
149+
result["events"] = appInstallation.Events
150+
} else {
151+
result["events"] = []string{}
152+
}
153+
154+
result["permissions"] = flattenInstallationPermissions(appInstallation.Permissions)
155+
156+
if appInstallation.SingleFilePaths != nil {
157+
result["single_file_paths"] = appInstallation.SingleFilePaths
158+
} else {
159+
result["single_file_paths"] = []string{}
160+
}
161+
162+
result["created_at"] = appInstallation.GetCreatedAt().Format("2006-01-02T15:04:05Z")
163+
result["updated_at"] = appInstallation.GetUpdatedAt().Format("2006-01-02T15:04:05Z")
164+
165+
results = append(results, result)
166+
}
167+
168+
return results
169+
}
170+
171+
func flattenInstallationPermissions(perms *github.InstallationPermissions) map[string]string {
172+
permissions := make(map[string]string)
173+
if perms == nil {
174+
return permissions
175+
}
176+
177+
if v := perms.GetActions(); v != "" {
178+
permissions["actions"] = v
179+
}
180+
if v := perms.GetAdministration(); v != "" {
181+
permissions["administration"] = v
182+
}
183+
if v := perms.GetChecks(); v != "" {
184+
permissions["checks"] = v
185+
}
186+
if v := perms.GetContents(); v != "" {
187+
permissions["contents"] = v
188+
}
189+
if v := perms.GetDeployments(); v != "" {
190+
permissions["deployments"] = v
191+
}
192+
if v := perms.GetEnvironments(); v != "" {
193+
permissions["environments"] = v
194+
}
195+
if v := perms.GetIssues(); v != "" {
196+
permissions["issues"] = v
197+
}
198+
if v := perms.GetMetadata(); v != "" {
199+
permissions["metadata"] = v
200+
}
201+
if v := perms.GetMembers(); v != "" {
202+
permissions["members"] = v
203+
}
204+
if v := perms.GetOrganizationAdministration(); v != "" {
205+
permissions["organization_administration"] = v
206+
}
207+
if v := perms.GetOrganizationHooks(); v != "" {
208+
permissions["organization_hooks"] = v
209+
}
210+
if v := perms.GetOrganizationPlan(); v != "" {
211+
permissions["organization_plan"] = v
212+
}
213+
if v := perms.GetOrganizationProjects(); v != "" {
214+
permissions["organization_projects"] = v
215+
}
216+
if v := perms.GetOrganizationSecrets(); v != "" {
217+
permissions["organization_secrets"] = v
218+
}
219+
if v := perms.GetOrganizationSelfHostedRunners(); v != "" {
220+
permissions["organization_self_hosted_runners"] = v
221+
}
222+
if v := perms.GetOrganizationUserBlocking(); v != "" {
223+
permissions["organization_user_blocking"] = v
224+
}
225+
if v := perms.GetPackages(); v != "" {
226+
permissions["packages"] = v
227+
}
228+
if v := perms.GetPages(); v != "" {
229+
permissions["pages"] = v
230+
}
231+
if v := perms.GetPullRequests(); v != "" {
232+
permissions["pull_requests"] = v
233+
}
234+
if v := perms.GetRepositoryHooks(); v != "" {
235+
permissions["repository_hooks"] = v
236+
}
237+
if v := perms.GetRepositoryProjects(); v != "" {
238+
permissions["repository_projects"] = v
239+
}
240+
if v := perms.GetRepositoryPreReceiveHooks(); v != "" {
241+
permissions["repository_pre_receive_hooks"] = v
242+
}
243+
if v := perms.GetSecrets(); v != "" {
244+
permissions["secrets"] = v
245+
}
246+
if v := perms.GetSecretScanningAlerts(); v != "" {
247+
permissions["secret_scanning_alerts"] = v
248+
}
249+
if v := perms.GetSecurityEvents(); v != "" {
250+
permissions["security_events"] = v
251+
}
252+
if v := perms.GetSingleFile(); v != "" {
253+
permissions["single_file"] = v
254+
}
255+
if v := perms.GetStatuses(); v != "" {
256+
permissions["statuses"] = v
257+
}
258+
if v := perms.GetTeamDiscussions(); v != "" {
259+
permissions["team_discussions"] = v
260+
}
261+
if v := perms.GetVulnerabilityAlerts(); v != "" {
262+
permissions["vulnerability_alerts"] = v
263+
}
264+
if v := perms.GetWorkflows(); v != "" {
265+
permissions["workflows"] = v
266+
}
267+
268+
return permissions
269+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package github
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
7+
)
8+
9+
func TestAccGithubOrganizationAppInstallations(t *testing.T) {
10+
t.Run("basic", func(t *testing.T) {
11+
config := `data "github_organization_app_installations" "test" {}`
12+
13+
resource.Test(t, resource.TestCase{
14+
PreCheck: func() {
15+
skipUnlessHasOrgs(t)
16+
skipUnlessHasAppInstallations(t)
17+
},
18+
ProviderFactories: providerFactories,
19+
Steps: []resource.TestStep{
20+
{
21+
Config: config,
22+
Check: resource.ComposeAggregateTestCheckFunc(
23+
resource.TestCheckResourceAttrSet("data.github_organization_app_installations.test", "installations.0.id"),
24+
resource.TestCheckResourceAttrSet("data.github_organization_app_installations.test", "installations.0.app_slug"),
25+
resource.TestCheckResourceAttrSet("data.github_organization_app_installations.test", "installations.0.app_id"),
26+
),
27+
},
28+
},
29+
})
30+
})
31+
}

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ func Provider() *schema.Provider {
268268
"github_organization_team_sync_groups": dataSourceGithubOrganizationTeamSyncGroups(),
269269
"github_organization_teams": dataSourceGithubOrganizationTeams(),
270270
"github_organization_webhooks": dataSourceGithubOrganizationWebhooks(),
271+
"github_organization_app_installations": dataSourceGithubOrganizationAppInstallations(),
271272
"github_ref": dataSourceGithubRef(),
272273
"github_release": dataSourceGithubRelease(),
273274
"github_release_asset": dataSourceGithubReleaseAsset(),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
layout: "github"
3+
page_title: "GitHub: github_organization_app_installations"
4+
description: |-
5+
Get information on all GitHub App installations of the organization.
6+
---
7+
8+
# github\_organization\_app_installations
9+
10+
Use this data source to retrieve all GitHub App installations of the organization.
11+
12+
## Example Usage
13+
14+
To retrieve *all* GitHub App installations of the organization:
15+
16+
```hcl
17+
data "github_organization_app_installations" "all" {}
18+
```
19+
20+
## Attributes Reference
21+
22+
* `installations` - List of GitHub App installations in the organization. Each `installation` block consists of the fields documented below.
23+
24+
---
25+
26+
The `installation` block consists of:
27+
28+
* `id` - The ID of the GitHub App installation.
29+
* `app_slug` - The URL-friendly name of the GitHub App.
30+
* `app_id` - The ID of the GitHub App.
31+
* `repository_selection` - Whether the installation has access to all repositories or only selected ones. Possible values are `all` or `selected`.
32+
* `permissions` - A map of the permissions granted to the GitHub App installation.
33+
* `events` - The list of events the GitHub App installation subscribes to.
34+
* `client_id` - The OAuth client ID of the GitHub App.
35+
* `target_id` - The ID of the account the GitHub App is installed on.
36+
* `target_type` - The type of account the GitHub App is installed on. Possible values are `Organization` or `User`.
37+
* `suspended` - Whether the GitHub App installation is currently suspended.
38+
* `single_file_paths` - The list of single file paths the GitHub App installation has access to.
39+
* `created_at` - The date the GitHub App installation was created.
40+
* `updated_at` - The date the GitHub App installation was last updated.

0 commit comments

Comments
 (0)