Skip to content

refactor(exoscale): migrate provider to egoscale v3#6371

Open
natalie-o-perret wants to merge 1 commit intokubernetes-sigs:masterfrom
exoscale:refactor/exoscale-provider-v3
Open

refactor(exoscale): migrate provider to egoscale v3#6371
natalie-o-perret wants to merge 1 commit intokubernetes-sigs:masterfrom
exoscale:refactor/exoscale-provider-v3

Conversation

@natalie-o-perret
Copy link
Copy Markdown

@natalie-o-perret natalie-o-perret commented Apr 15, 2026

Relates to:

Changes:

@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 15, 2026
@k8s-ci-robot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign ivankatliarchuk for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the provider Issues or PRs related to a provider label Apr 15, 2026
@k8s-ci-robot k8s-ci-robot requested a review from vflaux April 15, 2026 14:27
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla bot commented Apr 15, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

  • ✅ login: natalie-o-perret / name: Natalie Perret (f33fc10)

@k8s-ci-robot k8s-ci-robot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Apr 15, 2026
@k8s-ci-robot
Copy link
Copy Markdown
Contributor

Hi @natalie-o-perret. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot k8s-ci-robot added cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. and removed cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. labels Apr 15, 2026
@natalie-o-perret natalie-o-perret force-pushed the refactor/exoscale-provider-v3 branch from 6a10551 to 8a5880d Compare April 15, 2026 14:38
@k8s-ci-robot k8s-ci-robot added the apis Issues or PRs related to API change label Apr 15, 2026
@natalie-o-perret natalie-o-perret marked this pull request as ready for review April 15, 2026 14:50
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 15, 2026
@ivankatliarchuk
Copy link
Copy Markdown
Member

/ok-to-test

@k8s-ci-robot k8s-ci-robot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Apr 15, 2026
@natalie-o-perret natalie-o-perret marked this pull request as draft April 15, 2026 15:29
@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 15, 2026
@natalie-o-perret natalie-o-perret force-pushed the refactor/exoscale-provider-v3 branch from 8a5880d to fc9d858 Compare April 15, 2026 15:45
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 15, 2026

Coverage Report for CI Build 24484425352

Coverage decreased (-0.03%) to 80.49%

Details

  • Coverage decreased (-0.03%) from the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • 52 coverage regressions across 1 file.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

52 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
exoscale/exoscale.go 52 71.09%

Coverage Stats

Coverage Status
Relevant Lines: 21363
Covered Lines: 17195
Line Coverage: 80.49%
Coverage Strength: 1467.6 hits per line

💛 - Coveralls

@ivankatliarchuk
Copy link
Copy Markdown
Member

Could you share similar results for this PR #5085 (comment). Need to make sure it works

Copy link
Copy Markdown
Member

@ivankatliarchuk ivankatliarchuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth to execute make cover-html

And cover with unit tests, modified lines

Image

Comment thread provider/exoscale/exoscale.go

