diff --git a/docs/data-sources/ip_ranges.md b/docs/data-sources/ip_ranges.md index caeb7a51b2..2a43b03998 100644 --- a/docs/data-sources/ip_ranges.md +++ b/docs/data-sources/ip_ranges.md @@ -14,6 +14,12 @@ Use this data source to retrieve information about GitHub's IP addresses. data "github_ip_ranges" "test" {} ``` +## Timeouts + +The `timeouts` block allows you to configure [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when reading the GitHub IP ranges from the metadata API. + ## Attributes Reference - `actions` - An array of IP addresses in CIDR format specifying the addresses that incoming requests from GitHub Actions will originate from. diff --git a/github/data_source_github_ip_ranges.go b/github/data_source_github_ip_ranges.go index 6be4c6079a..87b2ef977d 100644 --- a/github/data_source_github_ip_ranges.go +++ b/github/data_source_github_ip_ranges.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -13,6 +14,9 @@ func dataSourceGithubIpRanges() *schema.Resource { return &schema.Resource{ Description: "Get the GitHub IP ranges used by various GitHub services.", ReadContext: dataSourceGithubIpRangesRead, + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(5 * time.Minute), + }, Schema: map[string]*schema.Schema{ "hooks": { Type: schema.TypeList, diff --git a/github/data_source_github_ip_ranges_test.go b/github/data_source_github_ip_ranges_test.go index a4f5801701..0b069a455a 100644 --- a/github/data_source_github_ip_ranges_test.go +++ b/github/data_source_github_ip_ranges_test.go @@ -1,17 +1,117 @@ package github import ( + "context" + "net/http" + "net/http/httptest" "testing" + "time" + "github.com/google/go-github/v89/github" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/knownvalue" "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) +func TestGithubIpRangesDataSourceRead(t *testing.T) { + t.Parallel() + + t.Run("gives up on a stalled metadata request once the deadline expires", func(t *testing.T) { + t.Parallel() + + serverDelay := 5 * time.Second + readDeadline := 200 * time.Millisecond + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + select { + case <-r.Context().Done(): + case <-time.After(serverDelay): + mustWrite(w, `{}`) + } + })) + defer ts.Close() + + // StopContext mirrors the provider configuration and never expires, so the + // read has to use the context it is called with to respect the deadline. + meta := &Owner{ + v3client: mustCreateTestGitHubClient(t, ts.URL), + StopContext: context.Background(), + } + + ctx, cancel := context.WithTimeout(t.Context(), readDeadline) + defer cancel() + + start := time.Now() + diags := dataSourceGithubIpRangesRead(ctx, dataSourceGithubIpRanges().TestResourceData(), meta) + elapsed := time.Since(start) + + if !diags.HasError() { + t.Fatal("expected an error when the read deadline expires before the response arrives") + } + if elapsed >= serverDelay { + t.Fatalf("read waited %s for the metadata response instead of honoring the %s deadline", elapsed, readDeadline) + } + }) + + t.Run("populates IP ranges when the response arrives before the deadline", func(t *testing.T) { + t.Parallel() + + ts := githubApiMock([]*mockResponse{ + mustGetTestMockResponse(t, "/meta", http.StatusOK, &github.APIMeta{ + Hooks: []string{"192.0.2.0/24", "2001:db8::/32"}, + }), + }) + defer ts.Close() + + meta := &Owner{v3client: mustCreateTestGitHubClient(t, ts.URL)} + + ctx, cancel := context.WithTimeout(t.Context(), time.Minute) + defer cancel() + + d := dataSourceGithubIpRanges().TestResourceData() + diags := dataSourceGithubIpRangesRead(ctx, d, meta) + if diags.HasError() { + t.Fatalf("unexpected error: %v", diags) + } + + if got, want := d.Get("hooks_ipv4").([]any), "192.0.2.0/24"; len(got) != 1 || got[0] != want { + t.Errorf("expected hooks_ipv4 to be [%s], got %v", want, got) + } + if got, want := d.Get("hooks_ipv6").([]any), "2001:db8::/32"; len(got) != 1 || got[0] != want { + t.Errorf("expected hooks_ipv6 to be [%s], got %v", want, got) + } + }) +} + func TestAccGithubIpRangesDataSource(t *testing.T) { t.Parallel() + t.Run("reads IP ranges with a configured read timeout", func(t *testing.T) { + t.Parallel() + + config := ` + data "github_ip_ranges" "test" { + timeouts { + read = "2m" + } + } + ` + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("data.github_ip_ranges.test", tfjsonpath.New("actions_ipv4"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_ip_ranges.test", tfjsonpath.New("actions_ipv6"), knownvalue.NotNull()), + }, + }, + }, + }) + }) + t.Run("reads IP ranges without error", func(t *testing.T) { t.Parallel() diff --git a/templates/data-sources/ip_ranges.md.tmpl b/templates/data-sources/ip_ranges.md.tmpl index 9d19c4dcb3..69769ad06c 100644 --- a/templates/data-sources/ip_ranges.md.tmpl +++ b/templates/data-sources/ip_ranges.md.tmpl @@ -12,6 +12,12 @@ Use this data source to retrieve information about GitHub's IP addresses. {{ tffile "examples/data-sources/ip_ranges/example_1.tf" }} +## Timeouts + +The `timeouts` block allows you to configure [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when reading the GitHub IP ranges from the metadata API. + ## Attributes Reference - `actions` - An array of IP addresses in CIDR format specifying the addresses that incoming requests from GitHub Actions will originate from.