-
Notifications
You must be signed in to change notification settings - Fork 933
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 all commits
943df18
eb02154
935e689
83a0055
c3735ac
e1b06a2
833f3f1
60c686b
f667dcc
06b2e43
f8d8807
aed945b
a032d9d
b076b2a
d5a1915
2511b0b
4c9ac44
d593d56
10de6a7
4474a4f
01d61fc
03d79ef
4e50668
252cfe4
0369b10
8918bb8
2eb508d
85b1956
5dd8ef6
08aa432
c5e2e12
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,283 @@ | ||
| package github | ||
|
|
||
| import ( | ||
| "context" | ||
| "strings" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-log/tflog" | ||
| "github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
| "github.com/shurcooL/githubv4" | ||
| ) | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntry() *schema.Resource { | ||
| return &schema.Resource{ | ||
| Description: "Manage a GitHub Enterprise IP Allow List Entry.", | ||
| CreateContext: resourceGithubEnterpriseIpAllowListEntryCreate, | ||
| ReadContext: resourceGithubEnterpriseIpAllowListEntryRead, | ||
| UpdateContext: resourceGithubEnterpriseIpAllowListEntryUpdate, | ||
| DeleteContext: resourceGithubEnterpriseIpAllowListEntryDelete, | ||
| Importer: &schema.ResourceImporter{ | ||
| StateContext: resourceGithubEnterpriseIpAllowListEntryImport, | ||
| }, | ||
|
|
||
| Schema: map[string]*schema.Schema{ | ||
| "enterprise_slug": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
ErikElkins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ForceNew: true, | ||
| Description: "The slug of the enterprise to apply the IP allow list entry to.", | ||
| }, | ||
| "ip": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
ErikElkins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ForceNew: true, | ||
| Description: "An IP address or range of IP addresses in CIDR notation.", | ||
| }, | ||
stevehipwell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "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
|
||
| "created_at": { | ||
| Type: schema.TypeString, | ||
| Computed: true, | ||
| Description: "Timestamp of when the entry was created.", | ||
| }, | ||
| "updated_at": { | ||
| Type: schema.TypeString, | ||
| Computed: true, | ||
| Description: "Timestamp of when the entry was last updated.", | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { | ||
| client := meta.(*Owner).v4client | ||
|
|
||
| // 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 diag.FromErr(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 diag.FromErr(err) | ||
| } | ||
|
|
||
| d.SetId(string(mutation.CreateIpAllowListEntry.IpAllowListEntry.ID)) | ||
|
|
||
| if err := d.Set("created_at", mutation.CreateIpAllowListEntry.IpAllowListEntry.CreatedAt); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| if err := d.Set("updated_at", mutation.CreateIpAllowListEntry.IpAllowListEntry.UpdatedAt); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { | ||
| client := meta.(*Owner).v4client | ||
|
|
||
| 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]any{ | ||
| "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") { | ||
| tflog.Info(ctx, "Removing IP allow list entry from state because it no longer exists in GitHub", map[string]any{ | ||
| "id": d.Id(), | ||
| }) | ||
| d.SetId("") | ||
| return nil | ||
| } | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| entry := query.Node.IpAllowListEntry | ||
| if err := d.Set("name", entry.Name); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| if err := d.Set("ip", entry.AllowListValue); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| if err := d.Set("is_active", entry.IsActive); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| if err := d.Set("created_at", entry.CreatedAt); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| if err := d.Set("updated_at", entry.UpdatedAt); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { | ||
| client := meta.(*Owner).v4client | ||
|
|
||
| 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 diag.FromErr(err) | ||
| } | ||
|
|
||
| if err := d.Set("updated_at", mutation.UpdateIpAllowListEntry.IpAllowListEntry.UpdatedAt); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { | ||
| client := meta.(*Owner).v4client | ||
|
|
||
| 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) | ||
| // GraphQL will return a 200 OK if it couldn't find the global ID | ||
| if err != nil && !strings.Contains(err.Error(), "Could not resolve to a node with the global id") { | ||
| return diag.FromErr(err) | ||
|
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 error handling that a
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. Added error handling and a comment. The GraphQL API (and github go client) means it'll look kinda janky. Let me know if you'd rather have me remove it. |
||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func resourceGithubEnterpriseIpAllowListEntryImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { | ||
ErikElkins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| client := meta.(*Owner).v4client | ||
|
|
||
| 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]any{ | ||
| "id": githubv4.ID(d.Id()), | ||
| } | ||
|
|
||
| err := client.Query(ctx, &query, variables) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| entry := query.Node.IpAllowListEntry | ||
|
|
||
| if err := d.Set("enterprise_slug", string(entry.Owner.Enterprise.Slug)); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := d.Set("ip", string(entry.AllowListValue)); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := d.Set("name", entry.Name); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := d.Set("is_active", entry.IsActive); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := d.Set("created_at", entry.CreatedAt); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := d.Set("updated_at", entry.UpdatedAt); err != nil { | ||
| return nil, err | ||
| } | ||
|
Comment on lines
+234
to
+280
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. issue: Am I seeing things or did you re-add the API query to the The ID should be enough as 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. I asked for this, the query is required otherwise it'll be recreated du to the IP being unset and the import is a complete waste of time.
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. It was the recommended approach by @stevehipwell here: #2649 (comment)
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. @stevehipwell Oh, right. Actually, I wonder if there is a way to make
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. I was hoping to do the import based on EDIT: It's unique based on IP AND description |
||
|
|
||
| return []*schema.ResourceData{d}, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| package github | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strconv" | ||
| "testing" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
| ) | ||
|
|
||
| func TestAccGithubEnterpriseIpAllowListEntry(t *testing.T) { | ||
| t.Run("basic", func(t *testing.T) { | ||
| resourceName := "github_enterprise_ip_allow_list_entry.test" | ||
| ip := "192.168.1.0/24" | ||
| name := "Test Entry" | ||
| isActive := true | ||
|
|
||
| config := ` | ||
| resource "github_enterprise_ip_allow_list_entry" "test" { | ||
| enterprise_slug = "%s" | ||
| ip = "%s" | ||
| name = "%s" | ||
| is_active = %t | ||
| } | ||
| ` | ||
|
|
||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { | ||
| skipUnlessEnterprise(t) | ||
| }, | ||
| ProviderFactories: providerFactories, | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| Config: fmt.Sprintf(config, testAccConf.enterpriseSlug, ip, name, isActive), | ||
| Check: resource.ComposeTestCheckFunc( | ||
| resource.TestCheckResourceAttr(resourceName, "enterprise_slug", testAccConf.enterpriseSlug), | ||
| resource.TestCheckResourceAttr(resourceName, "ip", ip), | ||
| resource.TestCheckResourceAttr(resourceName, "name", name), | ||
| resource.TestCheckResourceAttr(resourceName, "is_active", strconv.FormatBool(isActive)), | ||
| ), | ||
| }, | ||
| { | ||
| ResourceName: resourceName, | ||
| ImportState: true, | ||
| ImportStateVerify: true, | ||
| }, | ||
| }, | ||
| }) | ||
| }) | ||
| t.Run("update", func(t *testing.T) { | ||
| resourceName := "github_enterprise_ip_allow_list_entry.test" | ||
| ip := "192.168.1.0/24" | ||
| name := "Test Entry" | ||
| isActive := true | ||
|
|
||
| updatedIP := "10.0.0.0/16" | ||
| updatedName := "Updated Entry" | ||
| updatedIsActive := false | ||
|
|
||
| config := ` | ||
| resource "github_enterprise_ip_allow_list_entry" "test" { | ||
| enterprise_slug = "%s" | ||
| ip = "%s" | ||
| name = "%s" | ||
| is_active = %t | ||
| } | ||
| ` | ||
|
|
||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { | ||
| skipUnlessEnterprise(t) | ||
| }, | ||
| ProviderFactories: providerFactories, | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| Config: fmt.Sprintf(config, testAccConf.enterpriseSlug, ip, name, isActive), | ||
| Check: resource.ComposeTestCheckFunc( | ||
| resource.TestCheckResourceAttr(resourceName, "enterprise_slug", testAccConf.enterpriseSlug), | ||
| resource.TestCheckResourceAttr(resourceName, "ip", ip), | ||
| resource.TestCheckResourceAttr(resourceName, "name", name), | ||
| resource.TestCheckResourceAttr(resourceName, "is_active", fmt.Sprintf("%t", isActive)), | ||
| ), | ||
| }, | ||
| { | ||
| Config: fmt.Sprintf(config, testAccConf.enterpriseSlug, updatedIP, updatedName, updatedIsActive), | ||
| Check: resource.ComposeTestCheckFunc( | ||
| resource.TestCheckResourceAttr(resourceName, "enterprise_slug", testAccConf.enterpriseSlug), | ||
| resource.TestCheckResourceAttr(resourceName, "ip", updatedIP), | ||
| resource.TestCheckResourceAttr(resourceName, "name", updatedName), | ||
| resource.TestCheckResourceAttr(resourceName, "is_active", fmt.Sprintf("%t", updatedIsActive)), | ||
| ), | ||
| }, | ||
| }, | ||
| }) | ||
| }) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.