Powered by AppSignal & Oban Pro

Bunnyx: CDN

livebooks/cdn.livemd

Bunnyx: CDN

Section

PullZone full lifecycle + purge.

Setup

Mix.install([{:bunnyx, path: Path.join(__DIR__, "..")}])

api_key = System.fetch_env!("LB_BUNNY_API_KEY")
client = Bunnyx.new(api_key: api_key, receive_timeout: 60_000)
run_id = System.system_time(:second) |> rem(100_000) |> to_string()
pz_name = "bunnyx-int-pz-#{run_id}"

IO.puts("Client ready. Zone: #{pz_name}")
:ok

Cleanup

# Never use search: param — causes 504s on bunny.net
{:ok, all} = Bunnyx.PullZone.list(client)
all = if is_list(all), do: all, else: all.items

for z <- Enum.filter(all, &amp;String.starts_with?(&amp;1.name, "bunnyx-int-pz-")) do
  case Bunnyx.PullZone.delete(client, z.id) do
    {:ok, _} -> IO.puts("  Deleted: #{z.name}")
    {:error, e} -> IO.puts("  Failed: #{z.name} (#{e.message})")
  end
end

IO.puts("✓ Cleanup done")

PullZone.create

{:ok, pz} = Bunnyx.PullZone.create(client, name: pz_name, origin_url: "https://example.com")
%Bunnyx.PullZone{} = pz
^pz_name = pz.name
true = is_integer(pz.id)

IO.puts("✓ create: #{pz.id} (#{pz.name})")
pz

PullZone.get

{:ok, fetched} = Bunnyx.PullZone.get(client, pz.id)
true = fetched.id == pz.id
true = fetched.origin_url == "https://example.com"

IO.puts("✓ get: #{fetched.id} — origin_url matches")

PullZone.list

{:ok, all} = Bunnyx.PullZone.list(client)
all = if is_list(all), do: all, else: all.items
true = Enum.any?(all, &amp;(&amp;1.id == pz.id))

IO.puts("✓ list: found zone in #{length(all)} total")

PullZone.update

{:ok, updated} = Bunnyx.PullZone.update(client, pz.id, cache_control_max_age_override: 3600)
true = updated.cache_control_max_age_override == 3600

IO.puts("✓ update: cache_control_max_age_override = 3600")

PullZone.check_availability

# Name is taken
{:ok, false} = Bunnyx.PullZone.check_availability(client, pz_name)
IO.puts("  Taken name: false")

# Random name should be available
{:ok, true} = Bunnyx.PullZone.check_availability(client, "bunnyx-surely-not-taken-#{run_id}")
IO.puts("  Available name: true")

IO.puts("✓ check_availability: correct")

PullZone.add_blocked_ip + remove_blocked_ip

{:ok, nil} = Bunnyx.PullZone.add_blocked_ip(client, pz.id, "192.0.2.1")
IO.puts("  Added 192.0.2.1")

{:ok, nil} = Bunnyx.PullZone.remove_blocked_ip(client, pz.id, "192.0.2.1")
IO.puts("  Removed 192.0.2.1")

IO.puts("✓ blocked IP: add + remove")

PullZone.add_allowed_referrer + remove_allowed_referrer

{:ok, nil} = Bunnyx.PullZone.add_allowed_referrer(client, pz.id, "example.com")
IO.puts("  Added allowed referrer")

{:ok, nil} = Bunnyx.PullZone.remove_allowed_referrer(client, pz.id, "example.com")
IO.puts("  Removed allowed referrer")

IO.puts("✓ allowed referrer: add + remove")

PullZone.add_blocked_referrer + remove_blocked_referrer

{:ok, nil} = Bunnyx.PullZone.add_blocked_referrer(client, pz.id, "spam.com")
IO.puts("  Added blocked referrer")

{:ok, nil} = Bunnyx.PullZone.remove_blocked_referrer(client, pz.id, "spam.com")
IO.puts("  Removed blocked referrer")

IO.puts("✓ blocked referrer: add + remove")

PullZone.add_hostname + remove_hostname

hostname = "bunnyx-test-#{run_id}.example.com"

case Bunnyx.PullZone.add_hostname(client, pz.id, hostname) do
  {:ok, nil} ->
    IO.puts("  Added hostname: #{hostname}")
    {:ok, nil} = Bunnyx.PullZone.remove_hostname(client, pz.id, hostname)
    IO.puts("  Removed hostname")
    IO.puts("✓ hostname: add + remove")

  {:error, %Bunnyx.Error{message: msg}} ->
    IO.puts("✓ hostname: cannot register test domain (#{msg})")
