Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PDNS as a Provider #373

Merged
merged 1 commit into from
Apr 16, 2018
Merged

Conversation

ffledgling
Copy link
Contributor

This is the first commit in what is likely going to be a bunch of commits. It adds pdns-go bindings to the vendor directory, that will let us the pdns provider to external-dns. (Issue #363 )

@k8s-ci-robot
Copy link
Contributor

Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please follow instructions at https://github.com/kubernetes/kubernetes/wiki/CLA-FAQ to sign the CLA.

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify. Thanks.


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/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot added cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Oct 30, 2017
@k8s-ci-robot k8s-ci-robot added 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 Oct 30, 2017
@k8s-ci-robot
Copy link
Contributor

Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please follow instructions at https://github.com/kubernetes/kubernetes/wiki/CLA-FAQ to sign the CLA.

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify. Thanks.


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/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot added cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. and removed cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Oct 30, 2017
@k8s-ci-robot k8s-ci-robot added 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 Oct 30, 2017
@njuettner
Copy link
Member

njuettner commented Nov 1, 2017

@ffledgling Thanks for your PR!
It would be nice if you could rebase without the vendor commit as soon as everything is in place, so we can review your changes without going to commits one by one.

@ffledgling
Copy link
Contributor Author

@njuettner Thanks for taking a look! This PR is in a bit of flux at the moment and I should've marked it as incomplete. I'll be updating this in the upcoming days, the vendoring, external package and a lot of other things have been changing very quickly.

When it's done I'll update either via a comment here or via Slack. Cheers!

@ffledgling ffledgling changed the title Add fflegling/pdns-go as dep Add PDNS as a Provider Nov 6, 2017
@ffledgling
Copy link
Contributor Author

I've updated this commit and it now implements the pdns provider fully (for the most part).

Some notes:

  • It relies on a swagger generated client which is currently under provider/internal/pdns-go. This addition is in a separate commit to make reviewing easier.
    Ideally this can be added as an external vendored dependency and managed with glide. But I ran into issues getting glide to work correctly on my end.
  • Once the code is reviewed and OK'd I can spend more time trying to deal with glide and dependency management to get the dependency moved out of the code and into glide/vendor.

@ffledgling ffledgling force-pushed the 363-pdns branch 4 times, most recently from ec0ecfb to 6f14638 Compare November 7, 2017 10:36
Copy link

@ideahitme ideahitme left a comment

Choose a reason for hiding this comment

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

in general looks okay, but tests are really needed to have major functions covered for provider/pdns.go file.

@@ -129,7 +133,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)

// Flags related to providers
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, dnsimple, infoblox, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "inmemory")
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, dnsimple, infoblox, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "inmemory", "pdns")

Choose a reason for hiding this comment

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

please also mention it in the options list (same line)

provider/pdns.go Outdated
defaultServerID = "localhost"
defaultTTL = 300

maxUInt32 = ^uint32(0)

Choose a reason for hiding this comment

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

this is already defined in SL: math.MaxUint32 and math.MaxInt32 respectively.

provider/pdns.go Outdated
// We do not support dry running, exit safely instead of surprising the user
// TODO: Add Dry Run support
if dryRun {
log.Fatalf("PDNS Provider does not currently support dry-run, stopping.")

Choose a reason for hiding this comment

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

why this can't be returned as an error instead. It would fatal in the main then, which I find slightly preferable

provider/pdns.go Outdated
}

