-
Notifications
You must be signed in to change notification settings - Fork 923
feat: Adding github_enterprise_ip_allow_list_entry resource #2649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
943df18
eb02154
935e689
83a0055
c3735ac
e1b06a2
833f3f1
60c686b
f667dcc
06b2e43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| package github | ||
|
|
||
| import ( | ||
| "context" | ||
| "log" | ||
| "strings" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
| "github.com/shurcooL/githubv4" | ||
| ) | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntry() *schema.Resource { | ||
| return &schema.Resource{ | ||
| Create: resourceGithubEnterpriseIpAllowListEntryCreate, | ||
| Read: resourceGithubEnterpriseIpAllowListEntryRead, | ||
| Update: resourceGithubEnterpriseIpAllowListEntryUpdate, | ||
| Delete: resourceGithubEnterpriseIpAllowListEntryDelete, | ||
| Importer: &schema.ResourceImporter{ | ||
| StateContext: schema.ImportStatePassthroughContext, | ||
| }, | ||
|
|
||
| Schema: map[string]*schema.Schema{ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a top-level |
||
| "enterprise_slug": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| Description: "The slug of the enterprise to apply the IP allow list entry to.", | ||
| }, | ||
| "ip": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| Description: "An IP address or range of IP addresses in CIDR notation.", | ||
| }, | ||
| "name": { | ||
| Type: schema.TypeString, | ||
| Optional: true, | ||
| Description: "An optional name for the IP allow list entry.", | ||
| }, | ||
| "is_active": { | ||
| Type: schema.TypeBool, | ||
| Optional: true, | ||
| Default: true, | ||
| Description: "Whether the entry is currently active.", | ||
| }, | ||
ErikElkins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| } | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryCreate(d *schema.ResourceData, meta interface{}) error { | ||
| client := meta.(*Owner).v4client | ||
| ctx := context.WithValue(context.Background(), ctxId, d.Id()) | ||
ErikElkins marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // First, get the enterprise ID as we need it for the mutation | ||
| enterpriseSlug := d.Get("enterprise_slug").(string) | ||
| enterpriseID, err := getEnterpriseID(ctx, client, enterpriseSlug) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Then create the IP allow list entry | ||
| var mutation struct { | ||
| CreateIpAllowListEntry struct { | ||
| IpAllowListEntry struct { | ||
| ID githubv4.String | ||
| AllowListValue githubv4.String | ||
| Name githubv4.String | ||
| IsActive githubv4.Boolean | ||
| CreatedAt githubv4.String | ||
| UpdatedAt githubv4.String | ||
| } | ||
| } `graphql:"createIpAllowListEntry(input: $input)"` | ||
| } | ||
|
|
||
| name := d.Get("name").(string) | ||
| input := githubv4.CreateIpAllowListEntryInput{ | ||
| OwnerID: githubv4.ID(enterpriseID), | ||
| AllowListValue: githubv4.String(d.Get("ip").(string)), | ||
| IsActive: githubv4.Boolean(d.Get("is_active").(bool)), | ||
| } | ||
|
|
||
| if name != "" { | ||
| input.Name = githubv4.NewString(githubv4.String(name)) | ||
| } | ||
|
|
||
| err = client.Mutate(ctx, &mutation, input, nil) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| d.SetId(string(mutation.CreateIpAllowListEntry.IpAllowListEntry.ID)) | ||
|
|
||
| return resourceGithubEnterpriseIpAllowListEntryRead(d, meta) | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryRead(d *schema.ResourceData, meta interface{}) error { | ||
| client := meta.(*Owner).v4client | ||
| ctx := context.WithValue(context.Background(), ctxId, d.Id()) | ||
|
|
||
| var query struct { | ||
| Node struct { | ||
| IpAllowListEntry struct { | ||
| ID githubv4.String | ||
| AllowListValue githubv4.String | ||
| Name githubv4.String | ||
| IsActive githubv4.Boolean | ||
| CreatedAt githubv4.String | ||
| UpdatedAt githubv4.String | ||
| Owner struct { | ||
| Enterprise struct { | ||
| Slug githubv4.String | ||
| } `graphql:"... on Enterprise"` | ||
| } | ||
| } `graphql:"... on IpAllowListEntry"` | ||
| } `graphql:"node(id: $id)"` | ||
| } | ||
|
|
||
| variables := map[string]interface{}{ | ||
| "id": githubv4.ID(d.Id()), | ||
| } | ||
|
|
||
| err := client.Query(ctx, &query, variables) | ||
| if err != nil { | ||
| if strings.Contains(err.Error(), "Could not resolve to a node with the global id") { | ||
| log.Printf("[INFO] Removing IP allow list entry (%s) from state because it no longer exists in GitHub", d.Id()) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
| d.SetId("") | ||
| return nil | ||
| } | ||
| return err | ||
| } | ||
|
|
||
| entry := query.Node.IpAllowListEntry | ||
|
|
||
| d.Set("ip", entry.AllowListValue) | ||
| d.Set("name", entry.Name) | ||
| d.Set("is_active", entry.IsActive) | ||
| d.Set("created_at", entry.CreatedAt) | ||
| d.Set("updated_at", entry.UpdatedAt) | ||
| d.Set("enterprise_slug", entry.Owner.Enterprise.Slug) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryUpdate(d *schema.ResourceData, meta interface{}) error { | ||
| client := meta.(*Owner).v4client | ||
| ctx := context.WithValue(context.Background(), ctxId, d.Id()) | ||
|
|
||
| var mutation struct { | ||
| UpdateIpAllowListEntry struct { | ||
| IpAllowListEntry struct { | ||
| ID githubv4.String | ||
| AllowListValue githubv4.String | ||
| Name githubv4.String | ||
| IsActive githubv4.Boolean | ||
| UpdatedAt githubv4.String | ||
| } | ||
| } `graphql:"updateIpAllowListEntry(input: $input)"` | ||
| } | ||
|
|
||
| name := d.Get("name").(string) | ||
| input := githubv4.UpdateIpAllowListEntryInput{ | ||
| IPAllowListEntryID: githubv4.ID(d.Id()), | ||
| AllowListValue: githubv4.String(d.Get("ip").(string)), | ||
| IsActive: githubv4.Boolean(d.Get("is_active").(bool)), | ||
| } | ||
|
|
||
| if name != "" { | ||
| input.Name = githubv4.NewString(githubv4.String(name)) | ||
| } | ||
|
|
||
| err := client.Mutate(ctx, &mutation, input, nil) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| return resourceGithubEnterpriseIpAllowListEntryRead(d, meta) | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryDelete(d *schema.ResourceData, meta interface{}) error { | ||
| client := meta.(*Owner).v4client | ||
| ctx := context.WithValue(context.Background(), ctxId, d.Id()) | ||
|
|
||
| var mutation struct { | ||
| DeleteIpAllowListEntry struct { | ||
| ClientMutationID githubv4.String | ||
| } `graphql:"deleteIpAllowListEntry(input: $input)"` | ||
| } | ||
|
|
||
| input := githubv4.DeleteIpAllowListEntryInput{ | ||
| IPAllowListEntryID: githubv4.ID(d.Id()), | ||
| } | ||
|
|
||
| err := client.Mutate(ctx, &mutation, input, nil) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| d.SetId("") | ||
| return nil | ||
| } | ||
|
|
||
| // Helper function to get Enterprise ID from slug | ||
| func getEnterpriseID(ctx context.Context, client *githubv4.Client, enterpriseSlug string) (string, error) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK enterprise slug is the ID, the GraphQL ID shouldn't pollute TF.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If i understand this correctly, you'd still want the resource to reference the slug (and not the GraphQL ID) like so: resource "github_enterprise_ip_allow_list_entry" "example" {
enterprise_slug = "my-enterprise-slug"
ip = "1.2.3.4"
}If you pass
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it makes sense that it's not the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The GraphQL ID may be required, but it shouldn't be the resource ID as that should be the slug. I'm not fully familiar with the GitHub GraphQL implementation but isn't the whole point of GraphQL that you can do everything with a single query; in which case why can't you use the slug to lookup the GraphQL ID in the same query?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you can combine multiple "requests" in a single GQL query, but directly dependant things aren't possible AFAIK.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't see a pending REST implementation. We may have to leak a GraphQL ID into the state after all. I'd suggest using
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we sure IP isn't unique in the context of an enterprise?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we have had multiple duplicate IPs. We could treat it as unique even if the API allows duplicate IPS. One reason for allowing duplicate IPs would be ease of management. For example the IPs in api.github.com/meta have duplicates between products. If I'd be syncing these blindly to the allowlist with the product name (which we are doing) then it needs to enable duplicate IPS 😬
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not that it's relevant to this PR, but wouldn't you run them through
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function seems to exist already in resource_github_enterprise_organization.go, could you re-use that? Maybe it could be moved to |
||
| var query struct { | ||
| Enterprise struct { | ||
| ID githubv4.ID | ||
| } `graphql:"enterprise(slug: $slug)"` | ||
| } | ||
|
|
||
| variables := map[string]interface{}{ | ||
| "slug": githubv4.String(enterpriseSlug), | ||
| } | ||
|
|
||
| err := client.Query(ctx, &query, variables) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| return query.Enterprise.ID.(string), nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,101 @@ | ||||||
| package github | ||||||
|
|
||||||
| import ( | ||||||
| "fmt" | ||||||
| "testing" | ||||||
|
|
||||||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||||||
| ) | ||||||
|
|
||||||
| func TestAccGithubEnterpriseIpAllowListEntry_basic(t *testing.T) { | ||||||
| t.Skip("Acceptance test requires a real GitHub Enterprise environment") | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| resourceName := "github_enterprise_ip_allow_list_entry.test" | ||||||
| enterpriseSlug := "test-enterprise" | ||||||
| ip := "192.168.1.0/24" | ||||||
| name := "Test Entry" | ||||||
| isActive := true | ||||||
|
|
||||||
| resource.Test(t, resource.TestCase{ | ||||||
| PreCheck: func() { | ||||||
| testAccPreCheck(t) | ||||||
| testAccPreCheckEnterprise(t) | ||||||
ErikElkins marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| }, | ||||||
| Providers: testAccProviders, | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| Steps: []resource.TestStep{ | ||||||
| { | ||||||
| Config: testAccGithubEnterpriseIpAllowListEntryConfig(enterpriseSlug, ip, name, isActive), | ||||||
| Check: resource.ComposeTestCheckFunc( | ||||||
| resource.TestCheckResourceAttr(resourceName, "enterprise_slug", enterpriseSlug), | ||||||
| resource.TestCheckResourceAttr(resourceName, "ip", ip), | ||||||
| resource.TestCheckResourceAttr(resourceName, "name", name), | ||||||
| resource.TestCheckResourceAttr(resourceName, "is_active", fmt.Sprintf("%t", isActive)), | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| ), | ||||||
| }, | ||||||
| { | ||||||
| ResourceName: resourceName, | ||||||
| ImportState: true, | ||||||
| ImportStateVerify: true, | ||||||
| }, | ||||||
| }, | ||||||
| }) | ||||||
| } | ||||||
|
|
||||||
| func TestAccGithubEnterpriseIpAllowListEntry_update(t *testing.T) { | ||||||
| t.Skip("Acceptance test requires a real GitHub Enterprise environment") | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| resourceName := "github_enterprise_ip_allow_list_entry.test" | ||||||
| enterpriseSlug := "test-enterprise" | ||||||
| ip := "192.168.1.0/24" | ||||||
| name := "Test Entry" | ||||||
| isActive := true | ||||||
|
|
||||||
| updatedIP := "10.0.0.0/16" | ||||||
| updatedName := "Updated Entry" | ||||||
| updatedIsActive := false | ||||||
|
|
||||||
| resource.Test(t, resource.TestCase{ | ||||||
| PreCheck: func() { | ||||||
| testAccPreCheck(t) | ||||||
| testAccPreCheckEnterprise(t) | ||||||
ErikElkins marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| }, | ||||||
| Providers: testAccProviders, | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| Steps: []resource.TestStep{ | ||||||
| { | ||||||
| Config: testAccGithubEnterpriseIpAllowListEntryConfig(enterpriseSlug, ip, name, isActive), | ||||||
| Check: resource.ComposeTestCheckFunc( | ||||||
| resource.TestCheckResourceAttr(resourceName, "enterprise_slug", enterpriseSlug), | ||||||
| resource.TestCheckResourceAttr(resourceName, "ip", ip), | ||||||
| resource.TestCheckResourceAttr(resourceName, "name", name), | ||||||
| resource.TestCheckResourceAttr(resourceName, "is_active", fmt.Sprintf("%t", isActive)), | ||||||
| ), | ||||||
| }, | ||||||
| { | ||||||
| Config: testAccGithubEnterpriseIpAllowListEntryConfig(enterpriseSlug, updatedIP, updatedName, updatedIsActive), | ||||||
| Check: resource.ComposeTestCheckFunc( | ||||||
| resource.TestCheckResourceAttr(resourceName, "enterprise_slug", enterpriseSlug), | ||||||
| resource.TestCheckResourceAttr(resourceName, "ip", updatedIP), | ||||||
| resource.TestCheckResourceAttr(resourceName, "name", updatedName), | ||||||
| resource.TestCheckResourceAttr(resourceName, "is_active", fmt.Sprintf("%t", updatedIsActive)), | ||||||
| ), | ||||||
| }, | ||||||
| }, | ||||||
| }) | ||||||
| } | ||||||
|
|
||||||
| func testAccGithubEnterpriseIpAllowListEntryConfig(enterpriseSlug, ip, name string, isActive bool) string { | ||||||
| return fmt.Sprintf(` | ||||||
| resource "github_enterprise_ip_allow_list_entry" "test" { | ||||||
| enterprise_slug = "%s" | ||||||
| ip = "%s" | ||||||
| name = "%s" | ||||||
| is_active = %t | ||||||
| } | ||||||
| `, enterpriseSlug, ip, name, isActive) | ||||||
| } | ||||||
|
|
||||||
| func testAccPreCheckEnterprise(t *testing.T) { | ||||||
ErikElkins marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| if v := testAccProvider.Meta().(*Owner).name; v == "" { | ||||||
| t.Fatal("The GITHUB_ENTERPRISE_SLUG environment variable must be set for enterprise tests") | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| --- | ||
| layout: "github" | ||
| page_title: "GitHub: github_enterprise_ip_allow_list_entry" | ||
| description: |- | ||
| Creates and manages IP allow list entries within a GitHub Enterprise | ||
| --- | ||
|
|
||
| # github_enterprise_ip_allow_list_entry | ||
|
|
||
| This resource allows you to create and manage IP allow list entries for a GitHub Enterprise account. IP allow list entries define IP addresses or ranges that are permitted to access private resources in the enterprise. | ||
|
|
||
| ## Example Usage | ||
|
|
||
| ```hcl | ||
| resource "github_enterprise_ip_allow_list_entry" "test" { | ||
| enterprise_slug = "my-enterprise" | ||
| ip = "192.168.1.0/20" | ||
| name = "My IP Range Name" | ||
| is_active = true | ||
| } | ||
| ``` | ||
|
|
||
| ## Argument Reference | ||
|
|
||
| The following arguments are supported: | ||
|
|
||
| * `enterprise_slug` - (Required) The slug of the enterprise. | ||
| * `ip` - (Required) An IP address or range of IP addresses in CIDR notation. | ||
| * `name` - (Optional) A descriptive name for the IP allow list entry. | ||
| * `is_active` - (Optional) Whether the entry is currently active. Default: true. | ||
|
|
||
| ## Import | ||
|
|
||
| This resource can be imported using the ID of the IP allow list entry: | ||
|
|
||
| ```bash | ||
| $ terraform import github_enterprise_ip_allow_list_entry.test IALE_kwHOC1234567890a | ||
| ``` |


Uh oh!
There was an error while loading. Please reload this page.