diff --git a/github/provider.go b/github/provider.go index 8f44c95098..49f0d541eb 100644 --- a/github/provider.go +++ b/github/provider.go @@ -195,6 +195,7 @@ func Provider() *schema.Provider { "github_user_ssh_key": resourceGithubUserSshKey(), "github_enterprise_organization": resourceGithubEnterpriseOrganization(), "github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(), + "github_enterprise_ip_allow_list_entry": resourceGithubEnterpriseIpAllowListEntry(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/github/resource_github_enterprise_ip_allow_list_entry.go b/github/resource_github_enterprise_ip_allow_list_entry.go new file mode 100644 index 0000000000..65ebb98373 --- /dev/null +++ b/github/resource_github_enterprise_ip_allow_list_entry.go @@ -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{ + "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.", + }, + }, + } +} + +func resourceGithubEnterpriseIpAllowListEntryCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v4client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + // 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()) + 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) { + 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 +} diff --git a/github/resource_github_enterprise_ip_allow_list_entry_test.go b/github/resource_github_enterprise_ip_allow_list_entry_test.go new file mode 100644 index 0000000000..59ff1f15db --- /dev/null +++ b/github/resource_github_enterprise_ip_allow_list_entry_test.go @@ -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") + + 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) + }, + Providers: testAccProviders, + 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)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGithubEnterpriseIpAllowListEntry_update(t *testing.T) { + t.Skip("Acceptance test requires a real GitHub Enterprise environment") + + 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) + }, + Providers: testAccProviders, + 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) { + if v := testAccProvider.Meta().(*Owner).name; v == "" { + t.Fatal("The GITHUB_ENTERPRISE_SLUG environment variable must be set for enterprise tests") + } +} diff --git a/website/docs/r/enterprise_ip_allow_list_entry.html.markdown b/website/docs/r/enterprise_ip_allow_list_entry.html.markdown new file mode 100644 index 0000000000..45817dffba --- /dev/null +++ b/website/docs/r/enterprise_ip_allow_list_entry.html.markdown @@ -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 +``` \ No newline at end of file