Skip to content

Commit 48c3cb2

Browse files
committed
fixup CI integration test
1 parent dab9bc0 commit 48c3cb2

1 file changed

Lines changed: 112 additions & 5 deletions

File tree

ci/test.go

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"io"
78
"log/slog"
@@ -10,6 +11,7 @@ import (
1011
"net/http"
1112
"os"
1213
"os/exec"
14+
"path/filepath"
1315
"strings"
1416
"sync"
1517
"time"
@@ -28,9 +30,9 @@ const parallelism = 10
2830

2931
func main() {
3032
log := slog.New(slog.NewTextHandler(os.Stdout, nil))
31-
googleCIDRs, err := helper.FetchGooglebotIPs(log, http.DefaultClient, "https://developers.google.com/static/search/apis/ipranges/googlebot.json")
33+
googleCIDRs, err := helper.FetchGoogleCrawlerIPs(log, http.DefaultClient, helper.GoogleCrawlerIPRangeURLs)
3234
if err != nil {
33-
slog.Error("unable to fetch google bot ips", "err", err)
35+
slog.Error("unable to fetch google crawler ips", "err", err)
3436
os.Exit(1)
3537
}
3638

@@ -52,16 +54,27 @@ func main() {
5254
fmt.Printf("Generating %d IPs\n", numIPs)
5355
ips := generateUniquePublicIPs(numIPs)
5456

57+
statePath, err := prepareStateFile(0o777, 0o666)
58+
if err != nil {
59+
slog.Error("Failed to prepare state file", "statePath", statePath, "err", err)
60+
os.Exit(1)
61+
}
62+
5563
fmt.Println("Bringing traefik/nginx online")
5664
runCommand("docker", "compose", "up", "-d")
5765
waitForService("http://localhost")
5866
waitForService("http://localhost/app2")
67+
waitForGoogleExemptionReady(googleCIDRs)
5968

6069
fmt.Printf("Making sure %d attempt(s) pass\n", rateLimit)
6170
runParallelChecks(ips, rateLimit, "http://localhost")
6271

63-
time.Sleep(cp.StateSaveInterval + cp.StateSaveJitter + (1 * time.Second))
64-
runCommand("jq", ".", "tmp/state.json")
72+
statePath, err = waitForStateFile(30 * time.Second)
73+
if err != nil {
74+
slog.Error("State file was not created in time", "err", err)
75+
os.Exit(1)
76+
}
77+
runCommand("jq", ".", statePath)
6578

6679
fmt.Printf("Making sure attempt #%d causes a redirect to the challenge page\n", rateLimit+1)
6780
ensureRedirect(ips, "http://localhost")
@@ -81,7 +94,7 @@ func main() {
8194
time.Sleep(10 * time.Second)
8295
checkStateReload()
8396

84-
runCommand("rm", "tmp/state.json")
97+
runCommand("rm", "-f", statePath)
8598

8699
}
87100

@@ -421,3 +434,97 @@ func testGoogleBotGetsThrough(googleCIDRs []string) {
421434
// set things back to normal for other tests
422435
runCommand("docker", "compose", "down")
423436
}
437+
438+
func waitForGoogleExemptionReady(googleCIDRs []string) {
439+
googleIP, err := firstUsableIPv4FromCIDRs(googleCIDRs)
440+
if err != nil {
441+
slog.Warn("Unable to select Google IP for readiness check; skipping warmup", "err", err)
442+
return
443+
}
444+
445+
deadline := time.Now().Add(90 * time.Second)
446+
for time.Now().Before(deadline) {
447+
ready := true
448+
for i := 0; i < rateLimit+1; i++ {
449+
if output := httpRequest(googleIP, "http://localhost"); output != "" {
450+
ready = false
451+
break
452+
}
453+
}
454+
if ready {
455+
fmt.Printf("Google exemption is active for %s\n", googleIP)
456+
return
457+
}
458+
time.Sleep(500 * time.Millisecond)
459+
}
460+
461+
slog.Error("Timed out waiting for Google crawler IP exemption to become active", "googleIP", googleIP)
462+
os.Exit(1)
463+
}
464+
465+
func firstUsableIPv4FromCIDRs(cidrs []string) (string, error) {
466+
for _, cidr := range cidrs {
467+
ip, err := getIPFromCIDR(cidr)
468+
if err != nil {
469+
continue
470+
}
471+
parsed := net.ParseIP(ip)
472+
if parsed != nil && parsed.To4() != nil {
473+
return ip, nil
474+
}
475+
}
476+
477+
return "", fmt.Errorf("no usable IPv4 found in CIDR list")
478+
}
479+
480+
func waitForStateFile(timeout time.Duration) (string, error) {
481+
paths := []string{
482+
filepath.Join("tmp", "state.json"),
483+
filepath.Join("ci", "tmp", "state.json"),
484+
}
485+
486+
deadline := time.Now().Add(timeout)
487+
for time.Now().Before(deadline) {
488+
for _, p := range paths {
489+
info, err := os.Stat(p)
490+
if err == nil && !info.IsDir() {
491+
return p, nil
492+
}
493+
if err != nil && !errors.Is(err, os.ErrNotExist) {
494+
return "", fmt.Errorf("failed to stat %s: %w", p, err)
495+
}
496+
}
497+
time.Sleep(500 * time.Millisecond)
498+
}
499+
500+
return "", fmt.Errorf("state file not found; checked: %s", strings.Join(paths, ", "))
501+
}
502+
503+
func prepareStateFile(dirMode, fileMode os.FileMode) (string, error) {
504+
paths := []string{
505+
filepath.Join("tmp", "state.json"),
506+
filepath.Join("ci", "tmp", "state.json"),
507+
}
508+
509+
for _, p := range paths {
510+
dir := filepath.Dir(p)
511+
if err := os.MkdirAll(dir, dirMode); err != nil {
512+
return "", fmt.Errorf("failed to create state dir %s: %w", dir, err)
513+
}
514+
if err := os.Chmod(dir, dirMode); err != nil {
515+
return "", fmt.Errorf("failed to chmod state dir %s: %w", dir, err)
516+
}
517+
518+
f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, fileMode)
519+
if err != nil {
520+
return "", fmt.Errorf("failed to open state file %s: %w", p, err)
521+
}
522+
_ = f.Close()
523+
if err := os.Chmod(p, fileMode); err != nil {
524+
return "", fmt.Errorf("failed to chmod state file %s: %w", p, err)
525+
}
526+
}
527+
528+
// Prefer tmp/state.json by default; waitForStateFile will still detect either.
529+
return paths[0], nil
530+
}

0 commit comments

Comments
 (0)