Skip to content

Commit 2faba29

Browse files
committed
Add github_organization_ip_allow_list_entry resource
Adds a managed resource that mirrors github_enterprise_ip_allow_list_entry but is scoped to the organization configured on the provider. Uses the createIpAllowListEntry / updateIpAllowListEntry / deleteIpAllowListEntry GraphQL mutations with the organization node ID as the owner. Includes a tfplugindocs template at templates/resources/ and the generated docs file under docs/resources/, plus an example_1.tf alongside. Closes #1067
1 parent c3cc5fa commit 2faba29

7 files changed

Lines changed: 478 additions & 0 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
page_title: "github_organization_ip_allow_list_entry (Resource) - GitHub"
3+
description: |-
4+
Creates and manages IP allow list entries within a GitHub Organization
5+
---
6+
7+
# github_organization_ip_allow_list_entry (Resource)
8+
9+
This resource allows you to create and manage IP allow list entries for a GitHub Organization. IP allow list entries define IP addresses or ranges that are permitted to access private and internal resources owned by the organization.
10+
11+
The organization is taken from the `owner` configured on the provider. The organization must be on a GitHub Enterprise Cloud plan, and the IP allow list itself must be enabled in the organization's security settings before entries can be created.
12+
13+
## Example Usage
14+
15+
```terraform
16+
resource "github_organization_ip_allow_list_entry" "test" {
17+
ip = "192.168.1.0/20"
18+
name = "My IP Range Name"
19+
is_active = true
20+
}
21+
```
22+
23+
## Argument Reference
24+
25+
The following arguments are supported:
26+
27+
- `ip` - (Required) An IP address or range of IP addresses in CIDR notation.
28+
- `name` - (Optional) A descriptive name for the IP allow list entry.
29+
- `is_active` - (Optional) Whether the entry is currently active. Default: true.
30+
31+
## Import
32+
33+
This resource can be imported using the ID of the IP allow list entry:
34+
35+
```shell
36+
terraform import github_organization_ip_allow_list_entry.test IALE_kwHOC1234567890a
37+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
resource "github_organization_ip_allow_list_entry" "test" {
2+
ip = "192.168.1.0/20"
3+
name = "My IP Range Name"
4+
is_active = true
5+
}

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ func NewProvider() func() *schema.Provider {
213213
"github_enterprise_organization": resourceGithubEnterpriseOrganization(),
214214
"github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(),
215215
"github_enterprise_ip_allow_list_entry": resourceGithubEnterpriseIpAllowListEntry(),
216+
"github_organization_ip_allow_list_entry": resourceGithubOrganizationIpAllowListEntry(),
216217
"github_enterprise_actions_workflow_permissions": resourceGithubEnterpriseActionsWorkflowPermissions(),
217218
"github_actions_organization_workflow_permissions": resourceGithubActionsOrganizationWorkflowPermissions(),
218219
"github_enterprise_security_analysis_settings": resourceGithubEnterpriseSecurityAnalysisSettings(),
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"strings"
6+
7+
"github.com/hashicorp/terraform-plugin-log/tflog"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/shurcooL/githubv4"
11+
)
12+
13+
func resourceGithubOrganizationIpAllowListEntry() *schema.Resource {
14+
return &schema.Resource{
15+
Description: "Manage a GitHub Organization IP Allow List Entry.",
16+
CreateContext: resourceGithubOrganizationIpAllowListEntryCreate,
17+
ReadContext: resourceGithubOrganizationIpAllowListEntryRead,
18+
UpdateContext: resourceGithubOrganizationIpAllowListEntryUpdate,
19+
DeleteContext: resourceGithubOrganizationIpAllowListEntryDelete,
20+
Importer: &schema.ResourceImporter{
21+
StateContext: resourceGithubOrganizationIpAllowListEntryImport,
22+
},
23+
24+
Schema: map[string]*schema.Schema{
25+
"ip": {
26+
Type: schema.TypeString,
27+
Required: true,
28+
ForceNew: true,
29+
Description: "An IP address or range of IP addresses in CIDR notation.",
30+
},
31+
"name": {
32+
Type: schema.TypeString,
33+
Optional: true,
34+
Description: "An optional name for the IP allow list entry.",
35+
},
36+
"is_active": {
37+
Type: schema.TypeBool,
38+
Optional: true,
39+
Default: true,
40+
Description: "Whether the entry is currently active.",
41+
},
42+
"created_at": {
43+
Type: schema.TypeString,
44+
Computed: true,
45+
Description: "Timestamp of when the entry was created.",
46+
},
47+
"updated_at": {
48+
Type: schema.TypeString,
49+
Computed: true,
50+
Description: "Timestamp of when the entry was last updated.",
51+
},
52+
},
53+
}
54+
}
55+
56+
func resourceGithubOrganizationIpAllowListEntryCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
57+
if err := checkOrganization(meta); err != nil {
58+
return diag.FromErr(err)
59+
}
60+
61+
client := meta.(*Owner).v4client
62+
orgName := meta.(*Owner).name
63+
64+
organizationID, err := getOrganizationID(ctx, client, orgName)
65+
if err != nil {
66+
return diag.FromErr(err)
67+
}
68+
69+
var mutation struct {
70+
CreateIpAllowListEntry struct {
71+
IpAllowListEntry struct {
72+
ID githubv4.String
73+
AllowListValue githubv4.String
74+
Name githubv4.String
75+
IsActive githubv4.Boolean
76+
CreatedAt githubv4.String
77+
UpdatedAt githubv4.String
78+
}
79+
} `graphql:"createIpAllowListEntry(input: $input)"`
80+
}
81+
82+
name := d.Get("name").(string)
83+
input := githubv4.CreateIpAllowListEntryInput{
84+
OwnerID: githubv4.ID(organizationID),
85+
AllowListValue: githubv4.String(d.Get("ip").(string)),
86+
IsActive: githubv4.Boolean(d.Get("is_active").(bool)),
87+
}
88+
89+
if name != "" {
90+
v := githubv4.String(name)
91+
input.Name = &v
92+
}
93+
94+
err = client.Mutate(ctx, &mutation, input, nil)
95+
if err != nil {
96+
return diag.FromErr(err)
97+
}
98+
99+
d.SetId(string(mutation.CreateIpAllowListEntry.IpAllowListEntry.ID))
100+
101+
if err := d.Set("created_at", mutation.CreateIpAllowListEntry.IpAllowListEntry.CreatedAt); err != nil {
102+
return diag.FromErr(err)
103+
}
104+
if err := d.Set("updated_at", mutation.CreateIpAllowListEntry.IpAllowListEntry.UpdatedAt); err != nil {
105+
return diag.FromErr(err)
106+
}
107+
108+
return nil
109+
}
110+
111+
func resourceGithubOrganizationIpAllowListEntryRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
112+
if err := checkOrganization(meta); err != nil {
113+
return diag.FromErr(err)
114+
}
115+
116+
client := meta.(*Owner).v4client
117+
118+
var query struct {
119+
Node struct {
120+
IpAllowListEntry struct {
121+
ID githubv4.String
122+
AllowListValue githubv4.String
123+
Name githubv4.String
124+
IsActive githubv4.Boolean
125+
CreatedAt githubv4.String
126+
UpdatedAt githubv4.String
127+
Owner struct {
128+
Organization struct {
129+
Login githubv4.String
130+
} `graphql:"... on Organization"`
131+
}
132+
} `graphql:"... on IpAllowListEntry"`
133+
} `graphql:"node(id: $id)"`
134+
}
135+
136+
variables := map[string]any{
137+
"id": githubv4.ID(d.Id()),
138+
}
139+
140+
err := client.Query(ctx, &query, variables)
141+
if err != nil {
142+
if strings.Contains(err.Error(), "Could not resolve to a node with the global id") {
143+
tflog.Info(ctx, "Removing IP allow list entry from state because it no longer exists in GitHub", map[string]any{
144+
"id": d.Id(),
145+
})
146+
d.SetId("")
147+
return nil
148+
}
149+
return diag.FromErr(err)
150+
}
151+
152+
entry := query.Node.IpAllowListEntry
153+
if err := d.Set("name", entry.Name); err != nil {
154+
return diag.FromErr(err)
155+
}
156+
if err := d.Set("ip", entry.AllowListValue); err != nil {
157+
return diag.FromErr(err)
158+
}
159+
if err := d.Set("is_active", entry.IsActive); err != nil {
160+
return diag.FromErr(err)
161+
}
162+
if err := d.Set("created_at", entry.CreatedAt); err != nil {
163+
return diag.FromErr(err)
164+
}
165+
if err := d.Set("updated_at", entry.UpdatedAt); err != nil {
166+
return diag.FromErr(err)
167+
}
168+
169+
return nil
170+
}
171+
172+
func resourceGithubOrganizationIpAllowListEntryUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
173+
if err := checkOrganization(meta); err != nil {
174+
return diag.FromErr(err)
175+
}
176+
177+
client := meta.(*Owner).v4client
178+
179+
var mutation struct {
180+
UpdateIpAllowListEntry struct {
181+
IpAllowListEntry struct {
182+
ID githubv4.String
183+
AllowListValue githubv4.String
184+
Name githubv4.String
185+
IsActive githubv4.Boolean
186+
UpdatedAt githubv4.String
187+
}
188+
} `graphql:"updateIpAllowListEntry(input: $input)"`
189+
}
190+
191+
name := d.Get("name").(string)
192+
input := githubv4.UpdateIpAllowListEntryInput{
193+
IPAllowListEntryID: githubv4.ID(d.Id()),
194+
AllowListValue: githubv4.String(d.Get("ip").(string)),
195+
IsActive: githubv4.Boolean(d.Get("is_active").(bool)),
196+
}
197+
198+
if name != "" {
199+
v := githubv4.String(name)
200+
input.Name = &v
201+
}
202+
203+
err := client.Mutate(ctx, &mutation, input, nil)
204+
if err != nil {
205+
return diag.FromErr(err)
206+
}
207+
208+
if err := d.Set("updated_at", mutation.UpdateIpAllowListEntry.IpAllowListEntry.UpdatedAt); err != nil {
209+
return diag.FromErr(err)
210+
}
211+
212+
return nil
213+
}
214+
215+
func resourceGithubOrganizationIpAllowListEntryDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
216+
if err := checkOrganization(meta); err != nil {
217+
return diag.FromErr(err)
218+
}
219+
220+
client := meta.(*Owner).v4client
221+
222+
var mutation struct {
223+
DeleteIpAllowListEntry struct {
224+
ClientMutationID githubv4.String
225+
} `graphql:"deleteIpAllowListEntry(input: $input)"`
226+
}
227+
228+
input := githubv4.DeleteIpAllowListEntryInput{
229+
IPAllowListEntryID: githubv4.ID(d.Id()),
230+
}
231+
232+
err := client.Mutate(ctx, &mutation, input, nil)
233+
// GraphQL will return a 200 OK if it couldn't find the global ID
234+
if err != nil && !strings.Contains(err.Error(), "Could not resolve to a node with the global id") {
235+
return diag.FromErr(err)
236+
}
237+
238+
return nil
239+
}
240+
241+
func resourceGithubOrganizationIpAllowListEntryImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
242+
if err := checkOrganization(meta); err != nil {
243+
return nil, err
244+
}
245+
246+
client := meta.(*Owner).v4client
247+
248+
var query struct {
249+
Node struct {
250+
IpAllowListEntry struct {
251+
ID githubv4.String
252+
AllowListValue githubv4.String
253+
Name githubv4.String
254+
IsActive githubv4.Boolean
255+
CreatedAt githubv4.String
256+
UpdatedAt githubv4.String
257+
Owner struct {
258+
Organization struct {
259+
Login githubv4.String
260+
} `graphql:"... on Organization"`
261+
}
262+
} `graphql:"... on IpAllowListEntry"`
263+
} `graphql:"node(id: $id)"`
264+
}
265+
266+
variables := map[string]any{
267+
"id": githubv4.ID(d.Id()),
268+
}
269+
270+
err := client.Query(ctx, &query, variables)
271+
if err != nil {
272+
return nil, err
273+
}
274+
275+
entry := query.Node.IpAllowListEntry
276+
277+
if err := d.Set("ip", string(entry.AllowListValue)); err != nil {
278+
return nil, err
279+
}
280+
if err := d.Set("name", entry.Name); err != nil {
281+
return nil, err
282+
}
283+
if err := d.Set("is_active", entry.IsActive); err != nil {
284+
return nil, err
285+
}
286+
if err := d.Set("created_at", entry.CreatedAt); err != nil {
287+
return nil, err
288+
}
289+
if err := d.Set("updated_at", entry.UpdatedAt); err != nil {
290+
return nil, err
291+
}
292+
293+
return []*schema.ResourceData{d}, nil
294+
}

0 commit comments

Comments
 (0)