diff --git a/OWNERS b/OWNERS index 81bb3bfaa..aa364ecf8 100644 --- a/OWNERS +++ b/OWNERS @@ -20,3 +20,4 @@ providers/ns1 @captncraig # providers/softlayer providers/vultr @geek1011 providers/ovh @masterzen +providers/powerdns @jpbede diff --git a/README.md b/README.md index c037e69ee..030a27a9e 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Currently supported DNS providers: - OVH - OctoDNS - OpenSRS + - PowerDNS - SoftLayer - Vultr diff --git a/docs/_providers/powerdns.md b/docs/_providers/powerdns.md new file mode 100644 index 000000000..031acc757 --- /dev/null +++ b/docs/_providers/powerdns.md @@ -0,0 +1,57 @@ +--- +name: PowerDNS +title: PowerDNS Provider +layout: default +jsId: POWERDNS +--- +# PowerDNS Provider + +## Configuration +In your credentials file, you must provide your [API URL, API Key and Server ID](https://doc.powerdns.com/authoritative/http-api/index.html). + +In most cases the Server id is `localhost` + +{% highlight json %} +{ + "powerdns": { + "apiurl": "http://localhost", + "apikey": "your-key", + "servername": "localhost" + } +} +{% endhighlight %} + +## Metadata +Following metadata are available: + +{% highlight js %} +{ + 'default_ns': [ + 'a.example.com.', + 'b.example.com.' + ], + 'dnssec_on_create': false +} +{% endhighlight %} + +- `default_ns` sets the nameserver which are used +- `dnssec_on_create` specifies if DNSSEC should be enabled when creating zones + +## Usage +Example Javascript: + +{% highlight js %} +var REG_NONE = NewRegistrar('none', 'NONE') +var POWERDNS = NewDnsProvider("powerdns", "POWERDNS"); + +D("example.tld", REG_NONE, DnsProvider(POWERDNS), + A("test","1.2.3.4") +); +{%endhighlight%} + +## Activation +See the [PowerDNS documentation](https://doc.powerdns.com/authoritative/http-api/index.html) how the API can be enabled. + +## Caveats +Currently it is only possible to enable DNSSec while creating a new zone. +On-demand activation/deactivation of DNSSec will be added later. \ No newline at end of file diff --git a/docs/provider-list.md b/docs/provider-list.md index 52b4ce75e..00eecdf10 100644 --- a/docs/provider-list.md +++ b/docs/provider-list.md @@ -87,6 +87,7 @@ Maintainers of contributed providers: * `OCTODNS` @TomOnTime * `OPENSRS` @pierre-emmanuelJ * `OVH` @masterzen +* `POWERDNS` @jpbede * `SOFTLAYER`@jamielennox * `VULTR` @geek1011 diff --git a/go.mod b/go.mod index a25bcb2f4..854d05f61 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/jarcoal/httpmock v1.0.4 // indirect github.com/malexdev/utfutil v0.0.0-20180510171754-00c8d4a8e7a8 // indirect github.com/miekg/dns v1.1.29 + github.com/mittwald/go-powerdns v0.4.0 github.com/mjibson/esc v0.2.0 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 diff --git a/go.sum b/go.sum index e9f141b2e..aa9d8e5e4 100644 --- a/go.sum +++ b/go.sum @@ -167,6 +167,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/jquery v0.0.0-20191017083323-73f4c7416038 h1:/gx6joY4PjXUu6mKM4yx7yj9Ti6yP8ljOxY/Qt0J25g= github.com/gopherjs/jquery v0.0.0-20191017083323-73f4c7416038/go.mod h1:xKR3tvLne+vYYPH9d4DM8X9MKlNV2yXDEomxulcK218= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -234,10 +236,14 @@ github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUb github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mittwald/go-powerdns v0.4.0 h1:vEl2+4JINusy5NF8weObVRCuvHv8wqNBVMPZSQWq9zo= +github.com/mittwald/go-powerdns v0.4.0/go.mod h1:bI/sZBAWyTViDknOTp19VfDxVEnh1U7rWPx2aRKtlzg= github.com/mjibson/esc v0.2.0 h1:k96hdaR9Z+nMcnDwNrOvhdBqtjyMrbVyxLpsRCdP2mA= github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c= github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ= @@ -531,6 +537,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM= +gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ns1/ns1-go.v2 v2.0.0-20170502175150-c563826f4cbe h1:fuu3vZ8C6O8mk8Ich8YfkDv/Zpnx1HUotQk8JocBcSw= diff --git a/integrationTest/providers.json b/integrationTest/providers.json index 97e05a6f6..346279440 100644 --- a/integrationTest/providers.json +++ b/integrationTest/providers.json @@ -104,6 +104,11 @@ "consumer-key": "$OVH_CONSUMER_KEY", "domain": "$OVH_DOMAIN" }, + "POWERDNS": { + "apikey": "$POWERDNS_APIKEY", + "apiurl": "$POWERDNS_APIURL", + "servername": "$POWERDNS_SERVERNAME" + }, "ROUTE53": { "KeyId": "$R53_KEY_ID", "SecretKey": "$R53_KEY", diff --git a/providers/_all/all.go b/providers/_all/all.go index 5259d41a6..8ee3fbcf8 100644 --- a/providers/_all/all.go +++ b/providers/_all/all.go @@ -25,6 +25,7 @@ import ( _ "github.com/StackExchange/dnscontrol/v3/providers/octodns" _ "github.com/StackExchange/dnscontrol/v3/providers/opensrs" _ "github.com/StackExchange/dnscontrol/v3/providers/ovh" + _ "github.com/StackExchange/dnscontrol/v3/providers/powerdns" _ "github.com/StackExchange/dnscontrol/v3/providers/route53" _ "github.com/StackExchange/dnscontrol/v3/providers/softlayer" _ "github.com/StackExchange/dnscontrol/v3/providers/vultr" diff --git a/providers/powerdns/powerdnsProvider.go b/providers/powerdns/powerdnsProvider.go new file mode 100644 index 000000000..fe9977b9e --- /dev/null +++ b/providers/powerdns/powerdnsProvider.go @@ -0,0 +1,244 @@ +package powerdns + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/StackExchange/dnscontrol/v3/models" + "github.com/StackExchange/dnscontrol/v3/pkg/diff" + "github.com/StackExchange/dnscontrol/v3/providers" + "github.com/miekg/dns/dnsutil" + "github.com/mittwald/go-powerdns" + "github.com/mittwald/go-powerdns/apis/zones" + "strings" +) + +var features = providers.DocumentationNotes{ + providers.CanUseAlias: providers.Can(), + providers.CanUseCAA: providers.Can(), + providers.CanUsePTR: providers.Can(), + providers.CanUseSRV: providers.Can(), + providers.CanUseTLSA: providers.Can(), + providers.CanUseSSHFP: providers.Can(), + providers.CanAutoDNSSEC: providers.Unimplemented("Need support in library first"), + providers.DocCreateDomains: providers.Can(), + providers.DocOfficiallySupported: providers.Cannot(), + providers.CanGetZones: providers.Can(), +} + +func init() { + providers.RegisterDomainServiceProviderType("POWERDNS", NewProvider, features) +} + +// PowerDNS represents the PowerDNS DNSServiceProvider. +type PowerDNS struct { + client pdns.Client + APIKey string + APIUrl string + ServerName string + DefaultNS []string `json:"default_ns"` + DNSSecOnCreate bool `json:"dnssec_on_create"` + + nameservers []*models.Nameserver +} + +// NewProvider initializes a PowerDNS DNSServiceProvider. +func NewProvider(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) { + api := &PowerDNS{} + + api.APIKey = m["apiKey"] + if api.APIKey == "" { + return nil, fmt.Errorf("PowerDNS API Key is required") + } + + api.APIUrl = m["apiUrl"] + if api.APIUrl == "" { + return nil, fmt.Errorf("PowerDNS API URL is required") + } + + api.ServerName = m["serverName"] + if api.ServerName == "" { + return nil, fmt.Errorf("PowerDNS server name is required") + } + + // load js config + if len(metadata) != 0 { + err := json.Unmarshal(metadata, api) + if err != nil { + return nil, err + } + } + var nss []string + for _, ns := range api.DefaultNS { + nss = append(nss, ns[0:len(ns)-1]) + } + var err error + api.nameservers, err = models.ToNameservers(nss) + if err != nil { + return api, err + } + + var clientErr error + api.client, clientErr = pdns.New( + pdns.WithBaseURL(api.APIUrl), + pdns.WithAPIKeyAuthentication(api.APIKey), + ) + return api, clientErr +} + +// GetNameservers returns the nameservers for a domain. +func (api *PowerDNS) GetNameservers(string) ([]*models.Nameserver, error) { + var r []string + for _, j := range api.nameservers { + r = append(r, j.Name) + } + return models.ToNameservers(r) +} + +// ListZones returns all the zones in an account +func (api *PowerDNS) ListZones() ([]string, error) { + var result []string + zones, err := api.client.Zones().ListZones(context.Background(), api.ServerName) + if err != nil { + return result, err + } + for _, zone := range zones { + result = append(result, zone.Name) + } + return result, nil +} + +// GetZoneRecords gets the records of a zone and returns them in RecordConfig format. +func (api *PowerDNS) GetZoneRecords(domain string) (models.Records, error) { + zone, err := api.client.Zones().GetZone(context.Background(), api.ServerName, domain) + if err != nil { + return nil, err + } + + curRecords := models.Records{} + // loop over grouped records by type, called RRSet + for _, rrset := range zone.ResourceRecordSets { + if rrset.Type == "SOA" { + continue + } + // loop over single records of this group and create records + for _, pdnsRecord := range rrset.Records { + r, err := toRecordConfig(domain, pdnsRecord, rrset.TTL, rrset.Name, rrset.Type) + if err != nil { + return nil, err + } + curRecords = append(curRecords, r) + } + } + + return curRecords, nil +} + +// GetDomainCorrections returns a list of corrections to update a domain. +func (api *PowerDNS) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { + var corrections []*models.Correction + + // record corrections + curRecords, err := api.GetZoneRecords(dc.Name) + if err != nil { + return nil, err + } + + // post-process records + dc.Punycode() + models.PostProcessRecords(curRecords) + + // create record diff by group + keysToUpdate := (diff.New(dc)).ChangedGroups(curRecords) + desiredRecords := dc.Records.GroupedByKey() + + // create corrections by group + for label, msgs := range keysToUpdate { + labelName := label.NameFQDN + "." + labelType := label.Type + + if _, ok := desiredRecords[label]; !ok { + // nothing found, must be a delete + corrections = append(corrections, &models.Correction{ + Msg: strings.Join(msgs, "\n "), + F: func() error { + return api.client.Zones().RemoveRecordSetFromZone(context.Background(), api.ServerName, dc.Name, labelName, labelType) + }, + }) + } else { + ttl := desiredRecords[label][0].TTL + records := []zones.Record{} + for _, recordContent := range desiredRecords[label] { + records = append(records, zones.Record{ + Content: recordContent.GetTargetCombined(), + }) + } + corrections = append(corrections, &models.Correction{ + Msg: strings.Join(msgs, "\n "), + F: func() error { + return api.client.Zones().AddRecordSetToZone(context.Background(), api.ServerName, dc.Name, zones.ResourceRecordSet{ + Name: labelName, + Type: labelType, + TTL: int(ttl), + Records: records, + }) + }, + }) + } + } + + // DNSSec corrections + // TODO: Implementing of on-demand DNSSec creation in library + + return corrections, nil +} + +// EnsureDomainExists adds a domain to the DNS service if it does not exist +func (api *PowerDNS) EnsureDomainExists(domain string) error { + if zone, _ := api.client.Zones().GetZone(context.Background(), api.ServerName, domain); zone.ID != "" { + return nil + } + + _, err := api.client.Zones().CreateZone(context.Background(), api.ServerName, zones.Zone{ + Name: domain, + Type: zones.ZoneTypeZone, + DNSSec: api.DNSSecOnCreate, + Nameservers: api.DefaultNS, + }) + return err +} + +// toRecordConfig converts a PowerDNS DNSRecord to a RecordConfig. #rtype_variations +func toRecordConfig(domain string, r zones.Record, ttl int, name string, rtype string) (*models.RecordConfig, error) { + // trimming trailing dot and domain from name + name = strings.TrimSuffix(name, domain+".") + name = strings.TrimSuffix(name, ".") + + rc := &models.RecordConfig{ + TTL: uint32(ttl), + Original: r, + Type: rtype, + } + rc.SetLabel(name, domain) + + content := r.Content + switch rtype { + case "CNAME", "NS": + return rc, rc.SetTarget(dnsutil.AddOrigin(content, domain)) + case "CAA": + return rc, rc.SetTargetCAAString(content) + case "MX": + return rc, rc.SetTargetMXString(content) + case "SRV": + return rc, rc.SetTargetSRVString(content) + case "TXT": + // Remove quotes if it is a TXT record. + if !strings.HasPrefix(content, `"`) || !strings.HasSuffix(content, `"`) { + return nil, errors.New("Unexpected lack of quotes in TXT record from PowerDNS") + } + return rc, rc.SetTargetTXT(content[1 : len(content)-1]) + default: + return rc, rc.PopulateFromString(rtype, content, domain) + } +} diff --git a/vendor/github.com/mittwald/go-powerdns/.gitignore b/vendor/github.com/mittwald/go-powerdns/.gitignore new file mode 100644 index 000000000..757fee31c --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/.gitignore @@ -0,0 +1 @@ +/.idea \ No newline at end of file diff --git a/vendor/github.com/mittwald/go-powerdns/.travis.yml b/vendor/github.com/mittwald/go-powerdns/.travis.yml new file mode 100644 index 000000000..1d1eef094 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/.travis.yml @@ -0,0 +1,14 @@ +language: go +go: + - 1.12 + - 1.11 + +install: [] + +services: + - docker + +script: + - GO111MODULE=on go vet + - GO111MODULE=on go test ./... + - GO111MODULE=on go build \ No newline at end of file diff --git a/vendor/github.com/mittwald/go-powerdns/LICENSE b/vendor/github.com/mittwald/go-powerdns/LICENSE new file mode 100644 index 000000000..1b22bef9c --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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. \ No newline at end of file diff --git a/vendor/github.com/mittwald/go-powerdns/README.md b/vendor/github.com/mittwald/go-powerdns/README.md new file mode 100644 index 000000000..167b19bcb --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/README.md @@ -0,0 +1,77 @@ +# PowerDNS client library for Go + +[![GoDoc](https://godoc.org/github.com/mittwald/go-powerdns?status.svg)](https://godoc.org/github.com/mittwald/go-powerdns) +[![Build Status](https://travis-ci.org/mittwald/go-powerdns.svg?branch=master)](https://travis-ci.org/mittwald/go-powerdns) +[![Maintainability](https://api.codeclimate.com/v1/badges/aa54a869f5ff56477a2a/maintainability)](https://codeclimate.com/github/mittwald/go-powerdns/maintainability) + +This package contains a Go library for accessing the [PowerDNS][powerdns] Authoritative API. + +## Supported features + +- [x] Servers +- [x] Zones +- [ ] Cryptokeys +- [ ] Metadata +- [ ] TSIG Keys +- [x] Searching +- [ ] Statistics +- [x] Cache + +## Installation + +Install using `go get`: + +```console +> go get github.com/mittwald/go-powerdns +``` + +## Usage + +First, instantiate a client using `pdns.New`: + +```go +client, err := pdns.New( + pdns.WithBaseURL("http://localhost:8081"), + pdns.WithAPIKeyAuthentication("supersecret"), +) +``` + +The client then offers more specialiced sub-clients, for example for managing server and zones. +Have a look at this library's [documentation][godoc] for more information. + +## Complete example + +```go +package main + +import "context" +import "github.com/mittwald/go-powerdns" +import "github.com/mittwald/go-powerdns/apis/zones" + +func main() { + client, err := pdns.New( + pdns.WithBaseURL("http://localhost:8081"), + pdns.WithAPIKeyAuthentication("supersecret"), + ) + + if err != nil { + panic(err) + } + + client.Zones().CreateZone(context.Background(), "localhost", zones.Zone{ + Name: "mydomain.example.", + Type: zones.ZoneTypeZone, + Kind: zones.ZoneKindNative, + Nameservers: []string{ + "ns1.example.com.", + "ns2.example.com.", + }, + ResourceRecordSets: []zones.ResourceRecordSet{ + {Name: "foo.mydomain.example.", Type: "A", TTL: 60, Records: []zones.Record{{Content: "127.0.0.1"}}}, + }, + }) +} +``` + +[powerdns]: https://github.com/PowerDNS/pdns +[godoc]: https://godoc.org/github.com/mittwald/go-powerdns diff --git a/vendor/github.com/mittwald/go-powerdns/apis/cache/client.go b/vendor/github.com/mittwald/go-powerdns/apis/cache/client.go new file mode 100644 index 000000000..ef6d8d9b5 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/cache/client.go @@ -0,0 +1,14 @@ +package cache + +import "github.com/mittwald/go-powerdns/pdnshttp" + +type client struct { + httpClient *pdnshttp.Client +} + +// New creates a new Cache client +func New(hc *pdnshttp.Client) Client { + return &client{ + httpClient: hc, + } +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/cache/doc.go b/vendor/github.com/mittwald/go-powerdns/apis/cache/doc.go new file mode 100644 index 000000000..440d5d1e0 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/cache/doc.go @@ -0,0 +1,6 @@ +// Package cache contains a specialized client for interacting with PowerDNS' "Cache" API. +// +// More information +// +// Official API documentation: https://doc.powerdns.com/authoritative/http-api/cache.html +package cache diff --git a/vendor/github.com/mittwald/go-powerdns/apis/cache/flush.go b/vendor/github.com/mittwald/go-powerdns/apis/cache/flush.go new file mode 100644 index 000000000..c26413dac --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/cache/flush.go @@ -0,0 +1,21 @@ +package cache + +import ( + "context" + "fmt" + "net/url" + + "github.com/mittwald/go-powerdns/pdnshttp" +) + +func (c *client) Flush(ctx context.Context, serverID string, name string) (*FlushResult, error) { + cfr := FlushResult{} + path := fmt.Sprintf("/api/v1/servers/%s/cache/flush", url.PathEscape(serverID)) + + err := c.httpClient.Put(ctx, path, &cfr, pdnshttp.WithQueryValue("domain", name)) + if err != nil { + return nil, err + } + + return &cfr, nil +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/cache/interface.go b/vendor/github.com/mittwald/go-powerdns/apis/cache/interface.go new file mode 100644 index 000000000..a17dca6e7 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/cache/interface.go @@ -0,0 +1,9 @@ +package cache + +import "context" + +// Client defines the interface for Cache operations. +type Client interface { + // Flush flush a cache-entry by name + Flush(ctx context.Context, serverID string, name string) (*FlushResult, error) +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/cache/types.go b/vendor/github.com/mittwald/go-powerdns/apis/cache/types.go new file mode 100644 index 000000000..92a780fde --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/cache/types.go @@ -0,0 +1,7 @@ +package cache + +// FlushResult represent the result of a cache-flush. +type FlushResult struct { + Count int `json:"count"` + Result string `json:"result"` +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/search/client.go b/vendor/github.com/mittwald/go-powerdns/apis/search/client.go new file mode 100644 index 000000000..6b30b9fc9 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/search/client.go @@ -0,0 +1,14 @@ +package search + +import "github.com/mittwald/go-powerdns/pdnshttp" + +type client struct { + httpClient *pdnshttp.Client +} + +// New creates a new Search client +func New(hc *pdnshttp.Client) Client { + return &client{ + httpClient: hc, + } +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/search/interface.go b/vendor/github.com/mittwald/go-powerdns/apis/search/interface.go new file mode 100644 index 000000000..4d60bd398 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/search/interface.go @@ -0,0 +1,10 @@ +package search + +import "context" + +// Client defines method for interacting with the PowerDNS "Search" endpoints +type Client interface { + + // ListServers lists all known servers + Search(ctx context.Context, serverID, query string, max int, objectType ObjectType) (ResultList, error) +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/search/search.go b/vendor/github.com/mittwald/go-powerdns/apis/search/search.go new file mode 100644 index 000000000..e83b2b871 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/search/search.go @@ -0,0 +1,28 @@ +package search + +import ( + "context" + "fmt" + "github.com/mittwald/go-powerdns/pdnshttp" + "net/url" +) + +func (c *client) Search(ctx context.Context, serverID, query string, max int, objectType ObjectType) (ResultList, error) { + path := fmt.Sprintf("/api/v1/servers/%s/search-data", url.PathEscape(serverID)) + results := make(ResultList, 0) + + err := c.httpClient.Get( + ctx, + path, + &results, + pdnshttp.WithQueryValue("q", query), + pdnshttp.WithQueryValue("max", fmt.Sprintf("%d", max)), + pdnshttp.WithQueryValue("object_type", objectType.String()), + ) + + if err != nil { + return nil, err + } + + return results, nil +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/search/types.go b/vendor/github.com/mittwald/go-powerdns/apis/search/types.go new file mode 100644 index 000000000..3efad64f8 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/search/types.go @@ -0,0 +1,63 @@ +package search + +import "fmt" + +// ObjectType represents the object type for which a search should be performed +type ObjectType int + +// Possible object types; according to the PowerDNS documentation, this list is +// exhaustive. +const ( + _ = iota + ObjectTypeAll ObjectType = iota + ObjectTypeZone + ObjectTypeRecord + ObjectTypeComment +) + +// String makes this type implement fmt.Stringer +func (t ObjectType) String() string { + switch t { + case ObjectTypeAll: + return "all" + case ObjectTypeZone: + return "zone" + case ObjectTypeRecord: + return "record" + case ObjectTypeComment: + return "comment" + } + + return "" +} + +// UnmarshalJSON makes this type implement json.Unmarshaler +func (t *ObjectType) UnmarshalJSON(b []byte) error { + switch string(b) { + case `"all"`: + *t = ObjectTypeAll + case `"zone"`: + *t = ObjectTypeZone + case `"record"`: + *t = ObjectTypeRecord + case `"comment"`: + *t = ObjectTypeComment + default: + return fmt.Errorf(`unknown search type: %s'`, string(b)) + } + + return nil +} + +// Result represents a single search result. See the documentation for more +// information: https://doc.powerdns.com/authoritative/http-api/search.html#searchresult +type Result struct { + Content string `json:"content"` + Disabled bool `json:"disabled"` + Name string `json:"name"` + ObjectType ObjectType `json:"object_type"` + ZoneID string `json:"zone_id"` + Zone string `json:"zone"` + Type string `json:"type"` + TTL int `json:"ttl"` +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/search/types_resultlist.go b/vendor/github.com/mittwald/go-powerdns/apis/search/types_resultlist.go new file mode 100644 index 000000000..acaec9733 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/search/types_resultlist.go @@ -0,0 +1,37 @@ +package search + +// ResultList represents a list of search results. The type itself offers some +// advanced filtering functions for convenience. +type ResultList []Result + +// FilterByObjectType returns all elements of a result list that are of a given +// object type. +func (l ResultList) FilterByObjectType(t ObjectType) ResultList { + return l.FilterBy(func(r *Result) bool { + return r.ObjectType == t + }) +} + +// FilterByRecordType returns all elements of a result list that are a resource +// record and have a certain record type. +func (l ResultList) FilterByRecordType(t string) ResultList { + return l.FilterBy(func(r *Result) bool { + return r.ObjectType == ObjectTypeRecord && r.Type == t + }) +} + +// FilterBy returns all elements of a result list that match a generic matcher +// function. The "matcher" function will be invoked for each element in the +// result list; if it returns true, the respective item will be included in the +// result list. +func (l ResultList) FilterBy(matcher func(*Result) bool) ResultList { + out := make(ResultList, 0, len(l)) + + for i := range l { + if matcher(&l[i]) { + out = append(out, l[i]) + } + } + + return out +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/servers/client.go b/vendor/github.com/mittwald/go-powerdns/apis/servers/client.go new file mode 100644 index 000000000..828e98182 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/servers/client.go @@ -0,0 +1,13 @@ +package servers + +import "github.com/mittwald/go-powerdns/pdnshttp" + +type client struct { + httpClient *pdnshttp.Client +} + +func New(hc *pdnshttp.Client) Client { + return &client{ + httpClient: hc, + } +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/servers/doc.go b/vendor/github.com/mittwald/go-powerdns/apis/servers/doc.go new file mode 100644 index 000000000..c41c3c96f --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/servers/doc.go @@ -0,0 +1,6 @@ +// This package contains a specialized client for interacting with PowerDNS' "Servers" API. +// +// More information +// +// Official API documentation: https://doc.powerdns.com/authoritative/http-api/server.html +package servers diff --git a/vendor/github.com/mittwald/go-powerdns/apis/servers/interface.go b/vendor/github.com/mittwald/go-powerdns/apis/servers/interface.go new file mode 100644 index 000000000..6fd51fd4e --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/servers/interface.go @@ -0,0 +1,14 @@ +package servers + +import "context" + +// Client defines method for interacting with the PowerDNS "Servers" endpoints +type Client interface { + + // ListServers lists all known servers + ListServers(ctx context.Context) ([]Server, error) + + // GetServer returns a specific server. If the server with the given "serverID" does + // not exist, the error return value will contain a pdnshttp.ErrNotFound error (see example) + GetServer(ctx context.Context, serverID string) (*Server, error) +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/servers/servers_get.go b/vendor/github.com/mittwald/go-powerdns/apis/servers/servers_get.go new file mode 100644 index 000000000..c513c17ba --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/servers/servers_get.go @@ -0,0 +1,18 @@ +package servers + +import ( + "context" + "fmt" + "net/url" +) + +func (c *client) GetServer(ctx context.Context, serverID string) (*Server, error) { + server := Server{} + err := c.httpClient.Get(ctx, fmt.Sprintf("/api/v1/servers/%s", url.PathEscape(serverID)), &server) + + if err != nil { + return nil, err + } + + return &server, err +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/servers/servers_list.go b/vendor/github.com/mittwald/go-powerdns/apis/servers/servers_list.go new file mode 100644 index 000000000..c06e0acd0 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/servers/servers_list.go @@ -0,0 +1,14 @@ +package servers + +import "context" + +func (c *client) ListServers(ctx context.Context) ([]Server, error) { + servers := make([]Server, 0) + + err := c.httpClient.Get(ctx, "/api/v1/servers", &servers) + if err != nil { + return nil, err + } + + return servers, nil +} diff --git a/vendor/github.com/mittwald/go-powerdns/apis/servers/types.go b/vendor/github.com/mittwald/go-powerdns/apis/servers/types.go new file mode 100644 index 000000000..d072e9492 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/apis/servers/types.go @@ -0,0 +1,14 @@ +package servers + +// Server models a PowerDNS server. +// +// More information: https://doc.powerdns.com/authoritative/http-api/server.html#server +type Server struct { + ID string `json:"id"` + Type string `json:"type"` + DaemonType string `json:"daemon_type"` + Version string `json:"version"` + URL string `json:"url,omitempty"` + ConfigURL string `json:"config_url,omitempty"` + ZonesURL string `json:"zones_url,omitempty"` +} diff --git a/vendor/github.com/mittwald/go-powerdns/client.go b/vendor/github.com/mittwald/go-powerdns/client.go new file mode 100644 index 000000000..671b62388 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/client.go @@ -0,0 +1,129 @@ +package pdns + +import ( + "context" + "errors" + "io" + "io/ioutil" + "net/http" + "time" + + "github.com/mittwald/go-powerdns/apis/cache" + "github.com/mittwald/go-powerdns/apis/search" + "github.com/mittwald/go-powerdns/apis/servers" + "github.com/mittwald/go-powerdns/apis/zones" + "github.com/mittwald/go-powerdns/pdnshttp" +) + +type client struct { + baseURL string + httpClient *http.Client + authenticator pdnshttp.ClientAuthenticator + debugOutput io.Writer + + cache cache.Client + search search.Client + servers servers.Client + zones zones.Client +} + +type ClientOption func(c *client) error + +// New creates a new PowerDNS client. Various client options can be used to configure +// the PowerDNS client (see examples). +func New(opt ...ClientOption) (Client, error) { + c := client{ + baseURL: "http://localhost:8081", + httpClient: http.DefaultClient, + debugOutput: ioutil.Discard, + authenticator: &pdnshttp.NoopAuthenticator{}, + } + + for i := range opt { + if err := opt[i](&c); err != nil { + return nil, err + } + } + + if c.authenticator != nil { + err := c.authenticator.OnConnect(c.httpClient) + if err != nil { + return nil, err + } + } + + hc := pdnshttp.NewClient(c.baseURL, c.httpClient, c.authenticator, c.debugOutput) + + c.servers = servers.New(hc) + c.zones = zones.New(hc) + c.search = search.New(hc) + c.cache = cache.New(hc) + + return &c, nil +} + +func (c *client) Status() error { + req, err := http.NewRequest("GET", c.baseURL, nil) + if err != nil { + return err + } + + if err := c.authenticator.OnRequest(req); err != nil { + return err + } + + _, err = c.httpClient.Do(req) + if err != nil { + return err + } + + return nil +} + +func (c *client) WaitUntilUp(ctx context.Context) error { + up := make(chan error) + cancel := false + + go func() { + for !cancel { + req, err := http.NewRequest("GET", c.baseURL, nil) + if err != nil { + time.Sleep(1 * time.Second) + continue + } + + _, err = c.httpClient.Do(req) + if err != nil { + time.Sleep(1 * time.Second) + continue + } + + up <- nil + return + } + }() + + select { + case <-up: + return nil + case <-ctx.Done(): + cancel = true + return errors.New("context exceeded") + } +} + +func (c *client) Servers() servers.Client { + return c.servers +} + +func (c *client) Zones() zones.Client { + return c.zones +} + +func (c *client) Search() search.Client { + return c.search +} + +func (c *client) Cache() cache.Client { + return c.cache +} diff --git a/vendor/github.com/mittwald/go-powerdns/doc.go b/vendor/github.com/mittwald/go-powerdns/doc.go new file mode 100644 index 000000000..6fc43e838 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/doc.go @@ -0,0 +1,2 @@ +// This package contains a client library used for connecting to the PowerDNS API. +package pdns diff --git a/vendor/github.com/mittwald/go-powerdns/docker-compose.yml b/vendor/github.com/mittwald/go-powerdns/docker-compose.yml new file mode 100644 index 000000000..8952ac01c --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3" +services: + mysql: + image: mariadb:10.1 + environment: + MYSQL_ROOT_PASSWORD: supersecret + MYSQL_USER: powerdns + MYSQL_PASSWORD: secret + MYSQL_DATABASE: powerdns + volumes: + - database:/var/lib/mysql + networks: + - backend + powerdns: + image: psitrax/powerdns + environment: + MYSQL_HOST: mysql + MYSQL_USER: powerdns + MYSQL_PASS: secret + MYSQL_DB: powerdns + networks: + - backend + volumes: + - ./.docker/pdns:/etc/pdns/conf.d + ports: + - 8081:8081 +volumes: + database: {} +networks: + backend: {} \ No newline at end of file diff --git a/vendor/github.com/mittwald/go-powerdns/go.mod b/vendor/github.com/mittwald/go-powerdns/go.mod new file mode 100644 index 000000000..39563ab0d --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/go.mod @@ -0,0 +1,6 @@ +module github.com/mittwald/go-powerdns + +require ( + github.com/stretchr/testify v1.3.0 + gopkg.in/h2non/gock.v1 v1.0.14 +) diff --git a/vendor/github.com/mittwald/go-powerdns/go.sum b/vendor/github.com/mittwald/go-powerdns/go.sum new file mode 100644 index 000000000..91929e84a --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM= +gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= diff --git a/vendor/github.com/mittwald/go-powerdns/interface.go b/vendor/github.com/mittwald/go-powerdns/interface.go new file mode 100644 index 000000000..f3a78f0f3 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/interface.go @@ -0,0 +1,36 @@ +package pdns + +import ( + "context" + + "github.com/mittwald/go-powerdns/apis/cache" + "github.com/mittwald/go-powerdns/apis/search" + "github.com/mittwald/go-powerdns/apis/servers" + "github.com/mittwald/go-powerdns/apis/zones" +) + +// Client is the root-level interface for interacting with the PowerDNS API. +// You can instantiate an implementation of this interface using the "New" function. +type Client interface { + + // Status checks if the PowerDNS API is reachable. This does a simple HTTP connection check; + // it will NOT check if your authentication is set up correctly (except you're using TLS client + // authentication. + Status() error + + // WaitUntilUp will block until the PowerDNS API accepts HTTP requests. You can use the "ctx" + // parameter to make this method wait only for (or until) a certain time (see examples). + WaitUntilUp(ctx context.Context) error + + // Servers returns a specialized API for interacting with PowerDNS servers + Servers() servers.Client + + // Zones returns a specialized API for interacting with PowerDNS zones + Zones() zones.Client + + // Search returns a specialized API for searching + Search() search.Client + + // Cache returns a specialized API for caching + Cache() cache.Client +} diff --git a/vendor/github.com/mittwald/go-powerdns/options.go b/vendor/github.com/mittwald/go-powerdns/options.go new file mode 100644 index 000000000..9dc6c02c4 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/options.go @@ -0,0 +1,78 @@ +package pdns + +import ( + "crypto/tls" + "crypto/x509" + "github.com/mittwald/go-powerdns/pdnshttp" + "io" + "io/ioutil" + "net/http" +) + +// WithBaseURL sets a client's base URL +func WithBaseURL(baseURL string) ClientOption { + return func(c *client) error { + c.baseURL = baseURL + return nil + } +} + +// WithHTTPClient can be used to override a client's HTTP client. +// Otherwise, the default HTTP client will be used +func WithHTTPClient(httpClient *http.Client) ClientOption { + return func(c *client) error { + c.httpClient = httpClient + return nil + } +} + +// WithAPIKeyAuthentication adds API-key based authentication to the PowerDNS client. +// In effect, each HTTP request will have an additional header that contains the API key +// supplied to this function: +// X-API-Key: {{ key }} +func WithAPIKeyAuthentication(key string) ClientOption { + return func(c *client) error { + c.authenticator = &pdnshttp.APIKeyAuthenticator{ + APIKey: key, + } + + return nil + } +} + +// WithTLSAuthentication configures TLS-based authentication for the PowerDNS client. +// This is not a feature that is provided by PowerDNS natively, but might be implemented +// when the PowerDNS API is run behind a reverse proxy. +func WithTLSAuthentication(caFile string, clientCertFile string, clientKeyFile string) ClientOption { + return func(c *client) error { + cert, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile) + if err != nil { + return err + } + + caBytes, err := ioutil.ReadFile(caFile) + if err != nil { + return err + } + + ca, err := x509.ParseCertificates(caBytes) + + auth := pdnshttp.TLSClientCertificateAuthenticator{ + ClientCert: cert, + ClientKey: cert.PrivateKey, + CACerts: ca, + } + + c.authenticator = &auth + return nil + } +} + +// WithDebuggingOutput can be used to supply an io.Writer to the client into which all +// outgoing HTTP requests and their responses will be logged. Useful for debugging. +func WithDebuggingOutput(out io.Writer) ClientOption { + return func(c *client) error { + c.debugOutput = out + return nil + } +} diff --git a/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth.go b/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth.go new file mode 100644 index 000000000..6d4450cd3 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth.go @@ -0,0 +1,24 @@ +package pdnshttp + +import ( + "net/http" +) + +type ClientAuthenticator interface { + OnRequest(*http.Request) error + OnConnect(*http.Client) error +} + +// NoopAuthenticator provides an "empty" implementation of the +// ClientAuthenticator interface. +type NoopAuthenticator struct{} + +// OnRequest is applied each time a HTTP request is built. +func (NoopAuthenticator) OnRequest(*http.Request) error { + return nil +} + +// OnConnect is applied on the entire connection as soon as it is set up. +func (NoopAuthenticator) OnConnect(*http.Client) error { + return nil +} diff --git a/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth_key.go b/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth_key.go new file mode 100644 index 000000000..54bc01b97 --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth_key.go @@ -0,0 +1,16 @@ +package pdnshttp + +import "net/http" + +type APIKeyAuthenticator struct { + APIKey string +} + +func (a *APIKeyAuthenticator) OnRequest(r *http.Request) error { + r.Header.Set("X-API-Key", a.APIKey) + return nil +} + +func (a *APIKeyAuthenticator) OnConnect(*http.Client) error { + return nil +} diff --git a/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth_tls.go b/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth_tls.go new file mode 100644 index 000000000..49f191e9f --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/pdnshttp/auth_tls.go @@ -0,0 +1,55 @@ +package pdnshttp + +import ( + "crypto" + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" +) + +type TLSClientCertificateAuthenticator struct { + CACerts []*x509.Certificate + ClientCert tls.Certificate + ClientKey crypto.PrivateKey +} + +func (a *TLSClientCertificateAuthenticator) OnRequest(r *http.Request) error { + return nil +} + +func (a *TLSClientCertificateAuthenticator) OnConnect(c *http.Client) error { + if c.Transport == nil { + c.Transport = http.DefaultTransport + } + + t, ok := c.Transport.(*http.Transport) + if !ok { + return fmt.Errorf("client.Transport is no *http.Transport, instead %t", c.Transport) + } + + if t.TLSClientConfig == nil { + t.TLSClientConfig = &tls.Config{} + } + + if t.TLSClientConfig.Certificates == nil { + t.TLSClientConfig.Certificates = make([]tls.Certificate, 0, 1) + } + + t.TLSClientConfig.Certificates = append(t.TLSClientConfig.Certificates, a.ClientCert) + + if t.TLSClientConfig.RootCAs == nil { + systemPool, err := x509.SystemCertPool() + if err != nil { + return err + } + + t.TLSClientConfig.RootCAs = systemPool + } + + for i := range a.CACerts { + t.TLSClientConfig.RootCAs.AddCert(a.CACerts[i]) + } + + return nil +} diff --git a/vendor/github.com/mittwald/go-powerdns/pdnshttp/client.go b/vendor/github.com/mittwald/go-powerdns/pdnshttp/client.go new file mode 100644 index 000000000..443854d5f --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/pdnshttp/client.go @@ -0,0 +1,119 @@ +package pdnshttp + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httputil" + "strings" +) + +type Client struct { + baseURL string + httpClient *http.Client + authenticator ClientAuthenticator + debugOutput io.Writer +} + +// NewClient returns a new PowerDNS HTTP client +func NewClient(baseURL string, hc *http.Client, auth ClientAuthenticator, debugOutput io.Writer) *Client { + c := Client{ + baseURL: baseURL, + httpClient: hc, + authenticator: auth, + debugOutput: debugOutput, + } + + return &c +} + +// NewRequest builds a new request. Usually, this method should not be used; +// prefer using the "Get", "Post", ... methods if possible. +func (c *Client) NewRequest(method string, path string, body io.Reader) (*http.Request, error) { + path = strings.TrimPrefix(path, "/") + req, err := http.NewRequest(method, c.baseURL+"/"+path, body) + if err != nil { + return nil, err + } + + if c.authenticator != nil { + if err := c.authenticator.OnRequest(req); err != nil { + return nil, err + } + } + + return req, err +} + +// Get executes a GET request +func (c *Client) Get(ctx context.Context, path string, out interface{}, opts ...RequestOption) error { + return c.doRequest(ctx, http.MethodGet, path, out, opts...) +} + +// Post executes a POST request +func (c *Client) Post(ctx context.Context, path string, out interface{}, opts ...RequestOption) error { + return c.doRequest(ctx, http.MethodPost, path, out, opts...) +} + +// Put executes a PUT request +func (c *Client) Put(ctx context.Context, path string, out interface{}, opts ...RequestOption) error { + return c.doRequest(ctx, http.MethodPut, path, out, opts...) +} + +// Patch executes a PATCH request +func (c *Client) Patch(ctx context.Context, path string, out interface{}, opts ...RequestOption) error { + return c.doRequest(ctx, http.MethodPatch, path, out, opts...) +} + +// Delete executes a DELETE request +func (c *Client) Delete(ctx context.Context, path string, out interface{}, opts ...RequestOption) error { + return c.doRequest(ctx, http.MethodDelete, path, out, opts...) +} + +func (c *Client) doRequest(ctx context.Context, method string, path string, out interface{}, opts ...RequestOption) error { + req, err := c.NewRequest(method, path, nil) + if err != nil { + return err + } + + for i := range opts { + if err := opts[i](req); err != nil { + return err + } + } + + req = req.WithContext(ctx) + + reqDump, _ := httputil.DumpRequestOut(req, true) + c.debugOutput.Write(reqDump) + + res, err := c.httpClient.Do(req) + if err != nil { + return err + } + + resDump, _ := httputil.DumpResponse(res, true) + c.debugOutput.Write(resDump) + + if res.StatusCode == http.StatusNotFound { + return ErrNotFound{URL: req.URL.String()} + } else if res.StatusCode >= 400 { + return ErrUnexpectedStatus{URL: req.URL.String(), StatusCode: res.StatusCode} + } + + if out != nil { + if w, ok := out.(io.Writer); ok { + _, err := io.Copy(w, res.Body) + return err + } + + dec := json.NewDecoder(res.Body) + err = dec.Decode(out) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/mittwald/go-powerdns/pdnshttp/errors.go b/vendor/github.com/mittwald/go-powerdns/pdnshttp/errors.go new file mode 100644 index 000000000..64e4c7b0e --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/pdnshttp/errors.go @@ -0,0 +1,31 @@ +package pdnshttp + +import "fmt" + +type ErrNotFound struct { + URL string +} + +func (e ErrNotFound) Error() string { + return fmt.Sprintf("not found: %s", e.URL) +} + +type ErrUnexpectedStatus struct { + URL string + StatusCode int +} + +func (e ErrUnexpectedStatus) Error() string { + return fmt.Sprintf("unexpected status code %d: %s", e.StatusCode, e.URL) +} + +func IsNotFound(err error) bool { + switch err.(type) { + case ErrNotFound: + return true + case *ErrNotFound: + return true + } + + return false +} diff --git a/vendor/github.com/mittwald/go-powerdns/pdnshttp/req_opt.go b/vendor/github.com/mittwald/go-powerdns/pdnshttp/req_opt.go new file mode 100644 index 000000000..90e1dea4a --- /dev/null +++ b/vendor/github.com/mittwald/go-powerdns/pdnshttp/req_opt.go @@ -0,0 +1,58 @@ +package pdnshttp + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" +) + +// RequestOption is a special type of function that can be passed to most HTTP +// request functions in this package; it is used to modify an HTTP request and +// to implement special request logic. +type RequestOption func(*http.Request) error + +// WithJSONRequestBody adds a JSON body to a request. The input type can be +// anything, as long as it can be marshaled by "json.Marshal". This method will +// also automatically set the correct content type and content-length. +func WithJSONRequestBody(in interface{}) RequestOption { + return func(req *http.Request) error { + if in == nil { + return nil + } + + buf := bytes.Buffer{} + enc := json.NewEncoder(&buf) + err := enc.Encode(in) + + if err != nil { + return err + } + + rc := ioutil.NopCloser(&buf) + + copyBuf := buf.Bytes() + + req.Body = rc + req.Header.Set("Content-Type", "application/json") + req.ContentLength = int64(buf.Len()) + req.GetBody = func() (io.ReadCloser, error) { + r := bytes.NewReader(copyBuf) + return ioutil.NopCloser(r), nil + } + + return nil + } +} + +// WithQueryValue adds a query parameter to a request's URL. +func WithQueryValue(key, value string) RequestOption { + return func(req *http.Request) error { + q := req.URL.Query() + q.Set(key, value) + + req.URL.RawQuery = q.Encode() + return nil + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e8725e1ec..b7e7a5529 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -211,6 +211,14 @@ github.com/miekg/dns/dnsutil github.com/mitchellh/go-homedir # github.com/mitchellh/mapstructure v1.1.2 github.com/mitchellh/mapstructure +# github.com/mittwald/go-powerdns v0.4.0 +## explicit +github.com/mittwald/go-powerdns +github.com/mittwald/go-powerdns/apis/cache +github.com/mittwald/go-powerdns/apis/search +github.com/mittwald/go-powerdns/apis/servers +github.com/mittwald/go-powerdns/apis/zones +github.com/mittwald/go-powerdns/pdnshttp # github.com/mjibson/esc v0.2.0 ## explicit github.com/mjibson/esc/embed