mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
chore(cloudflare): use lib v4 for regional services (#5609)
* chore(cloudflare): add cloudflare v4 client * chrome(cloudflare): cli v4 for regional hostanmes
This commit is contained in:
parent
385327e2e1
commit
e2b56049f7
5
go.mod
5
go.mod
@ -25,6 +25,7 @@ require (
|
||||
github.com/cenkalti/backoff/v5 v5.0.2
|
||||
github.com/civo/civogo v0.6.1
|
||||
github.com/cloudflare/cloudflare-go v0.115.0
|
||||
github.com/cloudflare/cloudflare-go/v4 v4.5.1
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/datawire/ambassador v1.12.4
|
||||
github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace
|
||||
@ -162,6 +163,10 @@ require (
|
||||
github.com/sosodev/duration v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.26 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
|
12
go.sum
12
go.sum
@ -186,6 +186,8 @@ github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM=
|
||||
github.com/cloudflare/cloudflare-go v0.115.0/go.mod h1:Ds6urDwn/TF2uIU24mu7H91xkKP8gSAHxQ44DSZgVmU=
|
||||
github.com/cloudflare/cloudflare-go/v4 v4.5.1 h1:ZQgQ7QO+M9rK0KYx1CmppuG15ZTYGHn8F9/Fh7mCuQQ=
|
||||
github.com/cloudflare/cloudflare-go/v4 v4.5.1/go.mod h1:XcYpLe7Mf6FN87kXzEWVnJ6z+vskW/k6eUqgqfhFE9k=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@ -990,7 +992,17 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
|
||||
|
@ -29,6 +29,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/cloudflare-go"
|
||||
cloudflarev4 "github.com/cloudflare/cloudflare-go/v4"
|
||||
"github.com/cloudflare/cloudflare-go/v4/addressing"
|
||||
"github.com/cloudflare/cloudflare-go/v4/option"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
|
||||
@ -109,17 +112,18 @@ type cloudFlareDNS interface {
|
||||
CreateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDNSRecordParams) (cloudflare.DNSRecord, error)
|
||||
DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error
|
||||
UpdateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDNSRecordParams) error
|
||||
ListDataLocalizationRegionalHostnames(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.ListDataLocalizationRegionalHostnamesParams) ([]cloudflare.RegionalHostname, error)
|
||||
CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error
|
||||
UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error
|
||||
DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error
|
||||
ListDataLocalizationRegionalHostnames(ctx context.Context, params addressing.RegionalHostnameListParams) autoPager[addressing.RegionalHostnameListResponse]
|
||||
CreateDataLocalizationRegionalHostname(ctx context.Context, params addressing.RegionalHostnameNewParams) error
|
||||
UpdateDataLocalizationRegionalHostname(ctx context.Context, hostname string, params addressing.RegionalHostnameEditParams) error
|
||||
DeleteDataLocalizationRegionalHostname(ctx context.Context, hostname string, params addressing.RegionalHostnameDeleteParams) error
|
||||
CustomHostnames(ctx context.Context, zoneID string, page int, filter cloudflare.CustomHostname) ([]cloudflare.CustomHostname, cloudflare.ResultInfo, error)
|
||||
DeleteCustomHostname(ctx context.Context, zoneID string, customHostnameID string) error
|
||||
CreateCustomHostname(ctx context.Context, zoneID string, ch cloudflare.CustomHostname) (*cloudflare.CustomHostnameResponse, error)
|
||||
}
|
||||
|
||||
type zoneService struct {
|
||||
service *cloudflare.API
|
||||
service *cloudflare.API
|
||||
serviceV4 *cloudflarev4.Client
|
||||
}
|
||||
|
||||
func (z zoneService) ZoneIDByName(zoneName string) (string, error) {
|
||||
@ -287,8 +291,9 @@ func NewCloudFlareProvider(
|
||||
) (*CloudFlareProvider, error) {
|
||||
// initialize via chosen auth method and returns new API object
|
||||
var (
|
||||
config *cloudflare.API
|
||||
err error
|
||||
config *cloudflare.API
|
||||
configV4 *cloudflarev4.Client
|
||||
err error
|
||||
)
|
||||
if os.Getenv("CF_API_TOKEN") != "" {
|
||||
token := os.Getenv("CF_API_TOKEN")
|
||||
@ -300,8 +305,15 @@ func NewCloudFlareProvider(
|
||||
token = strings.TrimSpace(string(tokenBytes))
|
||||
}
|
||||
config, err = cloudflare.NewWithAPIToken(token)
|
||||
configV4 = cloudflarev4.NewClient(
|
||||
option.WithAPIToken(token),
|
||||
)
|
||||
} else {
|
||||
config, err = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))
|
||||
configV4 = cloudflarev4.NewClient(
|
||||
option.WithAPIKey(os.Getenv("CF_API_KEY")),
|
||||
option.WithAPIEmail(os.Getenv("CF_API_EMAIL")),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize cloudflare provider: %w", err)
|
||||
@ -312,7 +324,7 @@ func NewCloudFlareProvider(
|
||||
}
|
||||
|
||||
return &CloudFlareProvider{
|
||||
Client: zoneService{config},
|
||||
Client: zoneService{config, configV4},
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
proxiedByDefault: proxiedByDefault,
|
||||
@ -646,12 +658,12 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
|
||||
return fmt.Errorf("failed to build desired regional hostnames: %w", err)
|
||||
}
|
||||
if len(desiredRegionalHostnames) > 0 {
|
||||
regionalHostnames, err := p.listDataLocalisationRegionalHostnames(ctx, resourceContainer)
|
||||
regionalHostnames, err := p.listDataLocalisationRegionalHostnames(ctx, zoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not fetch regional hostnames from zone, %w", err)
|
||||
}
|
||||
regionalHostnamesChanges := regionalHostnamesChanges(desiredRegionalHostnames, regionalHostnames)
|
||||
if !p.submitRegionalHostnameChanges(ctx, regionalHostnamesChanges, resourceContainer) {
|
||||
if !p.submitRegionalHostnameChanges(ctx, zoneID, regionalHostnamesChanges) {
|
||||
failedChange = true
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ import (
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
cloudflare "github.com/cloudflare/cloudflare-go"
|
||||
cloudflarev4 "github.com/cloudflare/cloudflare-go/v4"
|
||||
"github.com/cloudflare/cloudflare-go/v4/addressing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
@ -53,46 +55,62 @@ type regionalHostnameChange struct {
|
||||
regionalHostname
|
||||
}
|
||||
|
||||
func (z zoneService) ListDataLocalizationRegionalHostnames(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.ListDataLocalizationRegionalHostnamesParams) ([]cloudflare.RegionalHostname, error) {
|
||||
return z.service.ListDataLocalizationRegionalHostnames(ctx, rc, rp)
|
||||
func (z zoneService) ListDataLocalizationRegionalHostnames(ctx context.Context, params addressing.RegionalHostnameListParams) autoPager[addressing.RegionalHostnameListResponse] {
|
||||
return z.serviceV4.Addressing.RegionalHostnames.ListAutoPaging(ctx, params)
|
||||
}
|
||||
|
||||
func (z zoneService) CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error {
|
||||
_, err := z.service.CreateDataLocalizationRegionalHostname(ctx, rc, rp)
|
||||
func (z zoneService) CreateDataLocalizationRegionalHostname(ctx context.Context, params addressing.RegionalHostnameNewParams) error {
|
||||
_, err := z.serviceV4.Addressing.RegionalHostnames.New(ctx, params)
|
||||
return err
|
||||
}
|
||||
|
||||
func (z zoneService) UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error {
|
||||
_, err := z.service.UpdateDataLocalizationRegionalHostname(ctx, rc, rp)
|
||||
func (z zoneService) UpdateDataLocalizationRegionalHostname(ctx context.Context, hostname string, params addressing.RegionalHostnameEditParams) error {
|
||||
_, err := z.serviceV4.Addressing.RegionalHostnames.Edit(ctx, hostname, params)
|
||||
return err
|
||||
}
|
||||
|
||||
func (z zoneService) DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error {
|
||||
return z.service.DeleteDataLocalizationRegionalHostname(ctx, rc, hostname)
|
||||
func (z zoneService) DeleteDataLocalizationRegionalHostname(ctx context.Context, hostname string, params addressing.RegionalHostnameDeleteParams) error {
|
||||
_, err := z.serviceV4.Addressing.RegionalHostnames.Delete(ctx, hostname, params)
|
||||
return err
|
||||
}
|
||||
|
||||
// listDataLocalizationRegionalHostnamesParams is a function that returns the appropriate RegionalHostname List Param based on the zoneID
|
||||
func listDataLocalizationRegionalHostnamesParams(zoneID string) addressing.RegionalHostnameListParams {
|
||||
return addressing.RegionalHostnameListParams{
|
||||
ZoneID: cloudflarev4.F(zoneID),
|
||||
}
|
||||
}
|
||||
|
||||
// createDataLocalizationRegionalHostnameParams is a function that returns the appropriate RegionalHostname Param based on the cloudFlareChange passed in
|
||||
func createDataLocalizationRegionalHostnameParams(rhc regionalHostnameChange) cloudflare.CreateDataLocalizationRegionalHostnameParams {
|
||||
return cloudflare.CreateDataLocalizationRegionalHostnameParams{
|
||||
Hostname: rhc.hostname,
|
||||
RegionKey: rhc.regionKey,
|
||||
func createDataLocalizationRegionalHostnameParams(zoneID string, rhc regionalHostnameChange) addressing.RegionalHostnameNewParams {
|
||||
return addressing.RegionalHostnameNewParams{
|
||||
ZoneID: cloudflarev4.F(zoneID),
|
||||
Hostname: cloudflarev4.F(rhc.hostname),
|
||||
RegionKey: cloudflarev4.F(rhc.regionKey),
|
||||
}
|
||||
}
|
||||
|
||||
// updateDataLocalizationRegionalHostnameParams is a function that returns the appropriate RegionalHostname Param based on the cloudFlareChange passed in
|
||||
func updateDataLocalizationRegionalHostnameParams(rhc regionalHostnameChange) cloudflare.UpdateDataLocalizationRegionalHostnameParams {
|
||||
return cloudflare.UpdateDataLocalizationRegionalHostnameParams{
|
||||
Hostname: rhc.hostname,
|
||||
RegionKey: rhc.regionKey,
|
||||
func updateDataLocalizationRegionalHostnameParams(zoneID string, rhc regionalHostnameChange) addressing.RegionalHostnameEditParams {
|
||||
return addressing.RegionalHostnameEditParams{
|
||||
ZoneID: cloudflarev4.F(zoneID),
|
||||
RegionKey: cloudflarev4.F(rhc.regionKey),
|
||||
}
|
||||
}
|
||||
|
||||
// deleteDataLocalizationRegionalHostnameParams is a function that returns the appropriate RegionalHostname Param based on the cloudFlareChange passed in
|
||||
func deleteDataLocalizationRegionalHostnameParams(zoneID string, rhc regionalHostnameChange) addressing.RegionalHostnameDeleteParams {
|
||||
return addressing.RegionalHostnameDeleteParams{
|
||||
ZoneID: cloudflarev4.F(zoneID),
|
||||
}
|
||||
}
|
||||
|
||||
// submitRegionalHostnameChanges applies a set of regional hostname changes, returns false if at least one fails
|
||||
func (p *CloudFlareProvider) submitRegionalHostnameChanges(ctx context.Context, rhChanges []regionalHostnameChange, resourceContainer *cloudflare.ResourceContainer) bool {
|
||||
func (p *CloudFlareProvider) submitRegionalHostnameChanges(ctx context.Context, zoneID string, rhChanges []regionalHostnameChange) bool {
|
||||
failedChange := false
|
||||
|
||||
for _, rhChange := range rhChanges {
|
||||
if !p.submitRegionalHostnameChange(ctx, rhChange, resourceContainer) {
|
||||
if !p.submitRegionalHostnameChange(ctx, zoneID, rhChange) {
|
||||
failedChange = true
|
||||
}
|
||||
}
|
||||
@ -101,12 +119,12 @@ func (p *CloudFlareProvider) submitRegionalHostnameChanges(ctx context.Context,
|
||||
}
|
||||
|
||||
// submitRegionalHostnameChange applies a single regional hostname change, returns false if it fails
|
||||
func (p *CloudFlareProvider) submitRegionalHostnameChange(ctx context.Context, rhChange regionalHostnameChange, resourceContainer *cloudflare.ResourceContainer) bool {
|
||||
func (p *CloudFlareProvider) submitRegionalHostnameChange(ctx context.Context, zoneID string, rhChange regionalHostnameChange) bool {
|
||||
changeLog := log.WithFields(log.Fields{
|
||||
"hostname": rhChange.hostname,
|
||||
"region_key": rhChange.regionKey,
|
||||
"action": rhChange.action.String(),
|
||||
"zone": resourceContainer.Identifier,
|
||||
"zone": zoneID,
|
||||
})
|
||||
if p.DryRun {
|
||||
changeLog.Debug("Dry run: skipping regional hostname change", rhChange.action)
|
||||
@ -115,21 +133,22 @@ func (p *CloudFlareProvider) submitRegionalHostnameChange(ctx context.Context, r
|
||||
switch rhChange.action {
|
||||
case cloudFlareCreate:
|
||||
changeLog.Debug("Creating regional hostname")
|
||||
regionalHostnameParam := createDataLocalizationRegionalHostnameParams(rhChange)
|
||||
if err := p.Client.CreateDataLocalizationRegionalHostname(ctx, resourceContainer, regionalHostnameParam); err != nil {
|
||||
params := createDataLocalizationRegionalHostnameParams(zoneID, rhChange)
|
||||
if err := p.Client.CreateDataLocalizationRegionalHostname(ctx, params); err != nil {
|
||||
changeLog.Errorf("failed to create regional hostname: %v", err)
|
||||
return false
|
||||
}
|
||||
case cloudFlareUpdate:
|
||||
changeLog.Debug("Updating regional hostname")
|
||||
regionalHostnameParam := updateDataLocalizationRegionalHostnameParams(rhChange)
|
||||
if err := p.Client.UpdateDataLocalizationRegionalHostname(ctx, resourceContainer, regionalHostnameParam); err != nil {
|
||||
params := updateDataLocalizationRegionalHostnameParams(zoneID, rhChange)
|
||||
if err := p.Client.UpdateDataLocalizationRegionalHostname(ctx, rhChange.hostname, params); err != nil {
|
||||
changeLog.Errorf("failed to update regional hostname: %v", err)
|
||||
return false
|
||||
}
|
||||
case cloudFlareDelete:
|
||||
changeLog.Debug("Deleting regional hostname")
|
||||
if err := p.Client.DeleteDataLocalizationRegionalHostname(ctx, resourceContainer, rhChange.hostname); err != nil {
|
||||
params := deleteDataLocalizationRegionalHostnameParams(zoneID, rhChange)
|
||||
if err := p.Client.DeleteDataLocalizationRegionalHostname(ctx, rhChange.hostname, params); err != nil {
|
||||
changeLog.Errorf("failed to delete regional hostname: %v", err)
|
||||
return false
|
||||
}
|
||||
@ -137,18 +156,22 @@ func (p *CloudFlareProvider) submitRegionalHostnameChange(ctx context.Context, r
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *CloudFlareProvider) listDataLocalisationRegionalHostnames(ctx context.Context, resourceContainer *cloudflare.ResourceContainer) (regionalHostnamesMap, error) {
|
||||
rhs, err := p.Client.ListDataLocalizationRegionalHostnames(ctx, resourceContainer, cloudflare.ListDataLocalizationRegionalHostnamesParams{})
|
||||
if err != nil {
|
||||
return nil, convertCloudflareError(err)
|
||||
}
|
||||
// listDataLocalisationRegionalHostnames fetches the current regional hostnames for the given zone ID.
|
||||
//
|
||||
// It returns a map of hostnames to regional hostnames, or an error if the request fails.
|
||||
func (p *CloudFlareProvider) listDataLocalisationRegionalHostnames(ctx context.Context, zoneID string) (regionalHostnamesMap, error) {
|
||||
params := listDataLocalizationRegionalHostnamesParams(zoneID)
|
||||
iter := p.Client.ListDataLocalizationRegionalHostnames(ctx, params)
|
||||
rhsMap := make(regionalHostnamesMap)
|
||||
for _, rh := range rhs {
|
||||
for rh := range autoPagerIterator(iter) {
|
||||
rhsMap[rh.Hostname] = regionalHostname{
|
||||
hostname: rh.Hostname,
|
||||
regionKey: rh.RegionKey,
|
||||
}
|
||||
}
|
||||
if iter.Err() != nil {
|
||||
return nil, convertCloudflareError(iter.Err())
|
||||
}
|
||||
return rhsMap, nil
|
||||
}
|
||||
|
||||
@ -193,7 +216,7 @@ func (p *CloudFlareProvider) addEnpointsProviderSpecificRegionKeyProperty(ctx co
|
||||
return nil
|
||||
}
|
||||
|
||||
regionalHostnames, err := p.listDataLocalisationRegionalHostnames(ctx, cloudflare.ZoneIdentifier(zoneID))
|
||||
regionalHostnames, err := p.listDataLocalisationRegionalHostnames(ctx, zoneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"testing"
|
||||
|
||||
cloudflare "github.com/cloudflare/cloudflare-go"
|
||||
"github.com/cloudflare/cloudflare-go/v4/addressing"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -33,61 +34,64 @@ import (
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
)
|
||||
|
||||
func (m *mockCloudFlareClient) ListDataLocalizationRegionalHostnames(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.ListDataLocalizationRegionalHostnamesParams) ([]cloudflare.RegionalHostname, error) {
|
||||
if strings.Contains(rc.Identifier, "rherror") {
|
||||
return nil, fmt.Errorf("failed to list regional hostnames")
|
||||
func (m *mockCloudFlareClient) ListDataLocalizationRegionalHostnames(ctx context.Context, params addressing.RegionalHostnameListParams) autoPager[addressing.RegionalHostnameListResponse] {
|
||||
zoneID := params.ZoneID.Value
|
||||
if strings.Contains(zoneID, "rherror") {
|
||||
return &mockAutoPager[addressing.RegionalHostnameListResponse]{err: fmt.Errorf("failed to list regional hostnames")}
|
||||
}
|
||||
rhs := make([]cloudflare.RegionalHostname, 0, len(m.regionalHostnames[rc.Identifier]))
|
||||
for _, rh := range m.regionalHostnames[rc.Identifier] {
|
||||
rhs = append(rhs, cloudflare.RegionalHostname{
|
||||
results := make([]addressing.RegionalHostnameListResponse, 0, len(m.regionalHostnames[zoneID]))
|
||||
for _, rh := range m.regionalHostnames[zoneID] {
|
||||
results = append(results, addressing.RegionalHostnameListResponse{
|
||||
Hostname: rh.hostname,
|
||||
RegionKey: rh.regionKey,
|
||||
})
|
||||
}
|
||||
return rhs, nil
|
||||
return &mockAutoPager[addressing.RegionalHostnameListResponse]{
|
||||
items: results,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error {
|
||||
if strings.Contains(rp.Hostname, "rherror") {
|
||||
func (m *mockCloudFlareClient) CreateDataLocalizationRegionalHostname(ctx context.Context, params addressing.RegionalHostnameNewParams) error {
|
||||
if strings.Contains(params.Hostname.Value, "rherror") {
|
||||
return fmt.Errorf("failed to create regional hostname")
|
||||
}
|
||||
|
||||
m.Actions = append(m.Actions, MockAction{
|
||||
Name: "CreateDataLocalizationRegionalHostname",
|
||||
ZoneId: rc.Identifier,
|
||||
ZoneId: params.ZoneID.Value,
|
||||
RecordId: "",
|
||||
RegionalHostname: regionalHostname{
|
||||
hostname: rp.Hostname,
|
||||
regionKey: rp.RegionKey,
|
||||
hostname: params.Hostname.Value,
|
||||
regionKey: params.RegionKey.Value,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error {
|
||||
if strings.Contains(rp.Hostname, "rherror") {
|
||||
func (m *mockCloudFlareClient) UpdateDataLocalizationRegionalHostname(ctx context.Context, hostname string, params addressing.RegionalHostnameEditParams) error {
|
||||
if strings.Contains(hostname, "rherror") {
|
||||
return fmt.Errorf("failed to update regional hostname")
|
||||
}
|
||||
|
||||
m.Actions = append(m.Actions, MockAction{
|
||||
Name: "UpdateDataLocalizationRegionalHostname",
|
||||
ZoneId: rc.Identifier,
|
||||
ZoneId: params.ZoneID.Value,
|
||||
RecordId: "",
|
||||
RegionalHostname: regionalHostname{
|
||||
hostname: rp.Hostname,
|
||||
regionKey: rp.RegionKey,
|
||||
hostname: hostname,
|
||||
regionKey: params.RegionKey.Value,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error {
|
||||
func (m *mockCloudFlareClient) DeleteDataLocalizationRegionalHostname(ctx context.Context, hostname string, params addressing.RegionalHostnameDeleteParams) error {
|
||||
if strings.Contains(hostname, "rherror") {
|
||||
return fmt.Errorf("failed to delete regional hostname")
|
||||
}
|
||||
m.Actions = append(m.Actions, MockAction{
|
||||
Name: "DeleteDataLocalizationRegionalHostname",
|
||||
ZoneId: rc.Identifier,
|
||||
ZoneId: params.ZoneID.Value,
|
||||
RecordId: "",
|
||||
RegionalHostname: regionalHostname{
|
||||
hostname: hostname,
|
||||
|
34
provider/cloudflare/pagination.go
Normal file
34
provider/cloudflare/pagination.go
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cloudflare
|
||||
|
||||
type autoPager[T any] interface {
|
||||
Next() bool
|
||||
Current() T
|
||||
Err() error
|
||||
}
|
||||
|
||||
// autoPagerIterator returns an iterator over an autoPager.
|
||||
func autoPagerIterator[T any](iter autoPager[T]) func(yield func(T) bool) {
|
||||
return func(yield func(T) bool) {
|
||||
for iter.Next() {
|
||||
if !yield(iter.Current()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
94
provider/cloudflare/pagination_test.go
Normal file
94
provider/cloudflare/pagination_test.go
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockAutoPager[T any] struct {
|
||||
items []T
|
||||
index int
|
||||
err error
|
||||
errIndex int
|
||||
}
|
||||
|
||||
func (m *mockAutoPager[T]) Next() bool {
|
||||
m.index++
|
||||
return !m.hasError() && m.hasNext()
|
||||
}
|
||||
|
||||
func (m *mockAutoPager[T]) Current() T {
|
||||
if m.hasNext() && !m.hasError() {
|
||||
return m.items[m.index-1]
|
||||
}
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
func (m *mockAutoPager[T]) Err() error {
|
||||
return m.err
|
||||
}
|
||||
|
||||
func (m *mockAutoPager[T]) hasError() bool {
|
||||
return m.err != nil && m.errIndex <= m.index
|
||||
}
|
||||
|
||||
func (m *mockAutoPager[T]) hasNext() bool {
|
||||
return m.index > 0 && m.index <= len(m.items)
|
||||
}
|
||||
|
||||
func TestAutoPagerIterator(t *testing.T) {
|
||||
t.Run("iterate empty", func(t *testing.T) {
|
||||
pager := &mockAutoPager[string]{}
|
||||
iterator := autoPagerIterator(pager)
|
||||
collected := slices.Collect(iterator)
|
||||
assert.Empty(t, collected)
|
||||
})
|
||||
|
||||
t.Run("iterate all items", func(t *testing.T) {
|
||||
pager := &mockAutoPager[int]{items: []int{1, 2, 3, 4, 5}}
|
||||
iterator := autoPagerIterator(pager)
|
||||
collected := slices.Collect(iterator)
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5}, collected)
|
||||
})
|
||||
|
||||
t.Run("iterate with early termination", func(t *testing.T) {
|
||||
pager := &mockAutoPager[int]{items: []int{1, 2, 3, 4, 5}}
|
||||
iterator := autoPagerIterator(pager)
|
||||
var collected []int
|
||||
for item := range iterator {
|
||||
collected = append(collected, item)
|
||||
if item == 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Equal(t, []int{1, 2, 3}, collected)
|
||||
})
|
||||
|
||||
t.Run("iterate with error at index", func(t *testing.T) {
|
||||
expectedErr := errors.New("pager error")
|
||||
pager := &mockAutoPager[int]{items: []int{1, 2, 3, 4, 5}, err: expectedErr, errIndex: 3}
|
||||
iterator := autoPagerIterator(pager)
|
||||
collected := slices.Collect(iterator)
|
||||
assert.Equal(t, []int{1, 2}, collected)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user