if apikey == "" {
log.Warnf("API Key for PDNS is empty. Specify using --pdns-api-key=")

Choose a reason for hiding this comment

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

does it work without specifying apikey? if not then this should return an error

provider/pdns.go Outdated
log.Warnf("API Key for PDNS is empty. Specify using --pdns-api-key=")
}
if len(domainFilter.filters) == 0 {
log.Warnf("Domain Filter is not supported by PDNS. It will be ignored.")

Choose a reason for hiding this comment

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

would it be safer just to quit if domain filter is passed ?

provider/pdns.go Outdated
package provider

import (
//"strings"

Choose a reason for hiding this comment

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

this should really be removed

provider/pdns.go Outdated
endpoints = []*endpoint.Endpoint{}

for _, record := range rr.Records {
//func NewEndpointWithTTL(dnsName, target, recordType string, ttl TTL) *Endpoint

Choose a reason for hiding this comment

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

please remove commented code

provider/pdns.go Outdated
return err
}
for _, zone := range zonelist {
jso, _ := json.Marshal(zone)

Choose a reason for hiding this comment

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

the error here should be handled

provider/pdns.go Outdated
}
}
*/
mastermap := make(map[string]map[string]map[string][]*endpoint.Endpoint)

Choose a reason for hiding this comment

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

is it possible to refactor this code, so that we don't have triple nested maps ?

Copy link
Member

Choose a reason for hiding this comment

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

@ffledgling could you take a look on that again?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have a local copy of this patched, but this is blocked on mock testing which is exhibiting segfaults, I'm trying to still fix this. 👍

@ideahitme
Copy link

what is the status here ?

@ffledgling
Copy link
Contributor Author

ffledgling commented Dec 27, 2017

@ideahitme This has fallen by the wayside for a bit because some other stuff came up, but I'll try to clean it up and send it in by the second/third week of January.

I have addressed most of your concerns in the review, but I need to add tests, which requires a bit of refactoring.

@ideahitme
Copy link

@ffledgling sure, ping me when it is ready

@fxpester
Copy link

hi, I`m trying to build it, but no luck, can you check it ?

go version go1.8.5 linux/amd64
git clone -b 363-pdns https://github.com/ffledgling/external-dns/
cd external-dns
make

errors I get:

k8s.io/client-go/plugin/pkg/client/auth
_/root/external-dns
# _/root/external-dns
./main.go:95: not enough arguments in call to provider.NewAWSProvider
        have (provider.DomainFilter, provider.ZoneTypeFilter, bool)
        want (provider.DomainFilter, provider.ZoneIDFilter, provider.ZoneTypeFilter, bool)
./main.go:97: not enough arguments in call to provider.NewAzureProvider
        have (string, provider.DomainFilter, string, bool)
        want (string, provider.DomainFilter, provider.ZoneIDFilter, string, bool)
./main.go:99: not enough arguments in call to provider.NewCloudFlareProvider
        have (provider.DomainFilter, bool, bool)
        want (provider.DomainFilter, provider.ZoneIDFilter, bool, bool)
./main.go:101: not enough arguments in call to provider.NewGoogleProvider
        have (string, provider.DomainFilter, bool)
        want (string, provider.DomainFilter, provider.ZoneIDFilter, bool)
./main.go:105: not enough arguments in call to provider.NewDnsimpleProvider
        have (provider.DomainFilter, bool)
        want (provider.DomainFilter, provider.ZoneIDFilter, bool)
./main.go:122: undefined: provider.NewPDNSProvider
./main.go:122: cfg.PDNSServer undefined (type *externaldns.Config has no field or method PDNSServer)
./main.go:122: cfg.PDNSAPIKey undefined (type *externaldns.Config has no field or method PDNSAPIKey)
make: *** [build/external-dns] Error 2

@ffledgling
Copy link
Contributor Author

@fxpester That shouldn't happen, looks like something's out of sync. I'll take a look when I get in tomorrow.

@k8s-ci-robot
Copy link
Contributor

Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please follow instructions at https://git.k8s.io/community/CLA.md#the-contributor-license-agreement to sign the CLA.

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify. Thanks.


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/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot removed the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Jan 17, 2018
@k8s-ci-robot k8s-ci-robot added 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 Jan 18, 2018
@linki
Copy link
Member

linki commented Feb 12, 2018

Merged-in current master which uses dep now.

@ffledgling
Copy link
Contributor Author

@linki I noticed, I'll switch over to dep.

@ffledgling
Copy link
Contributor Author

ffledgling commented Feb 20, 2018

While I work on this, here's the binary with the pdns bits built in for those who wish to play around with it - https://pastebin.ffledgling.com/external-dns/ (Please do not use this in production).

@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Mar 22, 2018
@ffledgling
Copy link
Contributor Author

ffledgling commented Mar 22, 2018

@ideahitme @njuettner I think this is ready for review on my end, can you please take a look? I think I've addressed all prior concerns here.

Copy link
Contributor

@Raffo Raffo left a comment

Choose a reason for hiding this comment

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

I added some comments, mostly covering code comments and a bit of error handling.

provider/pdns.go Outdated
"context"
"encoding/json"
"errors"
//"fmt"
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: Can we get rid of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can do.

provider/pdns.go Outdated

// ConvertEndpointsToZones marshals endpoints into pdns compatible Zone structs
func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changetype pdnsChangeType) (zonelist []pgo.Zone, _ error) {
/* eg of mastermap
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a leftover for developing purpose? If not, I would move it to the comment of the method, otherwise just drop it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a stale comment from a previous implementation, I'll remove it.


sort.SliceStable(zones, func(i, j int) bool { return len(zones[i].Name) > len(zones[j].Name) })

// NOTE: Complexity of this loop is O(Zones*Endpoints).
Copy link
Contributor

Choose a reason for hiding this comment

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

Love that you mention the complexity here, might be useful in the future.

ep := endpoints[0]
dnsname := ensureTrailingDot(ep.DNSName)
if strings.HasSuffix(dnsname, zone.Name) {
// The assumption here is that there will only ever be one target
Copy link
Contributor

Choose a reason for hiding this comment

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

The PR is against master, so that shouldn't be a problem, am I right? If so, it is not a concern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, this is just here as a note in case it changes in the future and results in a breakage. It's an assumption, so I thought to document it.

provider/pdns.go Outdated
for _, zone := range zonelist {
jso, err := json.Marshal(zone)
if err != nil {
log.Debugf("JSON Marshal for zone struct failed!")
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should return the error if we fail to unmarshal. It's not so much of a common problem but I think that just logging is pretty dangerous. Also, the log is at debug level which means that it will pass completely unnoticed in case with a lower debug level.

provider/pdns.go Outdated
func (c *PDNSAPIClient) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error) {
resp, err := c.client.ZonesApi.PatchZone(c.authCtx, defaultServerID, zoneID, zoneStruct)
if err != nil {
log.Warnf("Unable to patch zone %s. %v", zoneStruct.Name, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it okay to only log this error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Raffo Would you prefer this be a hard fail instead? Or are you suggesting it should be logged at level info ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to return an error here, in the end we want to know that something went wrong. Also, I see that when we call p.mutateRecords we don't actually have any check for the error... is that intended and do I look at it right? If it is not intended, maybe a run of a linter would help to spot those cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason I left it as a warning because network blips might result in one-off failures of the request, so it might make sense to retry instead of hard failing the first time we see an error. An alternative of-course would be to add retry logic w/ back-off so that we hard fail if we see no response after say 3 tries.

Copy link
Contributor

Choose a reason for hiding this comment

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

please return errors and because api-sdks like aws do retries, you might want to do the retry inside the pdns provider.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This now retries thrice and returns an error otherwise.

provider/pdns.go Outdated
func (c *PDNSAPIClient) ListZones() ([]pgo.Zone, *http.Response, error) {
zones, resp, err := c.client.ZonesApi.ListZones(c.authCtx, defaultServerID)
if err != nil {
log.Warnf("Unable to fetch zones. %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it okay to only log this error?

provider/pdns.go Outdated
jso, err := json.Marshal(zone)
if err != nil {
log.Debugf("JSON Marshal for zone struct failed!")
} else {
Copy link
Member

Choose a reason for hiding this comment

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

does this has to be in a else clause?

Copy link
Member

Choose a reason for hiding this comment

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

as Raffo mentioned if you return the err

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So the problem is that the JSON marshal itself is not used for anything except debug logging itself, does it still make sense to return an error? We'd be throwing an error for something that failed in our own logging setup. I'm still fine with returning an error, just making sure that's the intention.

startTime := time.Now()

// Create
for _, change := range changes.Create {
Copy link
Member

Choose a reason for hiding this comment

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

I would check if debug is true before, otherwise it will loop for nothing everytime the method got called if you don't debug it. Same for Update and Delete, @ffledgling thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! I'll update code accordingly.

Copy link
Contributor

@szuecs szuecs Apr 16, 2018

Choose a reason for hiding this comment

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

changes.Create could also implement Stringer interface String() string and then plan.Changes has to know about debug yes/no.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ignoring this change for now because short-circuiting on debug will require a change in the PDNS provider creation struct, which will require a sizable refactor of the code and tests, which after discussion on IRC with @szuecs and @njuettner is more trouble than it's worth at the moment.

@njuettner
Copy link
Member

@ffledgling I've resolved the conflict in types.go, besides that could you take a look again what Raffo was mentioning to you?

I think after that we're happy to merge it. Thanks again!

@njuettner
Copy link
Member

@ffledgling did you had time to take a look? I think there's only a minor part missing here.

Commit adds:
* Implementation of PowerDNS as a provider
* Tests for said implementation
* github.com/ffledgling/pdns-go, which provides go client bindings for
  PowerDNS's HTTP API, as a dependency
* "pdns" as an additional option for the `--provider` flag
* `--pdns-server` and `--pdns-api-key` as additional flags for PowerDNS
  specific configuration
@njuettner
Copy link
Member

👍

@njuettner njuettner merged commit b4a8c14 into kubernetes-sigs:master Apr 16, 2018
@tuapuikia
Copy link

Is it possible to include basic auth authentication?

@ffledgling
Copy link
Contributor Author

@tuapuikia what do you mean by basic auth authentication? The provider already supports a password protected PDNS API instance. Did you have something else in mind?

@tuapuikia
Copy link

@ffledgling the current provider only support x-auth token. But library support basic auth to authenticate api behind proxy.

@ffledgling
Copy link
Contributor Author

@tuapuikia Do you mind opening a new issue with examples of what you want?

lou-lan pushed a commit to lou-lan/external-dns that referenced this pull request May 11, 2022
When pushing individual tags, specifying the repos is mandatory.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. new-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.

9 participants