1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

Release 3.0.0 Candidate (#699)

Final changes before V3.0.0 release

* Remove old Gandi.  Fixes #575
* Many cleanups
* go mod tidy && go mod vendor

* integration_test.go: Output subtest name

* Cleanups

* integration_test.go: Description should include sub-test name
* Add a whitespace test to js/parse_tests/017-txt.js

* Cloudflare strips whitespace from end of TXT

* Fixes https://github.com/StackExchange/dnscontrol/issues/700

* Whitespace at end of TXT records

Name.com strips the whitespace from the end of a TXT record. There's
nothing we can do other than file a bug.

* Fixes https://github.com/StackExchange/dnscontrol/issues/701
This commit is contained in:
Tom Limoncelli
2020-03-22 13:38:37 -04:00
committed by GitHub
parent 5db21dcdec
commit 1b5935d1af
74 changed files with 153 additions and 4469 deletions

2
OWNERS
View File

@ -5,7 +5,7 @@ providers/bind @tlimoncelli
providers/cloudns @pragmaton
providers/digitalocean @Deraen
providers/dnsimple @aeden
providers/gandi @TomOnTime
providers/gandi_v5 @TomOnTime
# providers/gcloud
providers/hexonet @papakai
providers/internetbs @pragmaton

View File

@ -62,14 +62,14 @@ jobs:
DO_DOMAIN: $(DO_DOMAIN)
DO_TOKEN: $(DO_TOKEN)
- job: Gandi
- job: GandiV5
steps:
- template: go-env.yaml
- script: go test -v -verbose -provider GANDI
- script: go test -v -verbose -provider GANDI_V5
workingDirectory: $(wd)
env:
GANDI_KEY: $(GANDI_KEY)
GANDI_DOMAIN: $(GANDI_DOMAIN)
GANDI_KEY: $(GANDI_V5_APIKEY)
GANDI_DOMAIN: $(GANDI_V5_DOMAIN)
# - job: GandiLive
# steps:

View File

@ -14,8 +14,6 @@
<th class="rotate"><div><span>DIGITALOCEAN</span></div></th>
<th class="rotate"><div><span>DNSIMPLE</span></div></th>
<th class="rotate"><div><span>EXOSCALE</span></div></th>
<th class="rotate"><div><span>GANDI</span></div></th>
<th class="rotate"><div><span>GANDI-LIVEDNS</span></div></th>
<th class="rotate"><div><span>GANDI_V5</span></div></th>
<th class="rotate"><div><span>GCLOUD</span></div></th>
<th class="rotate"><div><span>HEXONET</span></div></th>

View File

@ -1,58 +0,0 @@
---
name: Gandi
title: Gandi Provider
layout: default
jsId: GANDI
---
# Gandi Provider
There are two providers for Gandi:
1. `GANDI` uses the v3 API and is able to act as a registrar provider
and a DNS provider. It is not able to handle domains that have
migrated to the new LiveDNS API. You need to get the API key from
the [v4 interface][].
2. `GANDI-LIVEDNS` uses the LiveDNS API and is only able to act as a
DNS provider. You need to get the API key from the [v5 interface][].
[v4 interface]: https://v4.gandi.net
[v5 interface]: https://v5.gandi.net
## Configuration
In your credentials file you must provide your Gandi.net API key:
{% highlight json %}
{
"gandi": {
"apikey": "your-gandi-key"
}
}
{% endhighlight %}
## Metadata
This provider does not recognize any special metadata fields unique to Gandi.
## Usage
Example Javascript:
{% highlight js %}
var GANDI = NewDnsProvider("gandi", "GANDI");
var REG_GANDI = NewRegistrar("gandi", "GANDI");
D("example.tld", REG_GANDI, DnsProvider(GANDI),
A("test","1.2.3.4")
);
{% endhighlight %}
## New domains
If a domain does not exist in your Gandi account, DNSControl will *not* automatically add it with the `create-domains` command. You'll need to do that via the control panel manually.
## Common errors
This is the error we see when someone uses GANDI instead of GANDI-LIVEDNS.
```
Error getting corrections: error: "Error on object : OBJECT_ZONE (CAUSE_NOTFOUND) [no such zone (id: 0)]" code: 581042
```

View File

@ -2,7 +2,7 @@
name: Gandi_v5
title: Gandi_v5 Provider
layout: default
jsId: GANDI
jsId: GANDI_V5
---
# Gandi_v5 Provider

View File

@ -125,9 +125,9 @@ jq:
jq < creds.json
FYI: `creds.json` fields can be an environment variable. The field must begin with a `$` followed by the variable name. No other text. For example:
FYI: `creds.json` fields can be read from an environment variable. The field must begin with a `$` followed by the variable name. No other text. For example:
"apiuser": "$GANDI_APIUSER",
"apikey": "$GANDI_V5_APIKEY",
## 5. Test the sample files.

View File

@ -76,8 +76,6 @@ Maintainers of contributed providers:
* `DNSIMPLE` @aeden
* `EXOSCALE` @pierre-emmanuelJ
* `GANDI_V5` @TomOnTime
* `GANDI-LIVEDNS` (going away in 3.0)
* `GANDI` (going away in 3.0)
* `HEXONET` @papakai
* `INTERNETBS` @pragmaton
* `LINODE` @koesie10

3
go.mod
View File

@ -25,20 +25,17 @@ require (
github.com/google/go-cmp v0.4.0 // indirect
github.com/google/go-github v17.0.0+incompatible
github.com/google/go-querystring v1.0.1-0.20190318165438-c8c88dbee036 // indirect
github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f
github.com/gopherjs/jquery v0.0.0-20191017083323-73f4c7416038
github.com/hashicorp/vault/api v1.0.4
github.com/hexonet/go-sdk v2.2.3+incompatible
github.com/jarcoal/httpmock v1.0.4 // indirect
github.com/jmespath/go-jmespath v0.0.0-20200310193758-2437e8417af5 // indirect
github.com/kolo/xmlrpc v0.0.0-20150413191830-0826b98aaa29 // indirect
github.com/malexdev/utfutil v0.0.0-20180510171754-00c8d4a8e7a8 // indirect
github.com/miekg/dns v1.1.27
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
github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d
github.com/prasmussen/gandi-api v0.0.0-20180224132202-58d3d4205661
github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
github.com/sergi/go-diff v1.1.0 // indirect

6
go.sum
View File

@ -112,8 +112,6 @@ github.com/google/go-querystring v1.0.1-0.20190318165438-c8c88dbee036/go.mod h1:
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f h1:XXzyYlFbxK3kWfcmu3Wc+Tv8/QQl/VqwsWuSYF1Rj0s=
github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@ -166,8 +164,6 @@ github.com/jmespath/go-jmespath v0.0.0-20200310193758-2437e8417af5/go.mod h1:9Qt
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kolo/xmlrpc v0.0.0-20150413191830-0826b98aaa29 h1:A9Cxbd2chMbJwYR6WpWelyovi0v8Pv4xTUc64pO1gxE=
github.com/kolo/xmlrpc v0.0.0-20150413191830-0826b98aaa29/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -206,8 +202,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prasmussen/gandi-api v0.0.0-20180224132202-58d3d4205661 h1:Frjzb2MZatv22HHLQ6BVv2rFBnYxITOk/tePom2DY0o=
github.com/prasmussen/gandi-api v0.0.0-20180224132202-58d3d4205661/go.mod h1:zZBfqqLFWgViJUcsGfFksd9RK9pDUVPkYDiWE/uIHy4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o=
github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU=

View File

@ -139,7 +139,7 @@ func testPermitted(t *testing.T, p string, f TestGroup) error {
func makeChanges(t *testing.T, prv providers.DNSServiceProvider, dc *models.DomainConfig, tst *TestCase, desc string, expectChanges bool, origConfig map[string]string) bool {
domainName := dc.Name
return t.Run(desc, func(t *testing.T) {
return t.Run(desc+":"+tst.Desc, func(t *testing.T) {
dom, _ := dc.Copy()
for _, r := range tst.Records {
rc := models.RecordConfig(*r)
@ -631,12 +631,18 @@ func makeTests(t *testing.T) []*TestGroup {
tc("Change a TXT", txt("foo", "changed")),
clear(),
tc("Create a TXT with spaces", txt("foo", "with spaces")),
tc("Change a TXT with spaces", txt("foo", "with whitespace ")),
tc("Create 1 TXT as array", txtmulti("foo", []string{"simple"})),
clear(),
tc("Create a 255-byte TXT", txt("foo", strings.Repeat("A", 255))),
),
testgroup("ws TXT",
not("CLOUDFLAREAPI", "NAMEDOTCOM"),
// These providers strip whitespace at the end of TXT records.
// TODO(tal): Add a check for this in normalize/validate.go
tc("Change a TXT with ws at end", txt("foo", "with space at end ")),
),
testgroup("empty TXT", not("DNSIMPLE", "CLOUDFLAREAPI"),
tc("TXT with empty str", txt("foo1", "")),
// https://github.com/StackExchange/dnscontrol/issues/598
@ -676,10 +682,10 @@ func makeTests(t *testing.T) []*TestGroup {
testgroup("page size",
// Tests the paging code of providers. Many providers page at 100.
// Notes:
// - gandi: page size is 100, therefore we test with 99, 100, and 101
// - ns1: free acct only allows 50 records, therefore we skip
// - digitalocean: fails due to rate limiting, not page limits.
not("NS1"),
// - Gandi: page size is 100, therefore we test with 99, 100, and 101
// - NS1: free acct only allows 50 records, therefore we skip
// - DigitalOcean: fails due to rate limiting, not page limits.
not("NS1", "DIGITALOCEAN"),
tc("99 records", manyA("rec%04d", "1.2.3.4", 99)...),
tc("100 records", manyA("rec%04d", "1.2.3.4", 100)...),
tc("101 records", manyA("rec%04d", "1.2.3.4", 101)...),
@ -745,7 +751,7 @@ func makeTests(t *testing.T) []*TestGroup {
tc("Change Weight", srv("_sip._tcp", 52, 62, 7, "foo.com."), srv("_sip._tcp", 15, 65, 75, "foo4.com.")),
tc("Change Port", srv("_sip._tcp", 52, 62, 72, "foo.com."), srv("_sip._tcp", 15, 65, 75, "foo4.com.")),
),
testgroup("SRV w/ null target", not("NAMEDOTCOM", "HEXONET", "EXOSCALE"),
testgroup("SRV w/ null target", not("EXOSCALE", "HEXONET", "NAMEDOTCOM"),
tc("Null Target", srv("_sip._tcp", 52, 62, 72, "foo.com."), srv("_sip._tcp", 15, 65, 75, ".")),
),

View File

@ -33,7 +33,6 @@
"token": "$DO_TOKEN"
},
"DNSIMPLE": {
"COMMENT": "20-22: no ns records managable. Not even for subdomains.",
"baseurl": "https://api.sandbox.dnsimple.com",
"domain": "$DNSIMPLE_DOMAIN",
"token": "$DNSIMPLE_TOKEN"
@ -44,16 +43,6 @@
"domain": "$EXOSCALE_DOMAIN",
"secretkey": "$EXOSCALE_SECRET_KEY"
},
"GANDI": {
"COMMENT": "5: gandi does not accept TTLs less than 300",
"apikey": "$GANDI_KEY",
"domain": "$GANDI_DOMAIN"
},
"GANDI-LIVEDNS": {
"COMMENT": "5: gandi does not accept TTLs less than 300",
"apikey": "$GANDILIVE_KEY",
"domain": "$GANDILIVE_DOMAIN"
},
"GANDI_V5": {
"apikey": "$GANDI_V5_APIKEY",
"domain": "$GANDI_V5_DOMAIN"
@ -74,7 +63,6 @@
"ipaddress": "$HEXONET_IP"
},
"LINODE": {
"COMMENT": "25: Linode's hostname validation does not allow the target domain TLD",
"domain": "$LINODE_DOMAIN",
"token": "$LINODE_TOKEN"
},
@ -109,7 +97,6 @@
"domain": "$R53_DOMAIN"
},
"SOFTLAYER": {
"COMMENT": "22-25 softlayer fails at direct internationalization, puncode works though",
"api_key": "$SL_API_KEY",
"domain": "$SL_DOMAIN",
"username": "$SL_USERNAME"

View File

@ -0,0 +1,112 @@
--- PASS: TestDNSProviders/example.com/0:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/1:_Create_an_A_record (0.00s)
--- PASS: TestDNSProviders/example.com/2:_Change_it (0.00s)
--- PASS: TestDNSProviders/example.com/3:_Add_another (0.00s)
--- PASS: TestDNSProviders/example.com/4:_Add_another(same_name) (0.00s)
--- PASS: TestDNSProviders/example.com/5:_Change_a_ttl (0.00s)
--- PASS: TestDNSProviders/example.com/6:_Change_single_target_from_set (0.00s)
--- PASS: TestDNSProviders/example.com/7:_Change_all_ttls (0.00s)
--- PASS: TestDNSProviders/example.com/8:_Delete_one (0.00s)
--- PASS: TestDNSProviders/example.com/9:_Add_back_and_change_ttl (0.00s)
--- PASS: TestDNSProviders/example.com/10:_Change_targets_and_ttls (0.00s)
--- PASS: TestDNSProviders/example.com/11:_Create_wildcard (0.00s)
--- PASS: TestDNSProviders/example.com/12:_Delete_wildcard (0.00s)
--- PASS: TestDNSProviders/example.com/13:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/14:_Create_a_CNAME (0.00s)
--- PASS: TestDNSProviders/example.com/15:_Change_it (0.00s)
--- PASS: TestDNSProviders/example.com/16:_Change_to_A_record (0.00s)
--- PASS: TestDNSProviders/example.com/17:_Change_back_to_CNAME (0.00s)
--- PASS: TestDNSProviders/example.com/18:_Record_pointing_to_@ (0.00s)
--- PASS: TestDNSProviders/example.com/19:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/20:_NS_for_subdomain (0.00s)
--- PASS: TestDNSProviders/example.com/21:_Dual_NS_for_subdomain (0.00s)
--- PASS: TestDNSProviders/example.com/22:_NS_Record_pointing_to_@ (0.00s)
--- PASS: TestDNSProviders/example.com/23:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/24:_Internationalized_name (0.00s)
--- PASS: TestDNSProviders/example.com/25:_Change_IDN (0.00s)
--- PASS: TestDNSProviders/example.com/26:_Internationalized_CNAME_Target (0.00s)
--- PASS: TestDNSProviders/example.com/27:_IDN_CNAME_AND_Target (0.00s)
--- PASS: TestDNSProviders/example.com/28:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/29:_MX_record (0.00s)
--- PASS: TestDNSProviders/example.com/30:_Second_MX_record,_same_prio (0.00s)
--- PASS: TestDNSProviders/example.com/31:_3_MX (0.00s)
--- PASS: TestDNSProviders/example.com/32:_Delete_one (0.00s)
--- PASS: TestDNSProviders/example.com/33:_Change_to_other_name (0.00s)
--- PASS: TestDNSProviders/example.com/34:_Change_Preference (0.00s)
--- PASS: TestDNSProviders/example.com/35:_Record_pointing_to_@ (0.00s)
--- PASS: TestDNSProviders/example.com/36:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/37:_Create_PTR_record (0.00s)
--- PASS: TestDNSProviders/example.com/38:_Modify_PTR_record (0.00s)
--- PASS: TestDNSProviders/example.com/39:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/40:_NAPTR_record (0.00s)
--- PASS: TestDNSProviders/example.com/41:_NAPTR_second_record (0.00s)
--- PASS: TestDNSProviders/example.com/42:_NAPTR_delete_record (0.00s)
--- PASS: TestDNSProviders/example.com/43:_NAPTR_change_target (0.00s)
--- PASS: TestDNSProviders/example.com/44:_NAPTR_change_order (0.00s)
--- PASS: TestDNSProviders/example.com/45:_NAPTR_change_preference (0.00s)
--- PASS: TestDNSProviders/example.com/46:_NAPTR_change_flags (0.00s)
--- PASS: TestDNSProviders/example.com/47:_NAPTR_change_service (0.00s)
--- PASS: TestDNSProviders/example.com/48:_NAPTR_change_regexp (0.00s)
--- PASS: TestDNSProviders/example.com/49:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/50:_SRV_record (0.00s)
--- PASS: TestDNSProviders/example.com/51:_Second_SRV_record,_same_prio (0.00s)
--- PASS: TestDNSProviders/example.com/52:_3_SRV (0.00s)
--- PASS: TestDNSProviders/example.com/53:_Delete_one (0.00s)
--- PASS: TestDNSProviders/example.com/54:_Change_Target (0.00s)
--- PASS: TestDNSProviders/example.com/55:_Change_Priority (0.00s)
--- PASS: TestDNSProviders/example.com/56:_Change_Weight (0.00s)
--- PASS: TestDNSProviders/example.com/57:_Change_Port (0.00s)
--- PASS: TestDNSProviders/example.com/58:_Null_Target (0.00s)
--- PASS: TestDNSProviders/example.com/59:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/60:_SSHFP_record (0.00s)
--- PASS: TestDNSProviders/example.com/61:_SSHFP_change_algorithm (0.00s)
--- PASS: TestDNSProviders/example.com/62:_SSHFP_change_fingerprint_and_type (0.00s)
--- PASS: TestDNSProviders/example.com/63:_SSHFP_Delete_one (0.00s)
--- PASS: TestDNSProviders/example.com/64:_SSHFP_add_many_records (0.00s)
--- PASS: TestDNSProviders/example.com/65:_SSHFP_delete_two (0.00s)
--- PASS: TestDNSProviders/example.com/66:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/67:_CAA_record (0.00s)
--- PASS: TestDNSProviders/example.com/68:_CAA_change_tag (0.00s)
--- PASS: TestDNSProviders/example.com/69:_CAA_change_target (0.00s)
--- PASS: TestDNSProviders/example.com/70:_CAA_change_flag (0.00s)
--- PASS: TestDNSProviders/example.com/71:_CAA_many_records (0.00s)
--- PASS: TestDNSProviders/example.com/72:_CAA_delete (0.00s)
--- PASS: TestDNSProviders/example.com/73:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/74:_TLSA_record (0.00s)
--- PASS: TestDNSProviders/example.com/75:_TLSA_change_usage (0.00s)
--- PASS: TestDNSProviders/example.com/76:_TLSA_change_selector (0.00s)
--- PASS: TestDNSProviders/example.com/77:_TLSA_change_matchingtype (0.00s)
--- PASS: TestDNSProviders/example.com/78:_TLSA_change_certificate (0.00s)
--- PASS: TestDNSProviders/example.com/79:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/80:_Create_CAPS (0.00s)
--- PASS: TestDNSProviders/example.com/81:_Downcase_label (0.00s)
--- PASS: TestDNSProviders/example.com/82:_Downcase_target (0.00s)
--- PASS: TestDNSProviders/example.com/83:_Upcase_both (0.00s)
--- PASS: TestDNSProviders/example.com/84:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/85:_99_records (0.01s)
--- PASS: TestDNSProviders/example.com/86:_100_records (0.01s)
--- PASS: TestDNSProviders/example.com/87:_101_records (0.03s)
--- PASS: TestDNSProviders/example.com/88:_Empty (0.01s)
--- PASS: TestDNSProviders/example.com/89:_Create_a_TXT (0.03s)
--- PASS: TestDNSProviders/example.com/90:_Change_a_TXT (0.00s)
--- PASS: TestDNSProviders/example.com/91:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/92:_Create_a_TXT_with_spaces (0.00s)
--- PASS: TestDNSProviders/example.com/93:_Change_a_TXT_with_spaces (0.00s)
--- PASS: TestDNSProviders/example.com/94:_Create_1_TXT_as_array (0.01s)
--- PASS: TestDNSProviders/example.com/95:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/96:_Create_a_255-byte_TXT (0.00s)
--- PASS: TestDNSProviders/example.com/97:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/98:_Create_TXTMulti_1 (0.01s)
--- PASS: TestDNSProviders/example.com/99:_Create_TXTMulti_2 (0.01s)
--- PASS: TestDNSProviders/example.com/100:_Create_TXTMulti_3 (0.02s)
--- PASS: TestDNSProviders/example.com/101:_Create_TXTMulti_with_quotes (0.00s)
--- PASS: TestDNSProviders/example.com/102:_Change_TXTMulti (0.00s)
--- PASS: TestDNSProviders/example.com/103:_Empty (0.00s)
--- PASS: TestDNSProviders/example.com/104:_3x255-byte_TXTMulti (0.00s)
--- PASS: TestDNSProviders/example.com/105:_Empty (0.01s)
--- PASS: TestDNSProviders/example.com/106:_Create_some_records (0.00s)
--- PASS: TestDNSProviders/example.com/107:_Add_a_new_record_-_ignoring_foo (0.02s)
--- PASS: TestDNSProviders/example.com/108:_Empty (0.01s)
--- PASS: TestDNSProviders/example.com/109:_Create_some_records (0.01s)
--- PASS: TestDNSProviders/example.com/110:_Add_a_new_record_-_ignoring_*.foo (0.00s)
--- PASS: TestDNSProviders/example.com/111:_Empty (0.01s)

View File

@ -1,6 +1,7 @@
D("foo.com","none"
, TXT("@","simple")
, TXT("@",["one"])
, TXT("@",["bonie", "clyde"])
, TXT("@",["straw", "wood", "brick"])
, TXT("a","simple")
, TXT("b","ws at end ")
, TXT("c",["one"])
, TXT("d",["bonie", "clyde"])
, TXT("e",["straw", "wood", "brick"])
);

View File

@ -9,7 +9,7 @@
"records": [
{
"type": "TXT",
"name": "@",
"name": "a",
"target": "simple",
"txtstrings": [
"simple"
@ -17,7 +17,15 @@
},
{
"type": "TXT",
"name": "@",
"name": "b",
"target": "ws at end ",
"txtstrings": [
"ws at end "
]
},
{
"type": "TXT",
"name": "c",
"target": "one",
"txtstrings": [
"one"
@ -25,7 +33,7 @@
},
{
"type": "TXT",
"name": "@",
"name": "d",
"target": "bonie",
"txtstrings": [
"bonie",
@ -34,7 +42,7 @@
},
{
"type": "TXT",
"name": "@",
"name": "e",
"target": "straw",
"txtstrings": [
"straw",

View File

@ -11,7 +11,6 @@ import (
_ "github.com/StackExchange/dnscontrol/v2/providers/digitalocean"
_ "github.com/StackExchange/dnscontrol/v2/providers/dnsimple"
_ "github.com/StackExchange/dnscontrol/v2/providers/exoscale"
_ "github.com/StackExchange/dnscontrol/v2/providers/gandi"
_ "github.com/StackExchange/dnscontrol/v2/providers/gandi_v5"
_ "github.com/StackExchange/dnscontrol/v2/providers/gcloud"
_ "github.com/StackExchange/dnscontrol/v2/providers/hexonet"

View File

@ -75,7 +75,6 @@ func (c *CloudflareApi) getRecordsForDomain(id string, domain string) ([]*models
}
page++
}
// fmt.Printf("DEBUG REORDS=%v\n", records)
return records, nil
}

View File

@ -1,215 +0,0 @@
package gandi
import (
"encoding/json"
"fmt"
"sort"
"strings"
gandidomain "github.com/prasmussen/gandi-api/domain"
gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
"github.com/StackExchange/dnscontrol/v2/models"
"github.com/StackExchange/dnscontrol/v2/pkg/diff"
"github.com/StackExchange/dnscontrol/v2/pkg/printer"
"github.com/StackExchange/dnscontrol/v2/providers"
)
/*
Gandi API DNS provider:
Info required in `creds.json`:
- apikey
*/
var deprecationWarned bool
var features = providers.DocumentationNotes{
providers.CanUseCAA: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CantUseNOPURGE: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot("Can only manage domains registered through their service"),
providers.DocOfficiallySupported: providers.Cannot(),
providers.CanGetZones: providers.Unimplemented(),
}
func init() {
providers.RegisterDomainServiceProviderType("GANDI", newDsp, features)
providers.RegisterRegistrarType("GANDI", newReg)
}
// GandiApi is the API handle for this module.
type GandiApi struct {
ApiKey string
domainIndex map[string]int64 // Map of domainname to index
nameservers map[string][]*models.Nameserver
ZoneId int64
}
type gandiRecord struct {
gandirecord.RecordInfo
}
func (c *GandiApi) getDomainInfo(domain string) (*gandidomain.DomainInfo, error) {
if err := c.fetchDomainList(); err != nil {
return nil, err
}
_, ok := c.domainIndex[domain]
if !ok {
return nil, fmt.Errorf("'%s' not a zone in gandi account", domain)
}
return c.fetchDomainInfo(domain)
}
// GetNameservers returns the nameservers for domain.
func (c *GandiApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
domaininfo, err := c.getDomainInfo(domain)
if err != nil {
return nil, err
}
ns := []*models.Nameserver{}
for _, nsname := range domaininfo.Nameservers {
ns = append(ns, &models.Nameserver{Name: nsname})
}
return ns, nil
}
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (client *GandiApi) GetZoneRecords(domain string) (models.Records, error) {
return nil, fmt.Errorf("not implemented")
// This enables the get-zones subcommand.
// Implement this by extracting the code from GetDomainCorrections into
// a single function. For most providers this should be relatively easy.
}
// GetDomainCorrections returns a list of corrections recommended for this domain.
func (c *GandiApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc.Punycode()
domaininfo, err := c.getDomainInfo(dc.Name)
if err != nil {
return nil, err
}
foundRecords, err := c.getZoneRecords(domaininfo.ZoneId, dc.Name)
if err != nil {
return nil, err
}
expectedRecordSets := make([]gandirecord.RecordSet, 0, len(dc.Records))
recordsToKeep := make([]*models.RecordConfig, 0, len(dc.Records))
for _, rec := range dc.Records {
if rec.TTL < 300 {
printer.Warnf("Gandi does not support ttls < 300. Setting %s from %d to 300\n", rec.GetLabelFQDN(), rec.TTL)
rec.TTL = 300
}
if rec.TTL > 2592000 {
return nil, fmt.Errorf("ERROR: Gandi does not support TTLs > 30 days (TTL=%d)", rec.TTL)
}
if rec.Type == "TXT" {
rec.SetTarget("\"" + rec.GetTargetField() + "\"") // FIXME(tlim): Should do proper quoting.
}
if rec.Type == "NS" && rec.GetLabel() == "@" {
if !strings.HasSuffix(rec.GetTargetField(), ".gandi.net.") {
printer.Warnf("Gandi does not support changing apex NS records. %s will not be added.\n", rec.GetTargetField())
}
continue
}
rs := gandirecord.RecordSet{
"type": rec.Type,
"name": rec.GetLabel(),
"value": rec.GetTargetCombined(),
"ttl": rec.TTL,
}
expectedRecordSets = append(expectedRecordSets, rs)
recordsToKeep = append(recordsToKeep, rec)
}
dc.Records = recordsToKeep
// Normalize
models.PostProcessRecords(foundRecords)
differ := diff.New(dc)
_, create, del, mod := differ.IncrementalDiff(foundRecords)
// Print a list of changes. Generate an actual change that is the zone
changes := false
desc := ""
for _, i := range create {
changes = true
desc += "\n" + i.String()
}
for _, i := range del {
changes = true
desc += "\n" + i.String()
}
for _, i := range mod {
changes = true
desc += "\n" + i.String()
}
msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc)
corrections := []*models.Correction{}
if changes {
corrections = append(corrections,
&models.Correction{
Msg: msg,
F: func() error {
printer.Printf("CREATING ZONE: %v\n", dc.Name)
return c.createGandiZone(dc.Name, domaininfo.ZoneId, expectedRecordSets)
},
})
}
return corrections, nil
}
func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
return newGandi(conf, metadata)
}
func newReg(conf map[string]string) (providers.Registrar, error) {
return newGandi(conf, nil)
}
func newGandi(m map[string]string, metadata json.RawMessage) (*GandiApi, error) {
if !deprecationWarned {
deprecationWarned = true
fmt.Printf("WARNING: GANDI is deprecated and will disappear in 3.0. Please migrate to GANDI_V5.\n")
}
api := &GandiApi{}
api.ApiKey = m["apikey"]
if api.ApiKey == "" {
return nil, fmt.Errorf("missing Gandi apikey")
}
return api, nil
}
// GetRegistrarCorrections returns a list of corrections for this registrar.
func (c *GandiApi) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
domaininfo, err := c.getDomainInfo(dc.Name)
if err != nil {
return nil, err
}
sort.Strings(domaininfo.Nameservers)
found := strings.Join(domaininfo.Nameservers, ",")
desiredNs := []string{}
for _, d := range dc.Nameservers {
desiredNs = append(desiredNs, d.Name)
}
sort.Strings(desiredNs)
desired := strings.Join(desiredNs, ",")
if found != desired {
return []*models.Correction{
{
Msg: fmt.Sprintf("Change Nameservers from '%s' to '%s'", found, desired),
F: func() (err error) {
_, err = c.setDomainNameservers(dc.Name, desiredNs)
return
}},
}, nil
}
return nil, nil
}

View File

@ -1,292 +0,0 @@
package gandi
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/google/uuid"
gandiclient "github.com/prasmussen/gandi-api/client"
gandilivedomain "github.com/prasmussen/gandi-api/live_dns/domain"
gandiliverecord "github.com/prasmussen/gandi-api/live_dns/record"
gandilivezone "github.com/prasmussen/gandi-api/live_dns/zone"
"github.com/StackExchange/dnscontrol/v2/models"
"github.com/StackExchange/dnscontrol/v2/pkg/diff"
"github.com/StackExchange/dnscontrol/v2/pkg/printer"
"github.com/StackExchange/dnscontrol/v2/providers"
)
var liveFeatures = providers.DocumentationNotes{
providers.CanUseCAA: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseTXTMulti: providers.Can(),
providers.CantUseNOPURGE: providers.Cannot(),
providers.DocCreateDomains: providers.Cannot("Can only manage domains registered through their service"),
providers.DocOfficiallySupported: providers.Cannot(),
}
func init() {
providers.RegisterDomainServiceProviderType("GANDI-LIVEDNS", newLiveDsp, liveFeatures)
}
func newLiveDsp(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
APIKey := m["apikey"]
if APIKey == "" {
return nil, fmt.Errorf("missing Gandi apikey")
}
return newLiveClient(APIKey), nil
}
type domainManager interface {
Info(string) (*gandilivedomain.Info, error)
Records(string) gandiliverecord.Manager
}
type zoneManager interface {
InfoByUUID(uuid.UUID) (*gandilivezone.Info, error)
Create(gandilivezone.Info) (*gandilivezone.CreateStatus, error)
Set(string, gandilivezone.Info) (*gandilivezone.Status, error)
Records(gandilivezone.Info) gandiliverecord.Manager
}
type liveClient struct {
client *gandiclient.Client
zoneManager zoneManager
domainManager domainManager
}
func newLiveClient(APIKey string) *liveClient {
cl := gandiclient.New(APIKey, gandiclient.LiveDNS)
return &liveClient{
client: cl,
zoneManager: gandilivezone.New(cl),
domainManager: gandilivedomain.New(cl),
}
}
// GetNameservers returns the list of gandi name servers for a given domain
func (c *liveClient) GetNameservers(domain string) ([]*models.Nameserver, error) {
domains := []string{}
response, err := c.client.Get("/nameservers/"+domain, &domains)
if err != nil {
return nil, fmt.Errorf("failed to get nameservers for domain %s", domain)
}
defer response.Body.Close()
ns := []*models.Nameserver{}
for _, domain := range domains {
ns = append(ns, &models.Nameserver{Name: domain})
}
return ns, nil
}
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (client *liveClient) GetZoneRecords(domain string) (models.Records, error) {
return nil, fmt.Errorf("not implemented")
// This enables the get-zones subcommand.
// Implement this by extracting the code from GetDomainCorrections into
// a single function. For most providers this should be relatively easy.
}
// GetDomainCorrections returns a list of corrections recommended for this domain.
func (c *liveClient) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
dc.Punycode()
records, err := c.domainManager.Records(dc.Name).List()
if err != nil {
return nil, err
}
foundRecords := c.recordConfigFromInfo(records, dc.Name)
recordsToKeep, records, err := c.recordsToInfo(dc.Records)
if err != nil {
return nil, err
}
dc.Records = recordsToKeep
// Normalize
models.PostProcessRecords(foundRecords)
differ := diff.New(dc)
_, create, del, mod := differ.IncrementalDiff(foundRecords)
buf := &bytes.Buffer{}
// Print a list of changes. Generate an actual change that is the zone
changes := false
for _, i := range create {
changes = true
fmt.Fprintln(buf, i)
}
for _, i := range del {
changes = true
fmt.Fprintln(buf, i)
}
for _, i := range mod {
changes = true
fmt.Fprintln(buf, i)
}
if changes {
message := fmt.Sprintf("Setting dns records for %s:", dc.Name)
message += "\n" + buf.String()
return []*models.Correction{
{
Msg: message,
F: func() error {
return c.createZone(dc.Name, records)
},
},
}, nil
}
return []*models.Correction{}, nil
}
// createZone creates a new empty zone for the domain, populates it with the record infos and associates the domain to it
func (c *liveClient) createZone(domainname string, records []*gandiliverecord.Info) error {
domainInfo, err := c.domainManager.Info(domainname)
infos, err := c.zoneManager.InfoByUUID(*domainInfo.ZoneUUID)
if err != nil {
return err
}
infos.Name = fmt.Sprintf("zone created by dnscontrol for %s on %s", domainname, time.Now().Format(time.RFC3339))
printer.Debugf("DEBUG: createZone SharingID=%v\n", infos.SharingID)
// duplicate zone Infos
status, err := c.zoneManager.Create(*infos)
if err != nil {
return err
}
zoneInfos, err := c.zoneManager.InfoByUUID(*status.UUID)
if err != nil {
// gandi might take some time to make the new zone available
for i := 0; i < 10; i++ {
printer.Printf("zone info not yet available. Delay and retry: %s\n", err.Error())
time.Sleep(100 * time.Millisecond)
zoneInfos, err = c.zoneManager.InfoByUUID(*status.UUID)
if err == nil {
break
}
}
}
if err != nil {
return err
}
recordManager := c.zoneManager.Records(*zoneInfos)
for _, record := range records {
_, err := recordManager.Create(*record)
if err != nil {
return err
}
}
_, err = c.zoneManager.Set(domainname, *zoneInfos)
if err != nil {
return err
}
return nil
}
// recordConfigFromInfo takes a DNS record from Gandi liveDNS and returns our native RecordConfig format.
func (c *liveClient) recordConfigFromInfo(infos []*gandiliverecord.Info, origin string) []*models.RecordConfig {
rcs := []*models.RecordConfig{}
for _, info := range infos {
// TXT records might have multiple values. In that case,
// they are all for the TXT record at that label.
if info.Type == "TXT" {
rc := &models.RecordConfig{
Type: info.Type,
Original: info,
TTL: uint32(info.TTL),
}
rc.SetLabel(info.Name, origin)
var parsed []string
for _, txt := range info.Values {
parsed = append(parsed, models.StripQuotes(txt))
}
err := rc.SetTargetTXTs(parsed)
if err != nil {
panic(fmt.Errorf("recordConfigFromInfo=TXT failed: %w", err))
}
rcs = append(rcs, rc)
} else {
// All other record types might have multiple values, but that means
// we should create one Recordconfig for each one.
for _, value := range info.Values {
rc := &models.RecordConfig{
Type: info.Type,
Original: info,
TTL: uint32(info.TTL),
}
rc.SetLabel(info.Name, origin)
switch rtype := info.Type; rtype {
default:
err := rc.PopulateFromString(rtype, value, origin)
if err != nil {
panic(fmt.Errorf("recordConfigFromInfo failed: %w", err))
}
}
rcs = append(rcs, rc)
}
}
}
return rcs
}
// recordsToInfo generates gandi record sets and filters incompatible entries from native records format
func (c *liveClient) recordsToInfo(records models.Records) (models.Records, []*gandiliverecord.Info, error) {
recordSets := map[string]map[string]*gandiliverecord.Info{}
recordInfos := []*gandiliverecord.Info{}
recordToKeep := models.Records{}
for _, rec := range records {
if rec.TTL < 300 {
printer.Warnf("Gandi does not support ttls < 300. %s will not be set to %d.\n", rec.GetLabelFQDN(), rec.TTL)
rec.TTL = 300
}
if rec.TTL > 2592000 {
return nil, nil, fmt.Errorf("ERROR: Gandi does not support TTLs > 30 days (TTL=%d)", rec.TTL)
}
if rec.Type == "NS" && rec.GetLabel() == "@" {
if !strings.HasSuffix(rec.GetTargetField(), ".gandi.net.") {
printer.Warnf("Gandi does not support changing apex NS records. %s will not be added.\n", rec.GetTargetField())
}
continue
}
r, ok := recordSets[rec.GetLabel()][rec.Type]
if !ok {
_, ok := recordSets[rec.GetLabel()]
if !ok {
recordSets[rec.GetLabel()] = map[string]*gandiliverecord.Info{}
}
r = &gandiliverecord.Info{
Type: rec.Type,
Name: rec.GetLabel(),
TTL: int64(rec.TTL),
}
recordInfos = append(recordInfos, r)
recordSets[rec.GetLabel()][rec.Type] = r
} else {
if r.TTL != int64(rec.TTL) {
printer.Warnf(
"Gandi liveDNS API does not support different TTL for the couple fqdn/type. Will use TTL of %d for %s %s\n",
r.TTL,
r.Type,
r.Name,
)
}
}
recordToKeep = append(recordToKeep, rec)
if rec.Type == "TXT" {
for _, t := range rec.TxtStrings {
r.Values = append(r.Values, "\""+t+"\"") // FIXME(tlim): Should do proper quoting.
}
} else {
r.Values = append(r.Values, rec.GetTargetCombined())
}
}
return recordToKeep, recordInfos, nil
}

View File

@ -1,132 +0,0 @@
package gandi
import (
"testing"
"github.com/StackExchange/dnscontrol/v2/models"
"github.com/prasmussen/gandi-api/live_dns/record"
"github.com/stretchr/testify/assert"
)
func makeRC(label, domain, target string, rc models.RecordConfig) *models.RecordConfig {
rc.SetLabel(label, domain)
rc.SetTarget(target)
return &rc
}
func TestRecordConfigFromInfo(t *testing.T) {
for _, data := range []struct {
info *record.Info
config []*models.RecordConfig
}{
{
&record.Info{
Name: "www",
Type: "A",
TTL: 500,
Values: []string{"127.0.0.1", "127.1.0.1"},
},
[]*models.RecordConfig{
makeRC("www", "example.com", "127.0.0.1", models.RecordConfig{
Type: "A",
TTL: 500,
}),
makeRC("www", "example.com", "127.1.0.1", models.RecordConfig{
Type: "A",
TTL: 500,
}),
},
},
{
&record.Info{
Name: "www",
Type: "TXT",
TTL: 500,
Values: []string{"\"test 2\"", "\"test message test message test message\""},
},
[]*models.RecordConfig{
makeRC("www", "example.com", "test 2", models.RecordConfig{
Type: "TXT",
TxtStrings: []string{"test 2", "test message test message test message"},
TTL: 500,
}),
},
},
{
&record.Info{
Name: "www",
Type: "CAA",
TTL: 500,
// examples from https://sslmate.com/caa/
Values: []string{"0 issue \"www.certinomis.com\"", "0 issuewild \"buypass.com\""},
},
[]*models.RecordConfig{
makeRC("www", "example.com", "www.certinomis.com", models.RecordConfig{
Type: "CAA",
CaaFlag: 0,
CaaTag: "issue",
TTL: 500,
}),
makeRC("www", "example.com", "buypass.com", models.RecordConfig{
Type: "CAA",
CaaFlag: 0,
CaaTag: "issuewild",
TTL: 500,
}),
},
},
{
&record.Info{
Name: "test",
Type: "SRV",
TTL: 500,
Values: []string{"20 0 5060 backupbox.example.com."},
},
[]*models.RecordConfig{
makeRC("test", "example.com", "backupbox.example.com.", models.RecordConfig{
Type: "SRV",
SrvPriority: 20,
SrvWeight: 0,
SrvPort: 5060,
TTL: 500,
}),
},
},
{
&record.Info{
Name: "mail",
Type: "MX",
TTL: 500,
Values: []string{"50 fb.mail.gandi.net.", "10 spool.mail.gandi.net."},
},
[]*models.RecordConfig{
makeRC("mail", "example.com", "fb.mail.gandi.net.", models.RecordConfig{
Type: "MX",
MxPreference: 50,
TTL: 500,
}),
makeRC("mail", "example.com", "spool.mail.gandi.net.", models.RecordConfig{
Type: "MX",
MxPreference: 10,
TTL: 500,
}),
},
},
} {
t.Run("with record type "+data.info.Type, func(t *testing.T) {
c := liveClient{}
for _, c := range data.config {
c.Original = data.info
}
t.Run("Convert gandi info to record config", func(t *testing.T) {
recordConfig := c.recordConfigFromInfo([]*record.Info{data.info}, "example.com")
assert.Equal(t, data.config, recordConfig)
})
t.Run("Convert record config to gandi info", func(t *testing.T) {
_, recordInfos, err := c.recordsToInfo(data.config)
assert.NoError(t, err)
assert.Equal(t, []*record.Info{data.info}, recordInfos)
})
})
}
}

View File

@ -1,201 +0,0 @@
package gandi
import (
"fmt"
gandiclient "github.com/prasmussen/gandi-api/client"
gandidomain "github.com/prasmussen/gandi-api/domain"
gandinameservers "github.com/prasmussen/gandi-api/domain/nameservers"
gandizone "github.com/prasmussen/gandi-api/domain/zone"
gandirecord "github.com/prasmussen/gandi-api/domain/zone/record"
gandiversion "github.com/prasmussen/gandi-api/domain/zone/version"
gandioperation "github.com/prasmussen/gandi-api/operation"
"github.com/StackExchange/dnscontrol/v2/models"
)
// fetchDomainList gets list of domains for account. Cache ids for easy lookup.
func (c *GandiApi) fetchDomainList() error {
if c.domainIndex != nil {
return nil
}
c.domainIndex = map[string]int64{}
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
domain := gandidomain.New(gc)
domains, err := domain.List()
if err != nil {
// fmt.Println(err)
return err
}
for _, d := range domains {
c.domainIndex[d.Fqdn] = d.Id
}
return nil
}
// fetchDomainInfo gets information about a domain.
func (c *GandiApi) fetchDomainInfo(fqdn string) (*gandidomain.DomainInfo, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
domain := gandidomain.New(gc)
return domain.Info(fqdn)
}
// setDomainNameservers updates the nameservers of a domain.
func (c *GandiApi) setDomainNameservers(fqdn string, nameservers []string) (*gandioperation.OperationInfo, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
nameserversapi := gandinameservers.New(gc)
return nameserversapi.Set(fqdn, nameservers)
}
// getRecordsForDomain returns a list of records for a zone.
func (c *GandiApi) getZoneRecords(zoneid int64, origin string) ([]*models.RecordConfig, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
record := gandirecord.New(gc)
recs, err := record.List(zoneid, 0)
if err != nil {
return nil, err
}
rcs := make([]*models.RecordConfig, 0, len(recs))
for _, r := range recs {
rcs = append(rcs, nativeToRecord(r, origin))
}
return rcs, nil
}
// convert takes a DNS record from Gandi and returns our native RecordConfig format.
func nativeToRecord(r *gandirecord.RecordInfo, origin string) *models.RecordConfig {
rc := &models.RecordConfig{
//NameFQDN: dnsutil.AddOrigin(r.Name, origin),
//Name: r.Name,
//Type: r.Type,
TTL: uint32(r.Ttl),
Original: r,
//Target: r.Value,
}
rc.SetLabel(r.Name, origin)
switch rtype := r.Type; rtype {
default: // "A", "AAAA", "CAA", "NS", "CNAME", "MX", "PTR", "SRV", "TXT"
if err := rc.PopulateFromString(rtype, r.Value, origin); err != nil {
panic(fmt.Errorf("unparsable record received from gandi: %w", err))
}
}
return rc
}
// listZones retrieves the list of zones.
func (c *GandiApi) listZones() ([]*gandizone.ZoneInfoBase, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
zone := gandizone.New(gc)
return zone.List()
}
// setZone assigns a particular zone to a domain.
func (c *GandiApi) setZones(domainname string, zoneID int64) (*gandidomain.DomainInfo, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
zone := gandizone.New(gc)
return zone.Set(domainname, zoneID)
}
// getZoneInfo gets ZoneInfo about a zone.
func (c *GandiApi) getZoneInfo(zoneid int64) (*gandizone.ZoneInfo, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
zone := gandizone.New(gc)
return zone.Info(zoneid)
}
// createZone creates an entirely new zone.
func (c *GandiApi) createZone(name string) (*gandizone.ZoneInfo, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
zone := gandizone.New(gc)
return zone.Create(name)
}
func (c *GandiApi) getEditableZone(domainname string, zoneinfo *gandizone.ZoneInfo) (int64, error) {
var zoneID int64
if zoneinfo.Domains < 2 {
// If there is only on{ domain linked to this zone, use it.
zoneID = zoneinfo.Id
fmt.Printf("Using zone id=%d named %#v\n", zoneID, zoneinfo.Name)
return zoneID, nil
}
// We can't use the zone_id given to us. Let's make/find a new one.
zones, err := c.listZones()
if err != nil {
return 0, err
}
zonename := fmt.Sprintf("%s dnscontrol", domainname)
for _, z := range zones {
if z.Name == zonename {
zoneID = z.Id
fmt.Printf("Recycling zone id=%d named %#v\n", zoneID, z.Name)
return zoneID, nil
}
}
zoneinfo, err = c.createZone(zonename)
if err != nil {
return 0, err
}
zoneID = zoneinfo.Id
fmt.Printf("Created zone id=%d named %#v\n", zoneID, zoneinfo.Name)
return zoneID, nil
}
// makeEditableZone
func (c *GandiApi) makeEditableZone(zoneID int64) (int64, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
version := gandiversion.New(gc)
return version.New(zoneID, 0)
}
// setZoneRecords
func (c *GandiApi) setZoneRecords(zoneID, versionID int64, records []gandirecord.RecordSet) ([]*gandirecord.RecordInfo, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
record := gandirecord.New(gc)
return record.SetRecords(zoneID, versionID, records)
}
// activateVersion
func (c *GandiApi) activateVersion(zoneID, versionID int64) (bool, error) {
gc := gandiclient.New(c.ApiKey, gandiclient.Production)
version := gandiversion.New(gc)
return version.Set(zoneID, versionID)
}
func (c *GandiApi) createGandiZone(domainname string, zoneID int64, records []gandirecord.RecordSet) error {
// Get the zone_id of the zone we'll be updating.
zoneinfo, err := c.getZoneInfo(zoneID)
if err != nil {
return err
}
zoneID, err = c.getEditableZone(domainname, zoneinfo)
if err != nil {
return err
}
// Get the versionID of the zone we're updating.
versionID, err := c.makeEditableZone(zoneID)
if err != nil {
return err
}
// Update the new version.
_, err = c.setZoneRecords(zoneID, versionID, records)
if err != nil {
return err
}
// Activate zone version
_, err = c.activateVersion(zoneID, versionID)
if err != nil {
return err
}
_, err = c.setZones(domainname, zoneID)
if err != nil {
return err
}
return nil
}

View File

@ -1,9 +0,0 @@
language: go
go:
- 1.4.3
- 1.5.3
- tip
script:
- go test -v ./...

View File

@ -1,10 +0,0 @@
# How to contribute
We definitely welcome patches and contribution to this project!
### Legal requirements
In order to protect both you and ourselves, you will need to sign the
[Contributor License Agreement](https://cla.developers.google.com/clas).
You may have already signed it for other Google projects.

View File

@ -1,9 +0,0 @@
Paul Borman <borman@google.com>
bmatsuo
shawnps
theory
jboverfelt
dsymonds
cd1
wallclockbuilder
dansouza

View File

@ -1,27 +0,0 @@
Copyright (c) 2009,2014 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,19 +0,0 @@
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on
[RFC 4122](http://tools.ietf.org/html/rfc4122)
and DCE 1.1: Authentication and Security Services.
This package is based on the github.com/pborman/uuid package (previously named
code.google.com/p/go-uuid). It differs from these earlier packages in that
a UUID is a 16 byte array rather than a byte slice. One loss due to this
change is the ability to represent an invalid UUID (vs a NIL UUID).
###### Install
`go get github.com/google/uuid`
###### Documentation
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
Full `go doc` style documentation for the package can be viewed online without
installing this package by using the GoDoc site here:
http://godoc.org/github.com/google/uuid

80
vendor/github.com/google/uuid/dce.go generated vendored
View File

@ -1,80 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
uuid, err := NewUUID()
if err == nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid, err
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCESecurity(Person, uint32(os.Getuid()))
func NewDCEPerson() (UUID, error) {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCESecurity(Group, uint32(os.Getgid()))
func NewDCEGroup() (UUID, error) {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID. Domains are only defined
// for Version 2 UUIDs.
func (uuid UUID) Domain() Domain {
return Domain(uuid[9])
}
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
// UUIDs.
func (uuid UUID) ID() uint32 {
return binary.BigEndian.Uint32(uuid[0:4])
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

12
vendor/github.com/google/uuid/doc.go generated vendored
View File

@ -1,12 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uuid generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
// Services.
//
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
// maps or compared directly.
package uuid

View File

@ -1 +0,0 @@
module github.com/google/uuid

View File

@ -1,53 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known namespace IDs and UUIDs
var (
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
Nil UUID // empty UUID, all zeros
)
// NewHash returns a new UUID derived from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space[:])
h.Write(data)
s := h.Sum(nil)
var uuid UUID
copy(uuid[:], s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}

View File

@ -1,37 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "fmt"
// MarshalText implements encoding.TextMarshaler.
func (uuid UUID) MarshalText() ([]byte, error) {
var js [36]byte
encodeHex(js[:], uuid)
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (uuid *UUID) UnmarshalText(data []byte) error {
id, err := ParseBytes(data)
if err == nil {
*uuid = id
}
return err
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (uuid UUID) MarshalBinary() ([]byte, error) {
return uuid[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (uuid *UUID) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(uuid[:], data)
return nil
}

View File

@ -1,90 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"sync"
)
var (
nodeMu sync.Mutex
ifname string // name of interface being used
nodeID [6]byte // hardware for version 1 UUIDs
zeroID [6]byte // nodeID with only 0's
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
defer nodeMu.Unlock()
nodeMu.Lock()
return ifname
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
return setNodeInterface(name)
}
func setNodeInterface(name string) bool {
iname, addr := getHardwareInterface(name) // null implementation for js
if iname != "" && addr != nil {
ifname = iname
copy(nodeID[:], addr)
return true
}
// We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
ifname = "random"
randomBits(nodeID[:])
return true
}
return false
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
defer nodeMu.Unlock()
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nid := nodeID
return nid[:]
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
if len(id) < 6 {
return false
}
defer nodeMu.Unlock()
nodeMu.Lock()
copy(nodeID[:], id)
ifname = "user"
return true
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
var node [6]byte
copy(node[:], uuid[10:])
return node[:]
}

View File

@ -1,12 +0,0 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build js
package uuid
// getHardwareInterface returns nil values for the JS version of the code.
// This remvoves the "net" dependency, because it is not used in the browser.
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
func getHardwareInterface(name string) (string, []byte) { return "", nil }

View File

@ -1,33 +0,0 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !js
package uuid
import "net"
var interfaces []net.Interface // cached list of interfaces
// getHardwareInterface returns the name and hardware address of interface name.
// If name is "" then the name and hardware address of one of the system's
// interfaces is returned. If no interfaces are found (name does not exist or
// there are no interfaces) then "", nil is returned.
//
// Only addresses of at least 6 bytes are returned.
func getHardwareInterface(name string) (string, []byte) {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil {
return "", nil
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
return ifs.Name, ifs.HardwareAddr
}
}
return "", nil
}

59
vendor/github.com/google/uuid/sql.go generated vendored
View File

@ -1,59 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"database/sql/driver"
"fmt"
)
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
switch src := src.(type) {
case nil:
return nil
case string:
// if an empty UUID comes from a table, we return a null UUID
if src == "" {
return nil
}
// see Parse for required string format
u, err := Parse(src)
if err != nil {
return fmt.Errorf("Scan: %v", err)
}
*uuid = u
case []byte:
// if an empty UUID comes from a table, we return a null UUID
if len(src) == 0 {
return nil
}
// assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse
if len(src) != 16 {
return uuid.Scan(string(src))
}
copy((*uuid)[:], src)
default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
}
return nil
}
// Value implements sql.Valuer so that UUIDs can be written to databases
// transparently. Currently, UUIDs map to strings. Please consult
// database-specific driver documentation for matching types.
func (uuid UUID) Value() (driver.Value, error) {
return uuid.String(), nil
}

123
vendor/github.com/google/uuid/time.go generated vendored
View File

@ -1,123 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"sync"
"time"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time int64
const (
lillian = 2299160 // Julian day of 15 Oct 1582
unix = 2440587 // Julian day of 1 Jan 1970
epoch = unix - lillian // Days between epochs
g1582 = epoch * 86400 // seconds between epochs
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
)
var (
timeMu sync.Mutex
lasttime uint64 // last time we returned
clockSeq uint16 // clock sequence for this run
timeNow = time.Now // for testing
)
// UnixTime converts t the number of seconds and nanoseconds using the Unix
// epoch of 1 Jan 1970.
func (t Time) UnixTime() (sec, nsec int64) {
sec = int64(t - g1582ns100)
nsec = (sec % 10000000) * 100
sec /= 10000000
return sec, nsec
}
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// clock sequence as well as adjusting the clock sequence as needed. An error
// is returned if the current time cannot be determined.
func GetTime() (Time, uint16, error) {
defer timeMu.Unlock()
timeMu.Lock()
return getTime()
}
func getTime() (Time, uint16, error) {
t := timeNow()
// If we don't have a clock sequence already, set one.
if clockSeq == 0 {
setClockSequence(-1)
}
now := uint64(t.UnixNano()/100) + g1582ns100
// If time has gone backwards with this clock sequence then we
// increment the clock sequence
if now <= lasttime {
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
}
lasttime = now
return Time(now), clockSeq, nil
}
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence is used, a new
// random clock sequence is generated the first time a clock sequence is
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
func ClockSequence() int {
defer timeMu.Unlock()
timeMu.Lock()
return clockSequence()
}
func clockSequence() int {
if clockSeq == 0 {
setClockSequence(-1)
}
return int(clockSeq & 0x3fff)
}
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) {
defer timeMu.Unlock()
timeMu.Lock()
setClockSequence(seq)
}
func setClockSequence(seq int) {
if seq == -1 {
var b [2]byte
randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1])
}
oldSeq := clockSeq
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if oldSeq != clockSeq {
lasttime = 0
}
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. The time is only defined for version 1 and 2 UUIDs.
func (uuid UUID) Time() Time {
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time)
}
// ClockSequence returns the clock sequence encoded in uuid.
// The clock sequence is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) ClockSequence() int {
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
}

View File

@ -1,43 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"io"
)
// randomBits completely fills slice b with random data.
func randomBits(b []byte) {
if _, err := io.ReadFull(rander, b); err != nil {
panic(err.Error()) // rand should never fail
}
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts hex characters x1 and x2 into a byte.
func xtob(x1, x2 byte) (byte, bool) {
b1 := xvalues[x1]
b2 := xvalues[x2]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

245
vendor/github.com/google/uuid/uuid.go generated vendored
View File

@ -1,245 +0,0 @@
// Copyright 2018 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
)
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID [16]byte
// A Version represents a UUID's version.
type Version byte
// A Variant represents a UUID's variant.
type Variant byte
// Constants returned by Variant.
const (
Invalid = Variant(iota) // Invalid UUID
RFC4122 // The variant specified in RFC4122
Reserved // Reserved, NCS backward compatibility.
Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
// Parse decodes s into a UUID or returns an error. Both the standard UUID
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
func Parse(s string) (UUID, error) {
var uuid UUID
switch len(s) {
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36:
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9:
if strings.ToLower(s[:9]) != "urn:uuid:" {
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
}
s = s[9:]
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
case 36 + 2:
s = s[1:]
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
case 32:
var ok bool
for i := range uuid {
uuid[i], ok = xtob(s[i*2], s[i*2+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
v, ok := xtob(s[x], s[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
uuid[i] = v
}
return uuid, nil
}
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
func ParseBytes(b []byte) (UUID, error) {
var uuid UUID
switch len(b) {
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
}
b = b[9:]
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
b = b[1:]
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
var ok bool
for i := 0; i < 32; i += 2 {
uuid[i/2], ok = xtob(b[i], b[i+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
v, ok := xtob(b[x], b[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
uuid[i] = v
}
return uuid, nil
}
// MustParse is like Parse but panics if the string cannot be parsed.
// It simplifies safe initialization of global variables holding compiled UUIDs.
func MustParse(s string) UUID {
uuid, err := Parse(s)
if err != nil {
panic(`uuid: Parse(` + s + `): ` + err.Error())
}
return uuid
}
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
// does not have a length of 16. The bytes are copied from the slice.
func FromBytes(b []byte) (uuid UUID, err error) {
err = uuid.UnmarshalBinary(b)
return uuid, err
}
// Must returns uuid if err is nil and panics otherwise.
func Must(uuid UUID, err error) UUID {
if err != nil {
panic(err)
}
return uuid
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
var buf [36 + 9]byte
copy(buf[:], "urn:uuid:")
encodeHex(buf[9:], uuid)
return string(buf[:])
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst, uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'
hex.Encode(dst[14:18], uuid[6:8])
dst[18] = '-'
hex.Encode(dst[19:23], uuid[8:10])
dst[23] = '-'
hex.Encode(dst[24:], uuid[10:])
}
// Variant returns the variant encoded in uuid.
func (uuid UUID) Variant() Variant {
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
}
// Version returns the version of uuid.
func (uuid UUID) Version() Version {
return Version(uuid[6] >> 4)
}
func (v Version) String() string {
if v > 15 {
return fmt.Sprintf("BAD_VERSION_%d", v)
}
return fmt.Sprintf("VERSION_%d", v)
}
func (v Variant) String() string {
switch v {
case RFC4122:
return "RFC4122"
case Reserved:
return "Reserved"
case Microsoft:
return "Microsoft"
case Future:
return "Future"
case Invalid:
return "Invalid"
}
return fmt.Sprintf("BadVariant%d", int(v))
}
// SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
return
}
rander = r
}

View File

@ -1,44 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil and an error.
//
// In most cases, New should be used.
func NewUUID() (UUID, error) {
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nodeMu.Unlock()
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}
timeLow := uint32(now & 0xffffffff)
timeMid := uint16((now >> 32) & 0xffff)
timeHi := uint16((now >> 48) & 0x0fff)
timeHi |= 0x1000 // Version 1
binary.BigEndian.PutUint32(uuid[0:], timeLow)
binary.BigEndian.PutUint16(uuid[4:], timeMid)
binary.BigEndian.PutUint16(uuid[6:], timeHi)
binary.BigEndian.PutUint16(uuid[8:], seq)
copy(uuid[10:], nodeID[:])
return uuid, nil
}

View File

@ -1,43 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "io"
// New creates a new random UUID or panics. New is equivalent to
// the expression
//
// uuid.Must(uuid.NewRandom())
func New() UUID {
return Must(NewRandom())
}
// NewRandom returns a Random (Version 4) UUID.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
return NewRandomFromReader(rander)
}
func NewRandomFromReader(r io.Reader) (UUID, error) {
var uuid UUID
_, err := io.ReadFull(r, uuid[:])
if err != nil {
return Nil, err
}
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

View File

@ -1,19 +0,0 @@
Copyright (C) 2012 Dmitry Maksimov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,79 +0,0 @@
## Overview
xmlrpc is an implementation of client side part of XMLRPC protocol in Go language.
## Installation
To install xmlrpc package run `go get github.com/kolo/xmlrpc`. To use
it in application add `"github.com/kolo/xmlrpc"` string to `import`
statement.
## Usage
client, _ := xmlrpc.NewClient("https://bugzilla.mozilla.org/xmlrpc.cgi", nil)
result := struct{
Version string `xmlrpc:"version"`
}{}
client.Call("Bugzilla.version", nil, &result)
fmt.Printf("Version: %s\n", result.Version) // Version: 4.2.7+
Second argument of NewClient function is an object that implements
[http.RoundTripper](http://golang.org/pkg/net/http/#RoundTripper)
interface, it can be used to get more control over connection options.
By default it initialized by http.DefaultTransport object.
### Arguments encoding
xmlrpc package supports encoding of native Go data types to method
arguments.
Data types encoding rules:
* int, int8, int16, int32, int64 encoded to int;
* float32, float64 encoded to double;
* bool encoded to boolean;
* string encoded to string;
* time.Time encoded to datetime.iso8601;
* xmlrpc.Base64 encoded to base64;
* slice decoded to array;
Structs decoded to struct by following rules:
* all public field become struct members;
* field name become member name;
* if field has xmlrpc tag, its value become member name.
Server method can accept few arguments, to handle this case there is
special approach to handle slice of empty interfaces (`[]interface{}`).
Each value of such slice encoded as separate argument.
### Result decoding
Result of remote function is decoded to native Go data type.
Data types decoding rules:
* int, i4 decoded to int, int8, int16, int32, int64;
* double decoded to float32, float64;
* boolean decoded to bool;
* string decoded to string;
* array decoded to slice;
* structs decoded following the rules described in previous section;
* datetime.iso8601 decoded as time.Time data type;
* base64 decoded to string.
## Implementation details
xmlrpc package contains clientCodec type, that implements [rpc.ClientCodec](http://golang.org/pkg/net/rpc/#ClientCodec)
interface of [net/rpc](http://golang.org/pkg/net/rpc) package.
xmlrpc package works over HTTP protocol, but some internal functions
and data type were made public to make it easier to create another
implementation of xmlrpc that works over another protocol. To encode
request body there is EncodeMethodCall function. To decode server
response Response data type can be used.
## Contribution
Feel free to fork the project, submit pull requests, ask questions.
## Authors
Dmitry Maksimov (dmtmax@gmail.com)

View File

@ -1,144 +0,0 @@
package xmlrpc
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/rpc"
"net/url"
)
type Client struct {
*rpc.Client
}
// clientCodec is rpc.ClientCodec interface implementation.
type clientCodec struct {
// url presents url of xmlrpc service
url *url.URL
// httpClient works with HTTP protocol
httpClient *http.Client
// cookies stores cookies received on last request
cookies http.CookieJar
// responses presents map of active requests. It is required to return request id, that
// rpc.Client can mark them as done.
responses map[uint64]*http.Response
response *Response
// ready presents channel, that is used to link request and it`s response.
ready chan uint64
}
func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)
if codec.cookies != nil {
for _, cookie := range codec.cookies.Cookies(codec.url) {
httpRequest.AddCookie(cookie)
}
}
if err != nil {
return err
}
var httpResponse *http.Response
httpResponse, err = codec.httpClient.Do(httpRequest)
if err != nil {
return err
}
if codec.cookies != nil {
codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
}
codec.responses[request.Seq] = httpResponse
codec.ready <- request.Seq
return nil
}
func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
seq := <-codec.ready
httpResponse := codec.responses[seq]
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode)
}
respData, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return err
}
httpResponse.Body.Close()
resp := NewResponse(respData)
if resp.Failed() {
response.Error = fmt.Sprintf("%v", resp.Err())
}
codec.response = resp
response.Seq = seq
delete(codec.responses, seq)
return nil
}
func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
if v == nil {
return nil
}
if err = codec.response.Unmarshal(v); err != nil {
return err
}
return nil
}
func (codec *clientCodec) Close() error {
transport := codec.httpClient.Transport.(*http.Transport)
transport.CloseIdleConnections()
return nil
}
// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
func NewClient(requrl string, transport http.RoundTripper) (*Client, error) {
if transport == nil {
transport = http.DefaultTransport
}
httpClient := &http.Client{Transport: transport}
jar, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
u, err := url.Parse(requrl)
if err != nil {
return nil, err
}
codec := clientCodec{
url: u,
httpClient: httpClient,
ready: make(chan uint64),
responses: make(map[uint64]*http.Response),
cookies: jar,
}
return &Client{rpc.NewClientWithCodec(&codec)}, nil
}

View File

@ -1,449 +0,0 @@
package xmlrpc
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"time"
)
const iso8601 = "20060102T15:04:05"
var (
// CharsetReader is a function to generate reader which converts a non UTF-8
// charset into UTF-8.
CharsetReader func(string, io.Reader) (io.Reader, error)
invalidXmlError = errors.New("invalid xml")
)
type TypeMismatchError string
func (e TypeMismatchError) Error() string { return string(e) }
type decoder struct {
*xml.Decoder
}
func unmarshal(data []byte, v interface{}) (err error) {
dec := &decoder{xml.NewDecoder(bytes.NewBuffer(data))}
if CharsetReader != nil {
dec.CharsetReader = CharsetReader
}
var tok xml.Token
for {
if tok, err = dec.Token(); err != nil {
return err
}
if t, ok := tok.(xml.StartElement); ok {
if t.Name.Local == "value" {
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return errors.New("non-pointer value passed to unmarshal")
}
if err = dec.decodeValue(val.Elem()); err != nil {
return err
}
break
}
}
}
// read until end of document
err = dec.Skip()
if err != nil && err != io.EOF {
return err
}
return nil
}
func (dec *decoder) decodeValue(val reflect.Value) error {
var tok xml.Token
var err error
if val.Kind() == reflect.Ptr {
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem()))
}
val = val.Elem()
}
var typeName string
for {
if tok, err = dec.Token(); err != nil {
return err
}
if t, ok := tok.(xml.EndElement); ok {
if t.Name.Local == "value" {
return nil
} else {
return invalidXmlError
}
}
if t, ok := tok.(xml.StartElement); ok {
typeName = t.Name.Local
break
}
// Treat value data without type identifier as string
if t, ok := tok.(xml.CharData); ok {
if value := strings.TrimSpace(string(t)); value != "" {
if err = checkType(val, reflect.String); err != nil {
return err
}
val.SetString(value)
return nil
}
}
}
switch typeName {
case "struct":
ismap := false
pmap := val
valType := val.Type()
if err = checkType(val, reflect.Struct); err != nil {
if checkType(val, reflect.Map) == nil {
if valType.Key().Kind() != reflect.String {
return fmt.Errorf("only maps with string key type can be unmarshalled")
}
ismap = true
} else if checkType(val, reflect.Interface) == nil && val.IsNil() {
var dummy map[string]interface{}
pmap = reflect.New(reflect.TypeOf(dummy)).Elem()
valType = pmap.Type()
ismap = true
} else {
return err
}
}
var fields map[string]reflect.Value
if !ismap {
fields = make(map[string]reflect.Value)
for i := 0; i < valType.NumField(); i++ {
field := valType.Field(i)
fieldVal := val.FieldByName(field.Name)
if fieldVal.CanSet() {
if fn := field.Tag.Get("xmlrpc"); fn != "" {
fields[fn] = fieldVal
} else {
fields[field.Name] = fieldVal
}
}
}
} else {
// Create initial empty map
pmap.Set(reflect.MakeMap(valType))
}
// Process struct members.
StructLoop:
for {
if tok, err = dec.Token(); err != nil {
return err
}
switch t := tok.(type) {
case xml.StartElement:
if t.Name.Local != "member" {
return invalidXmlError
}
tagName, fieldName, err := dec.readTag()
if err != nil {
return err
}
if tagName != "name" {
return invalidXmlError
}
var fv reflect.Value
ok := true
if !ismap {
fv, ok = fields[string(fieldName)]
} else {
fv = reflect.New(valType.Elem())
}
if ok {
for {
if tok, err = dec.Token(); err != nil {
return err
}
if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "value" {
if err = dec.decodeValue(fv); err != nil {
return err
}
// </value>
if err = dec.Skip(); err != nil {
return err
}
break
}
}
}
// </member>
if err = dec.Skip(); err != nil {
return err
}
if ismap {
pmap.SetMapIndex(reflect.ValueOf(string(fieldName)), reflect.Indirect(fv))
val.Set(pmap)
}
case xml.EndElement:
break StructLoop
}
}
case "array":
pslice := val
if checkType(val, reflect.Interface) == nil && val.IsNil() {
var dummy []interface{}
pslice = reflect.New(reflect.TypeOf(dummy)).Elem()
} else if err = checkType(val, reflect.Slice); err != nil {
return err
}
ArrayLoop:
for {
if tok, err = dec.Token(); err != nil {
return err
}
switch t := tok.(type) {
case xml.StartElement:
if t.Name.Local != "data" {
return invalidXmlError
}
slice := reflect.MakeSlice(pslice.Type(), 0, 0)
DataLoop:
for {
if tok, err = dec.Token(); err != nil {
return err
}
switch tt := tok.(type) {
case xml.StartElement:
if tt.Name.Local != "value" {
return invalidXmlError
}
v := reflect.New(pslice.Type().Elem())
if err = dec.decodeValue(v); err != nil {
return err
}
slice = reflect.Append(slice, v.Elem())
// </value>
if err = dec.Skip(); err != nil {
return err
}
case xml.EndElement:
pslice.Set(slice)
val.Set(pslice)
break DataLoop
}
}
case xml.EndElement:
break ArrayLoop
}
}
default:
if tok, err = dec.Token(); err != nil {
return err
}
var data []byte
switch t := tok.(type) {
case xml.EndElement:
return nil
case xml.CharData:
data = []byte(t.Copy())
default:
return invalidXmlError
}
switch typeName {
case "int", "i4", "i8":
if checkType(val, reflect.Interface) == nil && val.IsNil() {
i, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return err
}
pi := reflect.New(reflect.TypeOf(i)).Elem()
pi.SetInt(i)
val.Set(pi)
} else if err = checkType(val, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64); err != nil {
return err
} else {
i, err := strconv.ParseInt(string(data), 10, val.Type().Bits())
if err != nil {
return err
}
val.SetInt(i)
}
case "string", "base64":
str := string(data)
if checkType(val, reflect.Interface) == nil && val.IsNil() {
pstr := reflect.New(reflect.TypeOf(str)).Elem()
pstr.SetString(str)
val.Set(pstr)
} else if err = checkType(val, reflect.String); err != nil {
return err
} else {
val.SetString(str)
}
case "dateTime.iso8601":
t, err := time.Parse(iso8601, string(data))
if err != nil {
return err
}
if checkType(val, reflect.Interface) == nil && val.IsNil() {
ptime := reflect.New(reflect.TypeOf(t)).Elem()
ptime.Set(reflect.ValueOf(t))
val.Set(ptime)
} else if _, ok := val.Interface().(time.Time); !ok {
return TypeMismatchError(fmt.Sprintf("error: type mismatch error - can't decode %v to time", val.Kind()))
} else {
val.Set(reflect.ValueOf(t))
}
case "boolean":
v, err := strconv.ParseBool(string(data))
if err != nil {
return err
}
if checkType(val, reflect.Interface) == nil && val.IsNil() {
pv := reflect.New(reflect.TypeOf(v)).Elem()
pv.SetBool(v)
val.Set(pv)
} else if err = checkType(val, reflect.Bool); err != nil {
return err
} else {
val.SetBool(v)
}
case "double":
if checkType(val, reflect.Interface) == nil && val.IsNil() {
i, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return err
}
pdouble := reflect.New(reflect.TypeOf(i)).Elem()
pdouble.SetFloat(i)
val.Set(pdouble)
} else if err = checkType(val, reflect.Float32, reflect.Float64); err != nil {
return err
} else {
i, err := strconv.ParseFloat(string(data), val.Type().Bits())
if err != nil {
return err
}
val.SetFloat(i)
}
default:
return errors.New("unsupported type")
}
// </type>
if err = dec.Skip(); err != nil {
return err
}
}
return nil
}
func (dec *decoder) readTag() (string, []byte, error) {
var tok xml.Token
var err error
var name string
for {
if tok, err = dec.Token(); err != nil {
return "", nil, err
}
if t, ok := tok.(xml.StartElement); ok {
name = t.Name.Local
break
}
}
value, err := dec.readCharData()
if err != nil {
return "", nil, err
}
return name, value, dec.Skip()
}
func (dec *decoder) readCharData() ([]byte, error) {
var tok xml.Token
var err error
if tok, err = dec.Token(); err != nil {
return nil, err
}
if t, ok := tok.(xml.CharData); ok {
return []byte(t.Copy()), nil
} else {
return nil, invalidXmlError
}
}
func checkType(val reflect.Value, kinds ...reflect.Kind) error {
if len(kinds) == 0 {
return nil
}
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
match := false
for _, kind := range kinds {
if val.Kind() == kind {
match = true
break
}
}
if !match {
return TypeMismatchError(fmt.Sprintf("error: type mismatch - can't unmarshal %v to %v",
val.Kind(), kinds[0]))
}
return nil
}

View File

@ -1,164 +0,0 @@
package xmlrpc
import (
"bytes"
"encoding/xml"
"fmt"
"reflect"
"strconv"
"time"
)
type encodeFunc func(reflect.Value) ([]byte, error)
func marshal(v interface{}) ([]byte, error) {
if v == nil {
return []byte{}, nil
}
val := reflect.ValueOf(v)
return encodeValue(val)
}
func encodeValue(val reflect.Value) ([]byte, error) {
var b []byte
var err error
if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
if val.IsNil() {
return []byte("<value/>"), nil
}
val = val.Elem()
}
switch val.Kind() {
case reflect.Struct:
switch val.Interface().(type) {
case time.Time:
t := val.Interface().(time.Time)
b = []byte(fmt.Sprintf("<dateTime.iso8601>%s</dateTime.iso8601>", t.Format(iso8601)))
default:
b, err = encodeStruct(val)
}
case reflect.Map:
b, err = encodeMap(val)
case reflect.Slice:
b, err = encodeSlice(val)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
b = []byte(fmt.Sprintf("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
case reflect.Float32, reflect.Float64:
b = []byte(fmt.Sprintf("<double>%s</double>",
strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits())))
case reflect.Bool:
if val.Bool() {
b = []byte("<boolean>1</boolean>")
} else {
b = []byte("<boolean>0</boolean>")
}
case reflect.String:
var buf bytes.Buffer
xml.Escape(&buf, []byte(val.String()))
if _, ok := val.Interface().(Base64); ok {
b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
} else {
b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
}
default:
return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
}
if err != nil {
return nil, err
}
return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
}
func encodeStruct(val reflect.Value) ([]byte, error) {
var b bytes.Buffer
b.WriteString("<struct>")
t := val.Type()
for i := 0; i < t.NumField(); i++ {
b.WriteString("<member>")
f := t.Field(i)
name := f.Tag.Get("xmlrpc")
if name == "" {
name = f.Name
}
b.WriteString(fmt.Sprintf("<name>%s</name>", name))
p, err := encodeValue(val.FieldByName(f.Name))
if err != nil {
return nil, err
}
b.Write(p)
b.WriteString("</member>")
}
b.WriteString("</struct>")
return b.Bytes(), nil
}
func encodeMap(val reflect.Value) ([]byte, error) {
var t = val.Type()
if t.Key().Kind() != reflect.String {
return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported")
}
var b bytes.Buffer
b.WriteString("<struct>")
keys := val.MapKeys()
for i := 0; i < val.Len(); i++ {
key := keys[i]
kval := val.MapIndex(key)
b.WriteString("<member>")
b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
p, err := encodeValue(kval)
if err != nil {
return nil, err
}
b.Write(p)
b.WriteString("</member>")
}
b.WriteString("</struct>")
return b.Bytes(), nil
}
func encodeSlice(val reflect.Value) ([]byte, error) {
var b bytes.Buffer
b.WriteString("<array><data>")
for i := 0; i < val.Len(); i++ {
p, err := encodeValue(val.Index(i))
if err != nil {
return nil, err
}
b.Write(p)
}
b.WriteString("</data></array>")
return b.Bytes(), nil
}

View File

@ -1,57 +0,0 @@
package xmlrpc
import (
"bytes"
"fmt"
"net/http"
)
func NewRequest(url string, method string, args interface{}) (*http.Request, error) {
var t []interface{}
var ok bool
if t, ok = args.([]interface{}); !ok {
if args != nil {
t = []interface{}{args}
}
}
body, err := EncodeMethodCall(method, t...)
if err != nil {
return nil, err
}
request, err := http.NewRequest("POST", url, bytes.NewReader(body))
if err != nil {
return nil, err
}
request.Header.Set("Content-Type", "text/xml")
request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
return request, nil
}
func EncodeMethodCall(method string, args ...interface{}) ([]byte, error) {
var b bytes.Buffer
b.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
b.WriteString(fmt.Sprintf("<methodCall><methodName>%s</methodName>", method))
if args != nil {
b.WriteString("<params>")
for _, arg := range args {
p, err := marshal(arg)
if err != nil {
return nil, err
}
b.WriteString(fmt.Sprintf("<param>%s</param>", string(p)))
}
b.WriteString("</params>")
}
b.WriteString("</methodCall>")
return b.Bytes(), nil
}

View File

@ -1,52 +0,0 @@
package xmlrpc
import (
"regexp"
)
var (
faultRx = regexp.MustCompile(`<fault>(\s|\S)+</fault>`)
)
type failedResponse struct {
Code int `xmlrpc:"faultCode"`
Error string `xmlrpc:"faultString"`
}
func (r *failedResponse) err() error {
return &xmlrpcError{
code: r.Code,
err: r.Error,
}
}
type Response struct {
data []byte
}
func NewResponse(data []byte) *Response {
return &Response{
data: data,
}
}
func (r *Response) Failed() bool {
return faultRx.Match(r.data)
}
func (r *Response) Err() error {
failedResp := new(failedResponse)
if err := unmarshal(r.data, failedResp); err != nil {
return err
}
return failedResp.err()
}
func (r *Response) Unmarshal(v interface{}) error {
if err := unmarshal(r.data, v); err != nil {
return err
}
return nil
}

View File

@ -1,25 +0,0 @@
# encoding: utf-8
require "xmlrpc/server"
class Service
def time
Time.now
end
def upcase(s)
s.upcase
end
def sum(x, y)
x + y
end
def error
raise XMLRPC::FaultException.new(500, "Server error")
end
end
server = XMLRPC::Server.new 5001, 'localhost'
server.add_handler "service", Service.new
server.serve

View File

@ -1,19 +0,0 @@
package xmlrpc
import (
"fmt"
)
// xmlrpcError represents errors returned on xmlrpc request.
type xmlrpcError struct {
code int
err string
}
// Error() method implements Error interface
func (e *xmlrpcError) Error() string {
return fmt.Sprintf("error: \"%s\" code: %d", e.err, e.code)
}
// Base64 represents value in base64 encoding
type Base64 string

View File

@ -1,22 +0,0 @@
The MIT License
Copyright (c) 2013 Petter Rasmussen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,208 +0,0 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/kolo/xmlrpc"
)
const (
// Production is the SystemType to provide to New to use the production XML API
Production SystemType = iota
// Testing is the SystemType to provide to New to use the test XML API
Testing
// LiveDNS is the SystemType to provide to New to use the Live DNS REST API
// Full documentation of the API is available here: http://doc.livedns.gandi.net/
LiveDNS
)
// Enable/disable debug output:
const debug = false
// SystemType is the type used to resolve gandi API address
type SystemType int
// Url returns the actual gandi API base URL
func (s SystemType) Url() string {
if s == Production {
return "https://rpc.gandi.net/xmlrpc/"
}
if s == LiveDNS {
return "https://dns.api.gandi.net/api/v5/"
}
return "https://rpc.ote.gandi.net/xmlrpc/"
}
// Client holds the configuration of a gandi client
type Client struct {
// Key is the API key to provide to gandi
Key string
// Url is the base URL of the gandi API
Url string
}
// New creates a new gandi client for the given system
func New(apiKey string, system SystemType) *Client {
return &Client{
Key: apiKey,
Url: system.Url(),
}
}
// Call performs an acual XML RPC call to the gandi API
func (c *Client) Call(serviceMethod string, args []interface{}, reply interface{}) error {
rpc, err := xmlrpc.NewClient(c.Url, nil)
if err != nil {
return err
}
return rpc.Call(serviceMethod, args, reply)
}
// DoRest performs a request to gandi LiveDNS api and optionnally decodes the reply
func (c *Client) DoRest(req *http.Request, decoded interface{}) (*http.Response, error) {
if decoded != nil {
req.Header.Set("Accept", "application/json")
}
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusUnauthorized {
return nil, fmt.Errorf("the server returned unauthorized code. Your API key might be invalid or have expired")
}
//
defer func() { err = resp.Body.Close() }()
if decoded != nil {
b, e := ioutil.ReadAll(resp.Body)
if e != nil {
return nil, e
}
if len(b) > 0 {
e = json.Unmarshal(b, decoded)
if e != nil {
return nil, e
}
if resp.StatusCode == http.StatusBadRequest {
return nil, fmt.Errorf("the server returned 400 bad request (%v)", string(b))
}
}
resp.Body = ioutil.NopCloser(bytes.NewReader(b))
}
return resp, err
}
// NewJSONRequest creates a new authenticated to gandi live DNS REST API.
// If data is not null, it will be encoded as json and prodived in the request body
func (c *Client) NewJSONRequest(method string, url string, data interface{}) (*http.Request, error) {
var reader io.Reader
if data != nil {
b, err := json.Marshal(data)
if err != nil {
return nil, err
}
reader = bytes.NewReader(b)
}
req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", strings.TrimRight(c.Url, "/"), strings.TrimLeft(url, "/")), reader)
if err != nil {
return nil, err
}
if data != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("X-Api-Key", c.Key)
return req, nil
}
// Get performs a Get request to gandi Live DNS api and decodes the returned data if a not null decoded pointer is provided
func (c *Client) Get(URI string, decoded interface{}) (*http.Response, error) {
req, err := c.NewJSONRequest("GET", URI, nil)
if err != nil {
return nil, err
}
resp, err := c.DoRest(req, decoded)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Unexpected http code %d on URL %v. expecting %d", resp.StatusCode, resp.Request.URL, http.StatusOK)
}
return resp, err
}
// Delete performs a Delete request to gandi Live DNS api and decodes the returned data if a not null decoded pointer is provided
func (c *Client) Delete(URI string, decoded interface{}) (*http.Response, error) {
req, err := c.NewJSONRequest("DELETE", URI, nil)
if err != nil {
return nil, err
}
resp, err := c.DoRest(req, decoded)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusNoContent {
return nil, fmt.Errorf("Unexpected http code %d on URL %v. expecting %d", resp.StatusCode, resp.Request.URL, http.StatusNoContent)
}
return resp, err
}
// Post performs a Post request request to gandi Live DNS api
// - with data encoded as JSON if a not null data pointer is provided
// - decodes the returned data if a not null decoded pointer is provided
// - ensures the status code is an HTTP accepted
func (c *Client) Post(URI string, data interface{}, decoded interface{}) (*http.Response, error) {
if debug {
fmt.Printf("DEBUG: POST URI=%s\n", URI)
}
req, err := c.NewJSONRequest("POST", URI, data)
if err != nil {
return nil, err
}
if debug {
fmt.Printf("DEBUG: POST req=%v decoded=%v\n", req, decoded)
}
resp, err := c.DoRest(req, decoded)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusCreated {
return nil, fmt.Errorf("Unexpected http code %d on URL %v. expecting %d", resp.StatusCode, resp.Request.URL, http.StatusCreated)
}
return resp, err
}
// Put performs a Put request to gandi Live DNS api
// - with data encoded as JSON if a not null data pointer is provided
// - decodes the returned data if a not null decoded pointer is provided
func (c *Client) Put(URI string, data interface{}, decoded interface{}) (*http.Response, error) {
req, err := c.NewJSONRequest("PUT", URI, data)
if err != nil {
return nil, err
}
return c.DoRest(req, decoded)
}
// Patch performs a Patch request to gandi Live DNS api
// - with data encoded as JSON if a not null data pointer is provided
// - decodes the returned data if a not null decoded pointer is provided
// - ensures the status code is an HTTP accepted
func (c *Client) Patch(URI string, data interface{}, decoded interface{}) (*http.Response, error) {
req, err := c.NewJSONRequest("PATCH", URI, data)
if err != nil {
return nil, err
}
resp, err := c.DoRest(req, decoded)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusAccepted {
return nil, fmt.Errorf("Unexpected http code %d on URL %v. expecting %d", resp.StatusCode, resp.Request.URL, http.StatusAccepted)
}
return resp, err
}

View File

@ -1,87 +0,0 @@
package domain
import (
"github.com/prasmussen/gandi-api/client"
"github.com/prasmussen/gandi-api/operation"
)
type Domain struct {
*client.Client
}
func New(c *client.Client) *Domain {
return &Domain{c}
}
// Check the availability of some domain
func (self *Domain) Available(name string) (string, error) {
var result map[string]interface{}
domain := []string{name}
params := []interface{}{self.Key, domain}
if err := self.Call("domain.available", params, &result); err != nil {
return "", err
}
return result[name].(string), nil
}
// Get domain information
func (self *Domain) Info(name string) (*DomainInfo, error) {
var res map[string]interface{}
params := []interface{}{self.Key, name}
if err := self.Call("domain.info", params, &res); err != nil {
return nil, err
}
return ToDomainInfo(res), nil
}
// List domains associated to the contact represented by apikey
func (self *Domain) List() ([]*DomainInfoBase, error) {
opts := &struct {
Page int `xmlrpc:"page"`
}{0}
const perPage = 100
params := []interface{}{self.Key, opts}
domains := make([]*DomainInfoBase, 0)
for {
var res []interface{}
if err := self.Call("domain.list", params, &res); err != nil {
return nil, err
}
for _, r := range res {
domain := ToDomainInfoBase(r.(map[string]interface{}))
domains = append(domains, domain)
}
if len(res) < perPage {
break
}
opts.Page++
}
return domains, nil
}
// Count domains associated to the contact represented by apikey
func (self *Domain) Count() (int64, error) {
var result int64
params := []interface{}{self.Key}
if err := self.Call("domain.count", params, &result); err != nil {
return -1, err
}
return result, nil
}
// Create a domain
func (self *Domain) Create(name, contactHandle string, years int64) (*operation.OperationInfo, error) {
var res map[string]interface{}
createArgs := map[string]interface{}{
"admin": contactHandle,
"bill": contactHandle,
"owner": contactHandle,
"tech": contactHandle,
"duration": years,
}
params := []interface{}{self.Key, name, createArgs}
if err := self.Call("domain.create", params, &res); err != nil {
return nil, err
}
return operation.ToOperationInfo(res), nil
}

View File

@ -1,24 +0,0 @@
package zone
import (
"github.com/prasmussen/gandi-api/client"
"github.com/prasmussen/gandi-api/operation"
)
type Nameservers struct {
*client.Client
}
func New(c *client.Client) *Nameservers {
return &Nameservers{c}
}
// Set the current zone of a domain
func (self *Nameservers) Set(domainName string, nameservers []string) (*operation.OperationInfo, error) {
var res map[string]interface{}
params := []interface{}{self.Key, domainName, nameservers}
if err := self.Call("domain.nameservers.set", params, &res); err != nil {
return nil, err
}
return operation.ToOperationInfo(res), nil
}

View File

@ -1,57 +0,0 @@
package domain
import (
"time"
)
type DomainInfoBase struct {
AuthInfo string
DateCreated time.Time
DateRegistryCreation time.Time
DateRegistryEnd time.Time
DateUpdated time.Time
Fqdn string
Id int64
Status []string
Tld string
}
type DomainInfoExtra struct {
DateDelete time.Time
DateHoldBegin time.Time
DateHoldEnd time.Time
DatePendingDeleteEnd time.Time
DateRenewBegin time.Time
DateRestoreEnd time.Time
Nameservers []string
Services []string
ZoneId int64
Autorenew *AutorenewInfo
Contacts *ContactInfo
}
type DomainInfo struct {
*DomainInfoBase
*DomainInfoExtra
}
type AutorenewInfo struct {
Active bool
Contact string
Id int64
ProductId int64
ProductTypeId int64
}
type ContactInfo struct {
Admin *ContactDetails
Bill *ContactDetails
Owner *ContactDetails
Reseller *ContactDetails
Tech *ContactDetails
}
type ContactDetails struct {
Handle string
Id int64
}

View File

@ -1,69 +0,0 @@
package domain
import (
"github.com/prasmussen/gandi-api/util"
)
func ToDomainInfoBase(res map[string]interface{}) *DomainInfoBase {
return &DomainInfoBase{
AuthInfo: util.ToString(res["authinfo"]),
DateCreated: util.ToTime(res["date_created"]),
DateRegistryCreation: util.ToTime(res["date_registry_creation"]),
DateRegistryEnd: util.ToTime(res["date_registry_end"]),
DateUpdated: util.ToTime(res["date_updated"]),
Fqdn: util.ToString(res["fqdn"]),
Id: util.ToInt64(res["id"]),
Status: util.ToStringSlice(util.ToInterfaceSlice(res["status"])),
Tld: util.ToString(res["tld"]),
}
}
func ToDomainInfoExtra(res map[string]interface{}) *DomainInfoExtra {
return &DomainInfoExtra{
DateDelete: util.ToTime(res["date_delete"]),
DateHoldBegin: util.ToTime(res["date_hold_begin"]),
DateHoldEnd: util.ToTime(res["date_hold_end"]),
DatePendingDeleteEnd: util.ToTime(res["date_pending_delete_end"]),
DateRenewBegin: util.ToTime(res["date_renew_begin"]),
DateRestoreEnd: util.ToTime(res["date_restore_end"]),
Nameservers: util.ToStringSlice(util.ToInterfaceSlice(res["nameservers"])),
Services: util.ToStringSlice(util.ToInterfaceSlice(res["services"])),
ZoneId: util.ToInt64(res["zone_id"]),
Autorenew: toAutorenewInfo(util.ToXmlrpcStruct(res["autorenew"])),
Contacts: toContactInfo(util.ToXmlrpcStruct(res["contacts"])),
}
}
func ToDomainInfo(res map[string]interface{}) *DomainInfo {
return &DomainInfo{
ToDomainInfoBase(res),
ToDomainInfoExtra(res),
}
}
func toAutorenewInfo(res map[string]interface{}) *AutorenewInfo {
return &AutorenewInfo{
Active: util.ToBool(res["active"]),
Contact: util.ToString(res["contact"]),
Id: util.ToInt64(res["id"]),
ProductId: util.ToInt64(res["product_id"]),
ProductTypeId: util.ToInt64(res["product_type_id"]),
}
}
func toContactInfo(res map[string]interface{}) *ContactInfo {
return &ContactInfo{
Admin: toContactDetails(util.ToXmlrpcStruct(res["admin"])),
Bill: toContactDetails(util.ToXmlrpcStruct(res["bill"])),
Owner: toContactDetails(util.ToXmlrpcStruct(res["owner"])),
Reseller: toContactDetails(util.ToXmlrpcStruct(res["reseller"])),
Tech: toContactDetails(util.ToXmlrpcStruct(res["tech"])),
}
}
func toContactDetails(res map[string]interface{}) *ContactDetails {
return &ContactDetails{
Handle: util.ToString(res["handle"]),
Id: util.ToInt64(res["id"]),
}
}

View File

@ -1,129 +0,0 @@
package record
import (
"github.com/prasmussen/gandi-api/client"
)
type Record struct {
*client.Client
}
func New(c *client.Client) *Record {
return &Record{c}
}
// Count number of records for a given zone/version
func (self *Record) Count(zoneId, version int64) (int64, error) {
var result int64
params := []interface{}{self.Key, zoneId, version}
if err := self.Call("domain.zone.record.count", params, &result); err != nil {
return -1, err
}
return result, nil
}
// List records of a version of a DNS zone
func (self *Record) List(zoneId, version int64) ([]*RecordInfo, error) {
opts := &struct {
Page int `xmlrpc:"page"`
}{0}
const perPage = 100
params := []interface{}{self.Key, zoneId, version, opts}
records := make([]*RecordInfo, 0)
for {
var res []interface{}
if err := self.Call("domain.zone.record.list", params, &res); err != nil {
return nil, err
}
for _, r := range res {
record := ToRecordInfo(r.(map[string]interface{}))
records = append(records, record)
}
if len(res) < perPage {
break
}
opts.Page++
}
return records, nil
}
// Add a new record to zone
func (self *Record) Add(args RecordAdd) (*RecordInfo, error) {
var res map[string]interface{}
createArgs := map[string]interface{}{
"name": args.Name,
"type": args.Type,
"value": args.Value,
"ttl": args.Ttl,
}
params := []interface{}{self.Key, args.Zone, args.Version, createArgs}
if err := self.Call("domain.zone.record.add", params, &res); err != nil {
return nil, err
}
return ToRecordInfo(res), nil
}
// Remove a record from a zone/version
func (self *Record) Delete(zoneId, version int64, recordId string) (bool, error) {
var res int64
deleteArgs := map[string]interface{}{"id": recordId}
params := []interface{}{self.Key, zoneId, version, deleteArgs}
if err := self.Call("domain.zone.record.delete", params, &res); err != nil {
return false, err
}
return (res == 1), nil
}
// Update a record from zone/version
func (self *Record) Update(args RecordUpdate) ([]*RecordInfo, error) {
var res []interface{}
updateArgs := map[string]interface{}{
"name": args.Name,
"type": args.Type,
"value": args.Value,
"ttl": args.Ttl,
}
updateOpts := map[string]string{
"id": args.Id,
}
params := []interface{}{self.Key, args.Zone, args.Version, updateOpts, updateArgs}
if err := self.Call("domain.zone.record.update", params, &res); err != nil {
return nil, err
}
records := make([]*RecordInfo, 0)
for _, r := range res {
record := ToRecordInfo(r.(map[string]interface{}))
records = append(records, record)
}
return records, nil
}
// SetRecords replaces the entire zone with new records.
func (self *Record) SetRecords(zone_id, version_id int64, args []RecordSet) ([]*RecordInfo, error) {
var res []interface{}
params := []interface{}{self.Key, zone_id, version_id, args}
if err := self.Call("domain.zone.record.set", params, &res); err != nil {
return nil, err
}
records := make([]*RecordInfo, 0)
for _, r := range res {
record := ToRecordInfo(r.(map[string]interface{}))
records = append(records, record)
}
return records, nil
}
//// Set the current zone of a domain
//func (self *Record) Set(domainName string, id int64) (*domain.DomainInfo, error) {
// var res map[string]interface{}
// params := []interface{}{self.Key, domainName, id}
// if err := self.zone.set", params, &res); err != nil {
// return nil, err
// }
// return domain.ToDomainInfo(res), nil
//}

View File

@ -1,30 +0,0 @@
package record
type RecordInfo struct {
Id string
Name string
Ttl int64
Type string
Value string
}
type RecordAdd struct {
Zone int64 `goptions:"-z, --zone, obligatory, description='Zone id'"`
Version int64 `goptions:"-v, --version, obligatory, description='Zone version'"`
Name string `goptions:"-n, --name, obligatory, description='Record name. Relative name, may contain leading wildcard. @ for empty name'"`
Type string `goptions:"-t, --type, obligatory, description='Record type'"`
Value string `goptions:"-V, --value, obligatory, description='Value for record. Semantics depends on the record type.'"`
Ttl int64 `goptions:"-T, --ttl, description='Time to live, in seconds, between 5 minutes and 30 days'"`
}
type RecordUpdate struct {
Zone int64 `goptions:"-z, --zone, obligatory, description='Zone id'"`
Version int64 `goptions:"-v, --version, obligatory, description='Zone version'"`
Name string `goptions:"-n, --name, obligatory, description='Record name. Relative name, may contain leading wildcard. @ for empty name'"`
Type string `goptions:"-t, --type, obligatory, description='Record type'"`
Value string `goptions:"-V, --value, obligatory, description='Value for record. Semantics depends on the record type.'"`
Ttl int64 `goptions:"-T, --ttl, description='Time to live, in seconds, between 5 minutes and 30 days'"`
Id string `goptions:"-r, --record, obligatory, description='Record id'"`
}
type RecordSet map[string]interface{}

View File

@ -1,15 +0,0 @@
package record
import (
"github.com/prasmussen/gandi-api/util"
)
func ToRecordInfo(res map[string]interface{}) *RecordInfo {
return &RecordInfo{
Id: util.ToString(res["id"]),
Name: util.ToString(res["name"]),
Ttl: util.ToInt64(res["ttl"]),
Type: util.ToString(res["type"]),
Value: util.ToString(res["value"]),
}
}

View File

@ -1,24 +0,0 @@
package zone
import (
"time"
)
type ZoneInfoBase struct {
DateUpdated time.Time
Id int64
Name string
Public bool
Version int64
}
type ZoneInfoExtra struct {
Domains int64
Owner string
Versions []int64
}
type ZoneInfo struct {
*ZoneInfoBase
*ZoneInfoExtra
}

View File

@ -1,30 +0,0 @@
package zone
import (
"github.com/prasmussen/gandi-api/util"
)
func ToZoneInfoBase(res map[string]interface{}) *ZoneInfoBase {
return &ZoneInfoBase{
DateUpdated: util.ToTime(res["date_updated"]),
Id: util.ToInt64(res["id"]),
Name: util.ToString(res["name"]),
Public: util.ToBool(res["public"]),
Version: util.ToInt64(res["version"]),
}
}
func ToZoneInfoExtra(res map[string]interface{}) *ZoneInfoExtra {
return &ZoneInfoExtra{
Domains: util.ToInt64(res["domains"]),
Owner: util.ToString(res["owner"]),
Versions: util.ToIntSlice(util.ToInterfaceSlice(res["versions"])),
}
}
func ToZoneInfo(res map[string]interface{}) *ZoneInfo {
return &ZoneInfo{
ToZoneInfoBase(res),
ToZoneInfoExtra(res),
}
}

View File

@ -1,10 +0,0 @@
package version
import (
"time"
)
type VersionInfo struct {
Id int64
DateCreated time.Time
}

View File

@ -1,13 +0,0 @@
package version
import (
"github.com/prasmussen/gandi-api/util"
)
func ToVersionInfo(res map[string]interface{}) *VersionInfo {
return &VersionInfo{
Id: util.ToInt64(res["id"]),
DateCreated: util.ToTime(res["date_created"]),
}
}

View File

@ -1,68 +0,0 @@
package version
import "github.com/prasmussen/gandi-api/client"
type Version struct {
*client.Client
}
func New(c *client.Client) *Version {
return &Version{c}
}
// Count this zone versions
func (self *Version) Count(zoneId int64) (int64, error) {
var result int64
params := []interface{}{self.Key, zoneId}
if err := self.Call("domain.zone.version.count", params, &result); err != nil {
return -1, err
}
return result, nil
}
// List this zone versions, with their creation date
func (self *Version) List(zoneId int64) ([]*VersionInfo, error) {
var res []interface{}
params := []interface{}{self.Key, zoneId}
if err := self.Call("domain.zone.version.list", params, &res); err != nil {
return nil, err
}
versions := make([]*VersionInfo, 0)
for _, r := range res {
version := ToVersionInfo(r.(map[string]interface{}))
versions = append(versions, version)
}
return versions, nil
}
// Create a new version from another version. This will duplicate the versions records
func (self *Version) New(zoneId, version int64) (int64, error) {
var res int64
params := []interface{}{self.Key, zoneId, version}
if err := self.Call("domain.zone.version.new", params, &res); err != nil {
return -1, err
}
return res, nil
}
// Delete a specific version
func (self *Version) Delete(zoneId, version int64) (bool, error) {
var res bool
params := []interface{}{self.Key, zoneId, version}
if err := self.Call("domain.zone.version.delete", params, &res); err != nil {
return false, err
}
return res, nil
}
// Set the active version of a zone
func (self *Version) Set(zoneId, version int64) (bool, error) {
var res bool
params := []interface{}{self.Key, zoneId, version}
if err := self.Call("domain.zone.version.set", params, &res); err != nil {
return false, err
}
return res, nil
}

View File

@ -1,81 +0,0 @@
package zone
import (
"github.com/prasmussen/gandi-api/client"
"github.com/prasmussen/gandi-api/domain"
)
type Zone struct {
*client.Client
}
func New(c *client.Client) *Zone {
return &Zone{c}
}
// Counts accessible zones
func (self *Zone) Count() (int64, error) {
var result int64
params := []interface{}{self.Key}
if err := self.Call("domain.zone.count", params, &result); err != nil {
return -1, err
}
return result, nil
}
// Get zone information
func (self *Zone) Info(id int64) (*ZoneInfo, error) {
var res map[string]interface{}
params := []interface{}{self.Key, id}
if err := self.Call("domain.zone.info", params, &res); err != nil {
return nil, err
}
return ToZoneInfo(res), nil
}
// List accessible DNS zones.
func (self *Zone) List() ([]*ZoneInfoBase, error) {
var res []interface{}
params := []interface{}{self.Key}
if err := self.Call("domain.zone.list", params, &res); err != nil {
return nil, err
}
zones := make([]*ZoneInfoBase, 0)
for _, r := range res {
zone := ToZoneInfoBase(r.(map[string]interface{}))
zones = append(zones, zone)
}
return zones, nil
}
// Create a zone
func (self *Zone) Create(name string) (*ZoneInfo, error) {
var res map[string]interface{}
createArgs := map[string]interface{}{"name": name}
params := []interface{}{self.Key, createArgs}
if err := self.Call("domain.zone.create", params, &res); err != nil {
return nil, err
}
return ToZoneInfo(res), nil
}
// Delete a zone
func (self *Zone) Delete(id int64) (bool, error) {
var res bool
params := []interface{}{self.Key, id}
if err := self.Call("domain.zone.delete", params, &res); err != nil {
return false, err
}
return res, nil
}
// Set the current zone of a domain
func (self *Zone) Set(domainName string, id int64) (*domain.DomainInfo, error) {
var res map[string]interface{}
params := []interface{}{self.Key, domainName, id}
if err := self.Call("domain.zone.set", params, &res); err != nil {
return nil, err
}
return domain.ToDomainInfo(res), nil
}

View File

@ -1,35 +0,0 @@
package domain
import (
"fmt"
"github.com/prasmussen/gandi-api/client"
"github.com/prasmussen/gandi-api/live_dns/record"
)
// Domain holds the domain client stucture
type Domain struct {
*client.Client
}
// New instanciates a new Domain client
func New(c *client.Client) *Domain {
return &Domain{c}
}
// List domains associated to the contact represented by apikey
func (d *Domain) List() (domains []*InfoBase, err error) {
_, err = d.Get("/domains", &domains)
return
}
// Info Gets domain information
func (d *Domain) Info(name string) (infos *Info, err error) {
_, err = d.Get(fmt.Sprintf("/domains/%s", name), &infos)
return
}
// Records gets a record client for the current domain
func (d *Domain) Records(name string) record.Manager {
return record.New(d.Client, fmt.Sprintf("/domains/%s", name))
}

View File

@ -1,32 +0,0 @@
package domain
import "github.com/google/uuid"
// InfoBase holds the basic domain informations returned by the domain listing
type InfoBase struct {
// Fqdn stands for Fully Qualified Domain Name. It is the domain name managed by gandi (<domain>.<tld>)
Fqdn string `json:"fqdn,omitempty"`
// DomainRecordsHref contains the API URL to retrieve all records registered for the domain
DomainRecordsHref string `json:"domain_records_href,omitempty"`
// DomainHref contains the API URL to retrieve full DomainInfo for this domain
DomainHref string `json:"domain_href,omitempty"`
}
// InfoExtra holds the extra domain informations returned by domain details
type InfoExtra struct {
// ZomeUUID is the id of the zone currently configured on this domain
ZoneUUID *uuid.UUID `json:"zone_uuid,omitempty"`
// DomainKeysHref contains the API URL to list DNSSEC keys for this domain
// note: DNSSEC is currently not supported by this library.
DomainKeysHref string `json:"domain_keys_href,omitempty"`
// ZoneHref contains the API URL to retrieve informations about the zone
ZoneHref string `json:"zone_href,omitempty"`
// ZoneRecordsHref contains the API URL to retrieve all records registered for the zone linked to this domain
ZoneRecordsHref string `json:"zone_records_href,omitempty"`
}
// Info holds all domain information
type Info struct {
*InfoBase
*InfoExtra
}

View File

@ -1,185 +0,0 @@
package record
import (
"fmt"
"strings"
"github.com/prasmussen/gandi-api/client"
)
// Record holds the zone client structure
type Record struct {
*client.Client
Prefix string
}
// Creator is an interface to create new record entries
type Creator interface {
// Create creates a new record entry
// possible calls are:
// Create(recordInfo)
// Create(recordInfo, "entry")
// Create(recordInfo, "entry", "type")
// where "entry" matches entry.example.com
// and "type" is the record type (A, CNAME, ...)
Create(recordInfo Info, args ...string) (status *Status, err error)
}
// Updater is an interface to update existing record entries
type Updater interface {
// Update creates a new record entry
// possible calls are:
// Update(recordInfo)
// Update(recordInfo, "entry")
// Update(recordInfo, "entry", "type")
// where "entry" matches entry.example.com
// and "type" is the record type (A, CNAME, ...)
Update(recordInfo Info, args ...string) (status *Status, err error)
}
// Lister is an interface to list existing record entries
type Lister interface {
// List creates a new record entry
// possible calls are:
// List(recordInfo)
// List(recordInfo, "entry")
// List(recordInfo, "entry", "type")
// where "entry" matches entry.example.com
// and "type" is the record type (A, CNAME, ...)
List(args ...string) (list []*Info, err error)
}
// Deleter is an interface to delete existing record entries
type Deleter interface {
// Delete creates a new record entry
// possible calls are:
// Delete(recordInfo)
// Delete(recordInfo, "entry")
// Delete(recordInfo, "entry", "type")
// where "entry" matches entry.example.com
// and "type" is the record type (A, CNAME, ...)
Delete(args ...string) (err error)
}
// Manager is an interface to manage records (for a zone or domain)
type Manager interface {
Creator
Updater
Lister
Deleter
}
// New instanciates a new instance of a Zone client
func New(c *client.Client, prefix string) *Record {
return &Record{c, prefix}
}
func (r *Record) uri(pattern string, paths ...string) string {
args := make([]interface{}, len(paths))
for i, v := range paths {
args[i] = v
}
return fmt.Sprintf("%s/%s",
strings.TrimRight(r.Prefix, "/"),
strings.TrimLeft(fmt.Sprintf(pattern, args...), "/"))
}
func (r *Record) formatCallError(function string, args ...string) error {
format := "unexpected arguments for function %s." +
" supported calls are: %s(), %s(<Name>), %s(<Name>, <Type>)" +
" %s called with"
a := []interface{}{
function,
function, function, function,
function,
}
for _, v := range args {
format = format + " %s"
a = append(a, v)
}
return fmt.Errorf(format, a...)
}
// Create creates a new record entry
// possible calls are:
// Create(recordInfo)
// Create(recordInfo, "entry")
// Create(recordInfo, "entry", "type")
// where "entry" matches entry.example.com
// and "type" is the record type (A, CNAME, ...)
func (r *Record) Create(recordInfo Info, args ...string) (status *Status, err error) {
switch len(args) {
case 0:
_, err = r.Post(r.uri("/records"), recordInfo, &status)
case 1:
_, err = r.Post(r.uri("/records/%s", args...), recordInfo, &status)
case 2:
_, err = r.Post(r.uri("/records/%s/%s", args...), recordInfo, &status)
default:
err = r.formatCallError("Create", args...)
}
return
}
// Update creates a new record entry
// possible calls are:
// Update(recordInfo)
// Update(recordInfo, "entry")
// Update(recordInfo, "entry", "type")
// where "entry" matches entry.example.com
// and "type" is the record type (A, CNAME, ...)
func (r *Record) Update(recordInfo Info, args ...string) (status *Status, err error) {
switch len(args) {
case 0:
_, err = r.Put(r.uri("/records"), recordInfo, &status)
case 1:
_, err = r.Put(r.uri("/records/%s", args...), recordInfo, &status)
case 2:
_, err = r.Put(r.uri("/records/%s/%s", args...), recordInfo, &status)
default:
err = r.formatCallError("Update", args...)
}
return
}
// List creates a new record entry
// possible calls are:
// List(recordInfo)
// List(recordInfo, "entry")
// List(recordInfo, "entry", "type")
// where "entry" matches entry.example.com
// and "type" is the record type (A, CNAME, ...)
func (r *Record) List(args ...string) (list []*Info, err error) {
switch len(args) {
case 0:
_, err = r.Get(r.uri("/records"), &list)
case 1:
_, err = r.Get(r.uri("/records/%s", args...), &list)
case 2:
_, err = r.Get(r.uri("/records/%s/%s", args...), &list)
default:
err = r.formatCallError("List", args...)
}
return
}
// Delete creates a new record entry
// possible calls are:
// Delete(recordInfo)
// Delete(recordInfo, "entry")
// Delete(recordInfo, "entry", "type")
// where "entry" matches entry.example.com
// and "type" is the record type (A, CNAME, ...)
func (r *Record) Delete(args ...string) (err error) {
switch len(args) {
case 0:
_, err = r.Client.Delete(r.uri("/records"), nil)
case 1:
_, err = r.Client.Delete(r.uri("/records/%s", args...), nil)
case 2:
_, err = r.Client.Delete(r.uri("/records/%s/%s", args...), nil)
default:
err = r.formatCallError("Delete", args...)
}
return
}

View File

@ -1,68 +0,0 @@
package record
const (
// A is the type of record that hold a 32-bit IPv4 address, most commonly used to map hostnames to an IP address of the host
A = "A"
// AAAA is the type of record that hold a Returns a 128-bit IPv6 address, most commonly used to map hostnames to an IP address of the host
AAAA = "AAAA"
// CAA is the type of record that hold the DNS Certification Authority Authorization, constraining acceptable CAs for a host/domain
CAA = "CAA"
// CDS is the type of record that hold the child copy of DS record, for transfer to parent
CDS = "CDS"
// CNAME is the type of record that hold the alias of one name to another: the DNS lookup will continue by retrying the lookup with the new name
CNAME = "CNAME"
// DNAME is the type of record that hold the alias for a name and all its subnames, unlike CNAME, which is an alias for only the exact name.
// Like a CNAME record, the DNS lookup will continue by retrying the lookup with the new name
DNAME = "DNAME"
// DS is the type of record that hold the record used to identify the DNSSEC signing key of a delegated zone
DS = "DS"
// LOC is the type of record that specifies a geographical location associated with a domain name
LOC = "LOC"
// MX is the type of record that maps a domain name to a list of message transfer agents for that domain
MX = "MX"
// NS is the type of record that delegates a DNS zone to use the given authoritative name servers
NS = "NS"
// PTR is the type of record that hold a pointer to a canonical name. Unlike a CNAME, DNS processing stops and just the name is returned.
// The most common use is for implementing reverse DNS lookups, but other uses include such things as DNS-SD.
PTR = "PTR"
// SPF (99) (from RFC 4408) was specified as part of the Sender Policy Framework protocol as an alternative to storing SPF data in TXT records,
// using the same format. It was later found that the majority of SPF deployments lack proper support for this record type, and support for it was discontinued in RFC 7208
SPF = "SPF"
// SRV is the type of record that hold the generalized service location record, used for newer protocols instead of creating protocol-specific records such as MX.
SRV = "SRV"
// SSHFP is the type of record that hold resource record for publishing SSH public host key fingerprints in the DNS System,
// in order to aid in verifying the authenticity of the host. RFC 6594 defines ECC SSH keys and SHA-256 hashes
SSHFP = "SSHFP"
// TLSA is the type of record that hold a record for DANE.
// record for DANE. RFC 6698 defines "The TLSA DNS resource record is used to associate a TLS server
// certificate or public key with the domain name where the record is found, thus forming a 'TLSA certificate association'".
TLSA = "TLSA"
// TXT is the type of record that hold human readable text.
// Since the early 1990s, however, this record more often carries machine-readable data,
// such as specified by RFC 1464, opportunistic encryption, Sender Policy Framework, DKIM, DMARC, DNS-SD, etc.
TXT = "TXT"
// WKS is the type of record that describe well-known services supported by a host. Not used in practice.
// The current recommendation and practice is to determine whether a service is supported on an IP address by trying to connect to it.
// SMTP is even prohibited from using WKS records in MX processing
WKS = "WKS"
)
// Info holds the record informations for a single record entry
type Info struct {
// Href contains the API URL to get the record informations
Href string `json:"rrset_href,omitempty"`
// Name contains name of the subdomain for this record
Name string `json:"rrset_name,omitempty"`
// TTL contains the life time of the record.
TTL int64 `json:"rrset_ttl,omitempty"`
// Type contains the DNS record type
Type string `json:"rrset_type,omitempty"`
// Values contains the DNS values resolved by the record
Values []string `json:"rrset_values,omitempty"`
}
// Status holds the data returned by the API in case of record creation or update
type Status struct {
// Message is the status message returned by the gandi api
Message string `json:"message"`
}

View File

@ -1,56 +0,0 @@
package zone
import "github.com/google/uuid"
// Source: http://knowledgelayer.softlayer.com/faq/what-does-serial-refresh-retry-expire-minimum-and-ttl-mean
// Info holds the DNS zone informations
type Info struct {
// Retry is the amount of time in seconds that a domain's primary name server (or servers)
// should wait if an attempt to refresh by a secondary name server failed before
// attempting to refresh a domain's zone with that secondary name server again.
Retry int `json:"retry,omitempty"`
// UUID is the zone id
UUID *uuid.UUID `json:"uuid,omitempty"`
// Minimum is the amount of time in seconds that a domain's resource records are valid.
// This is also known as a minimum TTL, and can be overridden by an individual resource record's TTL
Minimum int `json:"minimum,omitempty"`
// Refresh is the amount of time in seconds that a secondary name server should wait to check for
// a new copy of a DNS zone from the domain's primary name server. If a zone file has changed then
// the secondary DNS server will update it's copy of the zone to match the primary DNS server's zone
Refresh int `json:"refresh,omitempty"`
// Expire is the amount of time in seconds that a secondary name server (or servers) will
// hold a zone before it is no longer considered authoritative
Expire int64 `json:"expire,omitempty"`
// SharingID is currently undocumented in http://doc.livedns.gandi.net/
// But seems to be the ID used to https://admin.gandi.net/domain/<...>
SharingID *uuid.UUID `json:"sharing_id,omitempty"`
// Serial is the revision number of this zone file. Increment this number each time the zone
// file is changed so that the changes will be distributed to any secondary DNS servers
Serial int `json:"serial,omitempty"`
// Email is listed but undocumented in http://doc.livedns.gandi.net/
Email string `json:"email,omitempty"`
// PrimaryNS is the name of the nameserver to be used for this zone
PrimaryNS string `json:"primary_ns,omitempty"`
// Name is the name of the zone
Name string `json:"name,omitempty"`
// DomainsHref contains the API URL to retrieve all domains using this zone
DomainsHref string `json:"domains_href,omitempty"`
// ZoneHref contains the API URL to retrieve full DomainInfo for this domain
ZoneHref string `json:"zone_href,omitempty"`
// ZoneRecordsHref contains the API URL to retrieve all records registered for the zone linked to this zone
ZoneRecordsHref string `json:"zone_records_href,omitempty"`
}
// Status holds the data returned by the API in case of zone update or association to a domain
type Status struct {
// Message is the status message returned by the gandi api
Message string `json:"message"`
}
// CreateStatus holds the data for returned by the API zone creation
type CreateStatus struct {
*Status
// UUID is the created zone ID
UUID *uuid.UUID `json:"uuid"`
}

View File

@ -1,100 +0,0 @@
package zone
import (
"fmt"
"github.com/google/uuid"
"github.com/prasmussen/gandi-api/client"
"github.com/prasmussen/gandi-api/live_dns/domain"
"github.com/prasmussen/gandi-api/live_dns/record"
)
// Enable/disable debug output:
const debug = false
// Zone holds the zone client structure
type Zone struct {
*client.Client
}
// New instanciates a new instance of a Zone client
func New(c *client.Client) *Zone {
return &Zone{c}
}
// List accessible DNS zones.
func (z *Zone) List() (zones []*Info, err error) {
_, err = z.Get("/zones", &zones)
return
}
// InfoByUUID Gets zone information from its UUID
func (z *Zone) InfoByUUID(uuid uuid.UUID) (info *Info, err error) {
_, err = z.Get(fmt.Sprintf("/zones/%s", uuid), &info)
if debug {
fmt.Printf("DEBUG: InfoByUUID returned SharingID=%v domain=%v\n", info.SharingID, info.Name)
}
return
}
// Info Gets zone information
func (z *Zone) Info(zoneInfo Info) (info *Info, err error) {
if zoneInfo.UUID == nil {
return nil, fmt.Errorf("can not get zone info %s without an id", zoneInfo.Name)
}
return z.InfoByUUID(*zoneInfo.UUID)
}
// Create creates a new zone
func (z *Zone) Create(zoneInfo Info) (status *CreateStatus, err error) {
if debug {
fmt.Printf("DEBUG: Create WILL SET SharingID=%v domain=%v\n", zoneInfo.SharingID, zoneInfo.Name)
}
_, err = z.Post(fmt.Sprintf("/zones?sharing_id=%s", zoneInfo.SharingID), zoneInfo, &status)
return
}
// Update updates an existing zone
func (z *Zone) Update(zoneInfo Info) (status *Status, err error) {
if zoneInfo.UUID == nil {
return nil, fmt.Errorf("can not update zone %s without an id", zoneInfo.Name)
}
_, err = z.Patch(fmt.Sprintf("/zones/%s", zoneInfo.UUID), zoneInfo, &status)
return
}
// Delete Deletes an existing zone
func (z *Zone) Delete(zoneInfo Info) (err error) {
if zoneInfo.UUID == nil {
return fmt.Errorf("can not update zone %s without an id", zoneInfo.Name)
}
_, err = z.Client.Delete(fmt.Sprintf("/zones/%s", zoneInfo.UUID), nil)
return
}
// Domains lists all domains using a zone
func (z *Zone) Domains(zoneInfo Info) (domains []*domain.InfoBase, err error) {
if zoneInfo.UUID == nil {
return nil, fmt.Errorf("can not get domains on a zone %s without an id", zoneInfo.Name)
}
_, err = z.Get(fmt.Sprintf("/zones/%s/domains", zoneInfo.UUID), &domains)
return
}
// Set the current zone of a domain
func (z *Zone) Set(domainName string, zoneInfo Info) (status *Status, err error) {
if zoneInfo.UUID == nil {
return nil, fmt.Errorf("can not attach a domain %s to a zone %s without an id", domainName, zoneInfo.Name)
}
if debug {
fmt.Printf("DEBUG: Set WILL SET SharingID=%v domain=%s dn=%v\n", zoneInfo.SharingID, domainName, zoneInfo.Name)
}
_, err = z.Post(fmt.Sprintf("/zones/%s/domains/%s", zoneInfo.UUID, domainName), nil, &status)
return
}
// Records gets a record client for the current zone
func (z *Zone) Records(zoneInfo Info) record.Manager {
return record.New(z.Client, fmt.Sprintf("/zones/%s", zoneInfo.UUID))
}

View File

@ -1,60 +0,0 @@
package operation
import "github.com/prasmussen/gandi-api/client"
type Operation struct {
*client.Client
}
func New(c *client.Client) *Operation {
return &Operation{c}
}
// Count operations created by this contact
func (self *Operation) Count() (int64, error) {
var result int64
// params := Params{Params: []interface{}{self.Key}}
params := []interface{}{self.Key}
if err := self.Call("operation.count", params, &result); err != nil {
return -1, err
}
return result, nil
}
// Get operation information
func (self *Operation) Info(id int64) (*OperationInfo, error) {
var res map[string]interface{}
// params := Params{Params: []interface{}{self.Key, id}}
params := []interface{}{self.Key, id}
if err := self.Call("operation.info", params, &res); err != nil {
return nil, err
}
return ToOperationInfo(res), nil
}
// Cancel an operation
func (self *Operation) Cancel(id int64) (bool, error) {
var res bool
// params := Params{Params: []interface{}{self.Key, id}}
params := []interface{}{self.Key, id}
if err := self.Call("operation.cancel", params, &res); err != nil {
return false, err
}
return res, nil
}
// List operations created by this contact
func (self *Operation) List() ([]*OperationInfo, error) {
var res []interface{}
// params := Params{Params: []interface{}{self.Key}}
params := []interface{}{self.Key}
if err := self.Call("operation.list", params, &res); err != nil {
return nil, err
}
operations := make([]*OperationInfo, len(res), len(res))
for i, r := range res {
operations[i] = ToOperationInfo(r.(map[string]interface{}))
}
return operations, nil
}

View File

@ -1,29 +0,0 @@
package operation
import (
"time"
)
type OperationInfo struct {
DateCreated time.Time
DateStart time.Time
DateUpdated time.Time
Eta string
Id int64
LastError string
SessionId int64
Source string
Step string
Type string
Params map[string]interface{}
OperationDetails *OperationDetails
}
type OperationDetails struct {
Id string
Label string
ProductAction string
ProductName string
ProductType string
Quantity int64
}

View File

@ -1,33 +0,0 @@
package operation
import (
"github.com/prasmussen/gandi-api/util"
)
func ToOperationInfo(res map[string]interface{}) *OperationInfo {
return &OperationInfo{
DateCreated: util.ToTime(res["date_created"]),
DateStart: util.ToTime(res["date_start"]),
DateUpdated: util.ToTime(res["date_updated"]),
Eta: util.ToString(res["eta"]),
Id: util.ToInt64(res["id"]),
LastError: util.ToString(res["last_error"]),
SessionId: util.ToInt64(res["session_id"]),
Source: util.ToString(res["source"]),
Step: util.ToString(res["step"]),
Type: util.ToString(res["type"]),
OperationDetails: ToOperationDetails(util.ToXmlrpcStruct(res["infos"])),
Params: util.ToXmlrpcStruct(res["params"]),
}
}
func ToOperationDetails(res map[string]interface{}) *OperationDetails {
return &OperationDetails{
Id: util.ToString(res["id"]),
Label: util.ToString(res["label"]),
ProductAction: util.ToString(res["product_action"]),
ProductName: util.ToString(res["product_name"]),
ProductType: util.ToString(res["product_type"]),
Quantity: util.ToInt64(res["quantity"]),
}
}

View File

@ -1,76 +0,0 @@
package util
import "time"
func ToStringSlice(is []interface{}) []string {
ss := make([]string, len(is), len(is))
for i, _ := range is {
ss[i] = is[i].(string)
}
return ss
}
func ToString(i interface{}) string {
if v, ok := i.(string); ok {
return v
}
return ""
}
func ToTime(i interface{}) time.Time {
if v, ok := i.(time.Time); ok {
return v
}
var t time.Time
return t
}
func ToInterfaceSlice(i interface{}) []interface{} {
if v, ok := i.([]interface{}); ok {
return v
}
var s []interface{}
return s
}
func ToInt64(i interface{}) int64 {
if v, ok := i.(int64); ok {
return v
}
var n int64
return n
}
func ToFloat64(i interface{}) float64 {
if v, ok := i.(float64); ok {
return v
}
var n float64
return n
}
func ToXmlrpcStruct(i interface{}) map[string]interface{} {
if v, ok := i.(map[string]interface{}); ok {
return v
}
var s map[string]interface{}
return s
}
func ToBool(i interface{}) bool {
if v, ok := i.(bool); ok {
return v
}
var b bool
return b
}
func ToIntSlice(is []interface{}) []int64 {
numbers := make([]int64, len(is), len(is))
for i, _ := range is {
numbers[i] = ToInt64(is[i])
}
return numbers
}

19
vendor/modules.txt vendored
View File

@ -147,9 +147,6 @@ github.com/google/go-github/github
# github.com/google/go-querystring v1.0.1-0.20190318165438-c8c88dbee036
## explicit
github.com/google/go-querystring/query
# github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f
## explicit
github.com/google/uuid
# github.com/googleapis/gax-go/v2 v2.0.5
github.com/googleapis/gax-go/v2
# github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1
@ -208,9 +205,6 @@ github.com/hexonet/go-sdk/socketconfig
# github.com/jmespath/go-jmespath v0.0.0-20200310193758-2437e8417af5
## explicit
github.com/jmespath/go-jmespath
# github.com/kolo/xmlrpc v0.0.0-20150413191830-0826b98aaa29
## explicit
github.com/kolo/xmlrpc
# github.com/malexdev/utfutil v0.0.0-20180510171754-00c8d4a8e7a8
## explicit
# github.com/miekg/dns v1.1.27
@ -240,19 +234,6 @@ github.com/pierrec/lz4/internal/xxh32
github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib
# github.com/prasmussen/gandi-api v0.0.0-20180224132202-58d3d4205661
## explicit
github.com/prasmussen/gandi-api/client
github.com/prasmussen/gandi-api/domain
github.com/prasmussen/gandi-api/domain/nameservers
github.com/prasmussen/gandi-api/domain/zone
github.com/prasmussen/gandi-api/domain/zone/record
github.com/prasmussen/gandi-api/domain/zone/version
github.com/prasmussen/gandi-api/live_dns/domain
github.com/prasmussen/gandi-api/live_dns/record
github.com/prasmussen/gandi-api/live_dns/zone
github.com/prasmussen/gandi-api/operation
github.com/prasmussen/gandi-api/util
# github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03
## explicit
github.com/renier/xmlrpc