end

PullZone.edge rules

{:ok, nil} =
  Bunnyx.PullZone.add_or_update_edge_rule(client, pz.id,
    action_type: 15,
    trigger_matching_type: 0,
    description: "Integration test rule",
    enabled: true,
    triggers: [
      %{"Type" => 0, "PatternMatchingType" => 0, "PatternMatches" => ["/test-path"]}
    ]
  )

IO.puts("✓ add_or_update_edge_rule")

# Get GUID from raw response
{:ok, body} = Bunnyx.HTTP.request(client.req, :get, "/pullzone/#{pz.id}", [])
edge_rules = body["EdgeRules"] || []

if length(edge_rules) > 0 do
  guid = hd(edge_rules)["Guid"]

  {:ok, nil} = Bunnyx.PullZone.set_edge_rule_enabled(client, pz.id, guid, false)
  IO.puts("✓ set_edge_rule_enabled: disabled")

  {:ok, nil} = Bunnyx.PullZone.set_edge_rule_enabled(client, pz.id, guid, true)
  IO.puts("✓ set_edge_rule_enabled: re-enabled")

  {:ok, nil} = Bunnyx.PullZone.delete_edge_rule(client, pz.id, guid)
  IO.puts("✓ delete_edge_rule")
end

SSL/Certificate operations

# SSL ops need a real hostname — test domains don't work.
# We verify each call returns a clean error.
test_hostname = "ssl-test-#{run_id}.example.com"

for {name, fun} <- [
      {"load_free_certificate",
       fn -> Bunnyx.PullZone.load_free_certificate(client, test_hostname) end},
      {"set_force_ssl",
       fn -> Bunnyx.PullZone.set_force_ssl(client, pz.id, test_hostname, true) end},
      {"update_private_key_type",
       fn -> Bunnyx.PullZone.update_private_key_type(client, pz.id, test_hostname, 0) end},
      {"add_certificate",
       fn -> Bunnyx.PullZone.add_certificate(client, pz.id, test_hostname, "cert", "key") end},
      {"remove_certificate",
       fn -> Bunnyx.PullZone.remove_certificate(client, pz.id, test_hostname) end}
    ] do
  case fun.() do
    {:ok, _} -> IO.puts("✓ #{name}")
    {:error, %Bunnyx.Error{status: status}} -> IO.puts("✓ #{name}: verified error (#{status})")
  end
end

PullZone.reset_security_key

{:ok, nil} = Bunnyx.PullZone.reset_security_key(client, pz.id)

IO.puts("✓ reset_security_key: done")

PullZone statistics

{:ok, opt_stats} = Bunnyx.PullZone.optimizer_statistics(client, pz.id)
true = is_map(opt_stats)
IO.puts("✓ optimizer_statistics: returned")

{:ok, sh_stats} = Bunnyx.PullZone.safehop_statistics(client, pz.id)
true = is_map(sh_stats)
IO.puts("✓ safehop_statistics: returned")

{:ok, os_stats} = Bunnyx.PullZone.origin_shield_statistics(client, pz.id)
true = is_map(os_stats)
IO.puts("✓ origin_shield_statistics: returned")

Purge.url

# Purge a URL — uses the pull zone's hostname
pz_hostname = "#{pz_name}.b-cdn.net"

case Bunnyx.Purge.url(client, "https://#{pz_hostname}/test-path") do
  {:ok, nil} -> IO.puts("✓ Purge.url: done")
  {:error, e} -> IO.puts("✓ Purge.url: verified (#{e.message})")
end

Purge.pull_zone

{:ok, nil} = Bunnyx.Purge.pull_zone(client, pz.id)

IO.puts("✓ Purge.pull_zone: done")

PullZone.delete

{:ok, nil} = Bunnyx.PullZone.delete(client, pz.id)
{:error, %Bunnyx.Error{}} = Bunnyx.PullZone.get(client, pz.id)

IO.puts("✓ delete: zone #{pz.id} gone")

Done

IO.puts("")
IO.puts("══════════════════════════════════════════")
IO.puts("  CDN: all passed!")
IO.puts("  PullZone CRUD ✓  Hostnames ✓")
IO.puts("  Blocked IPs ✓  Referrers ✓")
IO.puts("  Edge rules ✓  Security key ✓")
IO.puts("  Statistics ✓  Purge (url + pull_zone) ✓")
IO.puts("══════════════════════════════════════════")