record.Type = &epoint.RecordType
record.Content = &epoint.Targets[0]
req := v3.UpdateDNSDomainRecordRequest{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only Content and Ttl are updated. The original code set record.Type = &epoint.RecordType. How the type is handled?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v3 UpdateDNSDomainRecordRequest struct does not have a Type field, it only exposes Content, Name, Priority, and Ttl:

type UpdateDNSDomainRecordRequest struct {
    Content  string `json:"content,omitempty"`
    Name     string `json:"name,omitempty"`
    Priority int64  `json:"priority,omitempty"`
    Ttl      int64  `json:"ttl,omitempty"`
}

DNS record types are immutable on the Exoscale API (you cannot change an A record into a CNAME. You'd delete and recreate).

So the old v2 code setting record.Type = &epoint.RecordType was effectively a no-op at the API level.

To make sure we match the correct record during update/delete, the loop now includes a type-filter guard:

if string(record.Type) != epoint.RecordType {
    continue
}

This prevents updating/deleting a record that has the same name but a different type (e.g., matching a TXT record when the endpoint is an A record).

Comment thread provider/exoscale/exoscale_test.go Outdated
Comment thread go.mod Outdated
@Raffo
Copy link
Copy Markdown
Contributor

Raffo commented Apr 15, 2026

What if we build a webhook and move this out of tree instead?

@natalie-o-perret natalie-o-perret force-pushed the refactor/exoscale-provider-v3 branch from fc9d858 to f33fc10 Compare April 15, 2026 23:53
@natalie-o-perret
Copy link
Copy Markdown
Author

natalie-o-perret commented Apr 15, 2026

Could you share similar results for this PR #5085 (comment). Need to make sure it works

Tested the v3-migrated provider against a live Exoscale DNS zone.

The Go test imports and calls NewExoscaleProviderWithClient, ApplyChanges, and Records directly via egoscale/v3, covering the full CREATE -> UPDATE -> DELETE lifecycle (using an apex A record as the test case).

A Python wrapper cross-checks each step with exo dns show and dig queries.

Zone name and record UUIDs are redacted.

Go integration test source (main.go)
package main

import (
	"context"
	"fmt"
	"os"
	"time"

	v3 "github.com/exoscale/egoscale/v3"
	"github.com/exoscale/egoscale/v3/credentials"

	"sigs.k8s.io/external-dns/endpoint"
	"sigs.k8s.io/external-dns/plan"
	exoscale "sigs.k8s.io/external-dns/provider/exoscale"
)

func main() {
	zone := os.Getenv("TEST_ZONE")
	key := os.Getenv("TEST_KEY")
	secret := os.Getenv("TEST_SECRET")
	apizone := os.Getenv("TEST_APIZONE")
	if apizone == "" {
		apizone = "ch-gva-2"
	}

	if zone == "" || key == "" || secret == "" {
		fmt.Fprintln(os.Stderr, "set TEST_ZONE, TEST_KEY, TEST_SECRET")
		os.Exit(1)
	}

	creds := credentials.NewStaticCredentials(key, secret)
	ep := v3.Endpoint(fmt.Sprintf("https://api-%s.exoscale.com/v2", apizone))
	client, err := v3.NewClient(creds, v3.ClientOptWithEndpoint(ep))
	if err != nil {
		panic(err)
	}

	provider := exoscale.NewExoscaleProviderWithClient(
		&liveClient{c: client},
		false,
		0,
		exoscale.ExoscaleWithDomain(endpoint.NewDomainFilter([]string{zone})),
	)

	ctx := context.Background()

	// Step 1: BEFORE
	fmt.Println("=== STEP 1: Records() BEFORE ===")
	printRecords(provider, ctx)

	// Step 2: CREATE apex A record
	fmt.Printf("\n=== STEP 2: CREATE apex A record (%s -> 203.0.113.42) ===\n", zone)
	err = provider.ApplyChanges(ctx, &plan.Changes{
		Create: []*endpoint.Endpoint{
			endpoint.NewEndpoint(zone, endpoint.RecordTypeA, "203.0.113.42"),
		},
	})
	if err != nil {
		panic(fmt.Sprintf("CREATE failed: %v", err))
	}
	fmt.Println("OK")
	time.Sleep(2 * time.Second)

	// Step 3: Records() AFTER CREATE
	fmt.Println("\n=== STEP 3: Records() AFTER CREATE ===")
	printRecords(provider, ctx)

	// Step 4: UPDATE apex A record to new IP
	fmt.Printf("\n=== STEP 4: UPDATE apex A record (%s -> 198.51.100.1) ===\n", zone)
	err = provider.ApplyChanges(ctx, &plan.Changes{
		UpdateOld: []*endpoint.Endpoint{
			endpoint.NewEndpoint(zone, endpoint.RecordTypeA, "203.0.113.42"),
		},
		UpdateNew: []*endpoint.Endpoint{
			endpoint.NewEndpoint(zone, endpoint.RecordTypeA, "198.51.100.1"),
		},
	})
	if err != nil {
		panic(fmt.Sprintf("UPDATE failed: %v", err))
	}
	fmt.Println("OK")
	time.Sleep(2 * time.Second)

	// Step 5: Records() AFTER UPDATE
	fmt.Println("\n=== STEP 5: Records() AFTER UPDATE ===")
	printRecords(provider, ctx)

	// Step 6: DELETE apex A record
	fmt.Printf("\n=== STEP 6: DELETE apex A record ===\n")
	err = provider.ApplyChanges(ctx, &plan.Changes{
		Delete: []*endpoint.Endpoint{
			endpoint.NewEndpoint(zone, endpoint.RecordTypeA, "198.51.100.1"),
		},
	})
	if err != nil {
		panic(fmt.Sprintf("DELETE failed: %v", err))
	}
	fmt.Println("OK")

	// Step 7: Records() FINAL
	fmt.Println("\n=== STEP 7: Records() FINAL (should be empty) ===")
	printRecords(provider, ctx)

	fmt.Println("\n=== ALL STEPS PASSED ===")
}

func printRecords(p *exoscale.ExoscaleProvider, ctx context.Context) {
	recs, err := p.Records(ctx)
	if err != nil {
		panic(err)
	}
	if len(recs) == 0 {
		fmt.Println("  (no managed records)")
	}
	for _, r := range recs {
		fmt.Printf("  %s  %s  %v  TTL=%d\n", r.DNSName, r.RecordType, r.Targets, r.RecordTTL)
	}
}

// liveClient wraps the real v3.Client to satisfy the EgoscaleClientI interface.
type liveClient struct{ c *v3.Client }

func (w *liveClient) ListDNSDomains(ctx context.Context) ([]v3.DNSDomain, error) {
	resp, err := w.c.ListDNSDomains(ctx)
	if err != nil {
		return nil, err
	}
	return resp.DNSDomains, nil
}
func (w *liveClient) ListDNSDomainRecords(ctx context.Context, id v3.UUID) ([]v3.DNSDomainRecord, error) {
	resp, err := w.c.ListDNSDomainRecords(ctx, id)
	if err != nil {
		return nil, err
	}
	return resp.DNSDomainRecords, nil
}
func (w *liveClient) CreateDNSDomainRecord(ctx context.Context, id v3.UUID, req v3.CreateDNSDomainRecordRequest) error {
	op, err := w.c.CreateDNSDomainRecord(ctx, id, req)
	if err != nil {
		return err
	}
	_, err = w.c.Wait(ctx, op, v3.OperationStateSuccess)
	return err
}
func (w *liveClient) DeleteDNSDomainRecord(ctx context.Context, domainID v3.UUID, recordID v3.UUID) error {
	op, err := w.c.DeleteDNSDomainRecord(ctx, domainID, recordID)
	if err != nil {
		return err
	}
	_, err = w.c.Wait(ctx, op, v3.OperationStateSuccess)
	return err
}
func (w *liveClient) UpdateDNSDomainRecord(ctx context.Context, domainID v3.UUID, recordID v3.UUID, req v3.UpdateDNSDomainRecordRequest) error {
	op, err := w.c.UpdateDNSDomainRecord(ctx, domainID, recordID, req)
	if err != nil {
		return err
	}
	_, err = w.c.Wait(ctx, op, v3.OperationStateSuccess)
	return err
}
Python orchestrator (run_e2e.py)
#!/usr/bin/env python3
"""
Live E2E test for external-dns Exoscale provider: DNS record lifecycle.

Runs the Go integration test through the actual provider code (ApplyChanges / Records),
then cross-checks each step with `exo dns show` and `dig`.

Output is formatted as a markdown comment suitable for pasting on the PR.

Usage:
    export TEST_ZONE="<your-test-zone>"
    export TEST_KEY="<exoscale-api-key>"
    export TEST_SECRET="<exoscale-api-secret>"
    python3 run_e2e.py [--redact] [--mask-ids]
"""
import argparse
import os
import re
import subprocess
import sys
import time

ZONE = os.environ.get("TEST_ZONE", "")
KEY = os.environ.get("TEST_KEY", "")
SECRET = os.environ.get("TEST_SECRET", "")
APIZONE = os.environ.get("TEST_APIZONE", "ch-gva-2")
GO_TEST_DIR = os.path.dirname(os.path.abspath(__file__))


def run(cmd, *, check=True, timeout=60):
    """Run a subprocess, return stdout."""
    r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
    if check and r.returncode != 0:
        print(f"COMMAND FAILED: {' '.join(cmd)}", file=sys.stderr)
        print(r.stdout, file=sys.stderr)
        print(r.stderr, file=sys.stderr)
        sys.exit(1)
    return r.stdout.strip()


def exo_dns_show():
    """Return `exo dns show <zone>` output (table of all records)."""
    return run(["exo", "dns", "show", ZONE])


def dig_apex(rtype="A"):
    """Query Exoscale authoritative NS for the apex record."""
    try:
        out = run(
            ["dig", f"@ns1.exoscale.com", ZONE, rtype, "+short"],
            check=False,
            timeout=10,
        )
        return out if out else "(empty)"
    except FileNotFoundError:
        return "(dig not available)"


def banner(title):
    return f"\n### {title}\n"


def code_block(content, lang=""):
    return f"```{lang}\n{content}\n```"


def redact_output(text, zone, mask_ids=False):
    """Replace the real zone name and optionally mask UUIDs."""
    text = text.replace(zone, "<redacted>.net")
    if mask_ids:
        text = re.sub(
            r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
            "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            text,
        )
    return text


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--redact", action="store_true", help="Redact zone name in output")
    parser.add_argument("--mask-ids", action="store_true", help="Mask record UUIDs with XX")
    args = parser.parse_args()

    if not ZONE or not KEY or not SECRET:
        print("ERROR: set TEST_ZONE, TEST_KEY, TEST_SECRET env vars", file=sys.stderr)
        sys.exit(1)

    output_parts = []
    output_parts.append("## Live E2E test: Exoscale provider (egoscale v3)\n")
    output_parts.append(
        "This test exercises the v3-migrated provider code (`ApplyChanges` / `Records`) "
        "against a real Exoscale DNS zone, covering CREATE, UPDATE, and DELETE "
        "of an A record.\n"
    )

    # Step 1: Verify clean state
    output_parts.append(banner("Step 1: BEFORE (clean zone, no managed records)"))
    output_parts.append("**`exo dns show`**")
    output_parts.append(code_block(exo_dns_show()))
    output_parts.append(f"**`dig @ns1.exoscale.com {ZONE} A`**")
    output_parts.append(code_block(dig_apex()))

    # Steps 2-7: Run Go integration test
    output_parts.append(banner("Steps 2-7: Provider lifecycle (Go integration test)"))
    output_parts.append(
        "Calls `NewExoscaleProviderWithClient` with a live `egoscale/v3` API client, "
        "then runs CREATE => Records() => UPDATE => Records() => DELETE => Records()."
    )

    env = {**os.environ, "TEST_ZONE": ZONE, "TEST_KEY": KEY,
           "TEST_SECRET": SECRET, "TEST_APIZONE": APIZONE}
    go_result = subprocess.run(
        ["go", "run", "."], capture_output=True, text=True,
        timeout=120, cwd=GO_TEST_DIR, env=env,
    )
    go_out = "\n".join(
        line for line in (go_result.stdout + go_result.stderr).splitlines()
        if not line.startswith("INFO[") and not line.startswith("time=")
    ).strip()

    if go_result.returncode != 0:
        output_parts.append(code_block(go_out))
        output_parts.append("\n**FAILED**: see output above.\n")
        result = "\n".join(output_parts)
        if args.redact:
            result = redact_output(result, ZONE, mask_ids=args.mask_ids)
        print(result)
        sys.exit(1)

    output_parts.append(code_block(go_out))

    # Cross-check: re-create for dig verification
    output_parts.append(banner("Cross-check: `dig` after provider CREATE"))
    output_parts.append("Re-creating the record to verify via authoritative DNS query:")
    run(["exo", "dns", "add", "A", ZONE, "-n", "", "-a", "203.0.113.42"])
    time.sleep(2)
    output_parts.append(f"\n**`dig @ns1.exoscale.com {ZONE} A +short`**")
    output_parts.append(code_block(dig_apex()))
    output_parts.append(f"\n**`exo dns show`** (A record visible):")
    output_parts.append(code_block(exo_dns_show()))

    # Cleanup
    show_output = run(["exo", "dns", "show", ZONE])
    for line in show_output.splitlines():
        if "203.0.113.42" in line:
            record_id = line.split("|")[1].strip() if "|" in line else ""
            if record_id:
                run(["exo", "dns", "remove", "-f", ZONE, record_id])
                break

    output_parts.append(banner("FINAL (zone clean after test)"))
    output_parts.append(code_block(exo_dns_show()))

    # Result
    output_parts.append(banner("Result"))
    output_parts.append(
        "All operations completed successfully. The v3-migrated provider correctly "
        "handles the full CREATE -> READ -> UPDATE -> DELETE lifecycle "
        "against a live Exoscale DNS zone."
    )

    result = "\n".join(output_parts)
    if args.redact:
        result = redact_output(result, ZONE, mask_ids=args.mask_ids)
    print(result)


if __name__ == "__main__":
    main()

Step 1: BEFORE (clean zone, no managed records)

exo dns show

|                  ID                  | NAME | RECORD TYPE |                               CONTENT                                | PRIO | TTL  |
|--------------------------------------|------|-------------|----------------------------------------------------------------------|------|------|
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | SOA         | ns1.exoscale.com admin.dnsimple.com 1775572267 86400 7200 604800 300 | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.com                                                     | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.ch                                                      | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.net                                                     | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.io                                                      | 0    | 3600 |

dig @ns1.exoscale.com <redacted>.net A

(empty)

Steps 2-7: Provider lifecycle (Go integration test)

Calls NewExoscaleProviderWithClient with a live egoscale/v3 API client, then runs CREATE => Records() => UPDATE => Records() => DELETE => Records().

=== STEP 1: Records() BEFORE ===
  (no managed records)

=== STEP 2: CREATE apex A record (<redacted>.net -> 203.0.113.42) ===
OK

=== STEP 3: Records() AFTER CREATE ===
  <redacted>.net  A  203.0.113.42  TTL=3600

=== STEP 4: UPDATE apex A record (<redacted>.net -> 198.51.100.1) ===
OK

=== STEP 5: Records() AFTER UPDATE ===
  <redacted>.net  A  198.51.100.1  TTL=3600

=== STEP 6: DELETE apex A record ===
OK

=== STEP 7: Records() FINAL (should be empty) ===
  (no managed records)

=== ALL STEPS PASSED ===

Cross-check: dig after provider CREATE

Re-creating the record to verify via authoritative DNS query:

dig @ns1.exoscale.com <redacted>.net A +short

203.0.113.42

exo dns show (A record visible):

|                  ID                  | NAME | RECORD TYPE |                               CONTENT                                | PRIO | TTL  |
|--------------------------------------|------|-------------|----------------------------------------------------------------------|------|------|
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | SOA         | ns1.exoscale.com admin.dnsimple.com 1775572271 86400 7200 604800 300 | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.com                                                     | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.ch                                                      | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.net                                                     | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.io                                                      | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | A           | 203.0.113.42                                                         | 0    | 3600 |

FINAL (zone clean after test)

|                  ID                  | NAME | RECORD TYPE |                               CONTENT                                | PRIO | TTL  |
|--------------------------------------|------|-------------|----------------------------------------------------------------------|------|------|
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | SOA         | ns1.exoscale.com admin.dnsimple.com 1775572272 86400 7200 604800 300 | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.com                                                     | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.ch                                                      | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.net                                                     | 0    | 3600 |
| XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX |      | NS          | ns1.exoscale.io                                                      | 0    | 3600 |

Result

All operations completed successfully.

The v3-migrated provider correctly handles the full CREATE -> READ -> UPDATE -> DELETE lifecycle against a live Exoscale DNS zone.


Note: both IPs listed above are from RFC 5737 reserved ranges for documentation/examples:

  • 203.0.113.42 is in 203.0.113.0/24 (TEST-NET-3)
  • 198.51.100.1 is in 198.51.100.0/24 (TEST-NET-2)

They don't route to real infrastructure.

Same idea as 192.0.2.0/24 (TEST-NET-1), IANA reserves them specifically so they're safe to use in examples, docs, and test output without risk of pointing at someone's actual server.

@natalie-o-perret
Copy link
Copy Markdown
Author

What if we build a webhook and move this out of tree instead?

That is definitely something we can look into.

Tbs, Moving to a webhook-based out-of-tree provider would be a bigger scope change.

We can discuss that as a separate effort.

For now, this PR focuses on unblocking the egoscale/v3 migration and fixing the existing provider bugs.

@natalie-o-perret natalie-o-perret marked this pull request as ready for review April 16, 2026 09:12
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

apis Issues or PRs related to API change cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. docs ok-to-test Indicates a non-member PR verified by an org member that is safe to test. provider Issues or PRs related to a provider size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants