mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Merge remote-tracking branch 'origin/master' into constellix-improvements
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
manager:
|
||||
max_workers: 2
|
||||
providers:
|
||||
in:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: tests/config
|
||||
dump:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
zones:
|
||||
unit.tests.:
|
||||
sources:
|
||||
- in
|
||||
targets:
|
||||
- dump
|
||||
|
||||
alias.tests.:
|
||||
alias: unit.tests.
|
||||
|
||||
alias-loop.tests.:
|
||||
alias: alias.tests.
|
||||
@@ -109,6 +109,29 @@ cname:
|
||||
- pool: iad
|
||||
type: CNAME
|
||||
value: target.unit.tests.
|
||||
pool-only-in-fallback:
|
||||
dynamic:
|
||||
pools:
|
||||
one:
|
||||
fallback: two
|
||||
values:
|
||||
- value: 1.1.1.1
|
||||
three:
|
||||
values:
|
||||
- value: 3.3.3.3
|
||||
two:
|
||||
values:
|
||||
- value: 2.2.2.2
|
||||
rules:
|
||||
- geos:
|
||||
- NA-US
|
||||
pool: one
|
||||
- geos:
|
||||
- AS-SG
|
||||
pool: three
|
||||
ttl: 300
|
||||
type: A
|
||||
values: [4.4.4.4]
|
||||
real-ish-a:
|
||||
dynamic:
|
||||
pools:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
manager:
|
||||
plan_outputs:
|
||||
"doesntexist":
|
||||
class: octodns.provider.plan.DoesntExist
|
||||
providers: {}
|
||||
zones: {}
|
||||
@@ -0,0 +1,23 @@
|
||||
providers:
|
||||
config:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: tests/config
|
||||
dump:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
geo:
|
||||
class: helpers.GeoProvider
|
||||
nosshfp:
|
||||
class: helpers.NoSshFpProvider
|
||||
|
||||
processors:
|
||||
no-class: {}
|
||||
|
||||
zones:
|
||||
unit.tests.:
|
||||
processors:
|
||||
- noop
|
||||
sources:
|
||||
- in
|
||||
targets:
|
||||
- dump
|
||||
@@ -0,0 +1,25 @@
|
||||
providers:
|
||||
config:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: tests/config
|
||||
dump:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
geo:
|
||||
class: helpers.GeoProvider
|
||||
nosshfp:
|
||||
class: helpers.NoSshFpProvider
|
||||
|
||||
processors:
|
||||
# valid class, but it wants a param and we're not passing it
|
||||
wants-config:
|
||||
class: helpers.WantsConfigProcessor
|
||||
|
||||
zones:
|
||||
unit.tests.:
|
||||
processors:
|
||||
- noop
|
||||
sources:
|
||||
- in
|
||||
targets:
|
||||
- dump
|
||||
@@ -0,0 +1,33 @@
|
||||
providers:
|
||||
config:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: tests/config
|
||||
dump:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
geo:
|
||||
class: helpers.GeoProvider
|
||||
nosshfp:
|
||||
class: helpers.NoSshFpProvider
|
||||
|
||||
processors:
|
||||
# Just testing config so any processor will do
|
||||
noop:
|
||||
class: octodns.processor.base.BaseProcessor
|
||||
|
||||
zones:
|
||||
unit.tests.:
|
||||
processors:
|
||||
- noop
|
||||
sources:
|
||||
- config
|
||||
targets:
|
||||
- dump
|
||||
|
||||
bad.unit.tests.:
|
||||
processors:
|
||||
- doesnt-exist
|
||||
sources:
|
||||
- in
|
||||
targets:
|
||||
- dump
|
||||
@@ -0,0 +1,19 @@
|
||||
manager:
|
||||
max_workers: 2
|
||||
providers:
|
||||
in:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: tests/config
|
||||
dump:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
zones:
|
||||
unit.tests.:
|
||||
sources:
|
||||
- in
|
||||
targets:
|
||||
- dump
|
||||
|
||||
alias.tests.:
|
||||
alias: unit.tests.
|
||||
|
||||
@@ -4,14 +4,17 @@ providers:
|
||||
in:
|
||||
class: octodns.provider.yaml.SplitYamlProvider
|
||||
directory: tests/config/split
|
||||
extension: .tst
|
||||
dump:
|
||||
class: octodns.provider.yaml.SplitYamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
extension: .tst
|
||||
# This is sort of ugly, but it shouldn't hurt anything. It'll just write out
|
||||
# the target file twice where it and dump are both used
|
||||
dump2:
|
||||
class: octodns.provider.yaml.SplitYamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
extension: .tst
|
||||
simple:
|
||||
class: helpers.SimpleProvider
|
||||
geo:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
dname:
|
||||
ttl: 300
|
||||
type: DNAME
|
||||
value: unit.tests.
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
ptr:
|
||||
ttl: 300
|
||||
type: PTR
|
||||
value: foo.bar.com.
|
||||
values: [foo.bar.com.]
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
urlfwd:
|
||||
ttl: 300
|
||||
type: URLFWD
|
||||
values:
|
||||
- code: 302
|
||||
masking: 2
|
||||
path: '/'
|
||||
query: 0
|
||||
target: 'http://www.unit.tests'
|
||||
- code: 301
|
||||
masking: 2
|
||||
path: '/target'
|
||||
query: 0
|
||||
target: 'http://target.unit.tests'
|
||||
@@ -36,6 +36,22 @@
|
||||
- flags: 0
|
||||
tag: issue
|
||||
value: ca.unit.tests
|
||||
_imap._tcp:
|
||||
ttl: 600
|
||||
type: SRV
|
||||
values:
|
||||
- port: 0
|
||||
priority: 0
|
||||
target: .
|
||||
weight: 0
|
||||
_pop3._tcp:
|
||||
ttl: 600
|
||||
type: SRV
|
||||
values:
|
||||
- port: 0
|
||||
priority: 0
|
||||
target: .
|
||||
weight: 0
|
||||
_srv._tcp:
|
||||
ttl: 600
|
||||
type: SRV
|
||||
@@ -56,6 +72,10 @@ cname:
|
||||
ttl: 300
|
||||
type: CNAME
|
||||
value: unit.tests.
|
||||
dname:
|
||||
ttl: 300
|
||||
type: DNAME
|
||||
value: unit.tests.
|
||||
excluded:
|
||||
octodns:
|
||||
excluded:
|
||||
@@ -73,6 +93,34 @@ included:
|
||||
- test
|
||||
type: CNAME
|
||||
value: unit.tests.
|
||||
loc:
|
||||
ttl: 300
|
||||
type: LOC
|
||||
values:
|
||||
- altitude: 20
|
||||
lat_degrees: 31
|
||||
lat_direction: S
|
||||
lat_minutes: 58
|
||||
lat_seconds: 52.1
|
||||
long_degrees: 115
|
||||
long_direction: E
|
||||
long_minutes: 49
|
||||
long_seconds: 11.7
|
||||
precision_horz: 10
|
||||
precision_vert: 2
|
||||
size: 10
|
||||
- altitude: 20
|
||||
lat_degrees: 53
|
||||
lat_direction: N
|
||||
lat_minutes: 13
|
||||
lat_seconds: 10
|
||||
long_degrees: 2
|
||||
long_direction: W
|
||||
long_minutes: 18
|
||||
long_seconds: 26
|
||||
precision_horz: 1000
|
||||
precision_vert: 2
|
||||
size: 10
|
||||
mx:
|
||||
ttl: 300
|
||||
type: MX
|
||||
@@ -104,7 +152,7 @@ naptr:
|
||||
ptr:
|
||||
ttl: 300
|
||||
type: PTR
|
||||
value: foo.bar.com.
|
||||
values: [foo.bar.com.]
|
||||
spf:
|
||||
ttl: 600
|
||||
type: SPF
|
||||
@@ -121,6 +169,20 @@ txt:
|
||||
- Bah bah black sheep
|
||||
- have you any wool.
|
||||
- 'v=DKIM1\;k=rsa\;s=email\;h=sha256\;p=A/kinda+of/long/string+with+numb3rs'
|
||||
urlfwd:
|
||||
ttl: 300
|
||||
type: URLFWD
|
||||
values:
|
||||
- code: 302
|
||||
masking: 2
|
||||
path: '/'
|
||||
query: 0
|
||||
target: 'http://www.unit.tests'
|
||||
- code: 301
|
||||
masking: 2
|
||||
path: '/target'
|
||||
query: 0
|
||||
target: 'http://target.unit.tests'
|
||||
www:
|
||||
ttl: 300
|
||||
type: A
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
manager:
|
||||
max_workers: 2
|
||||
providers:
|
||||
in:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: tests/config
|
||||
dump:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
zones:
|
||||
unit.tests.:
|
||||
sources:
|
||||
- in
|
||||
processors:
|
||||
- missing
|
||||
targets:
|
||||
- dump
|
||||
@@ -0,0 +1,18 @@
|
||||
manager:
|
||||
max_workers: 2
|
||||
providers:
|
||||
in:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: tests/config
|
||||
dump:
|
||||
class: octodns.provider.yaml.YamlProvider
|
||||
directory: env/YAML_TMP_DIR
|
||||
zones:
|
||||
unit.tests.:
|
||||
sources:
|
||||
- in
|
||||
targets:
|
||||
- dump
|
||||
|
||||
alias.tests.:
|
||||
alias: does-not-exists.tests.
|
||||
+16
-16
@@ -177,15 +177,15 @@
|
||||
{
|
||||
"id": "fc12ab34cd5611334422ab3322997656",
|
||||
"type": "SRV",
|
||||
"name": "_srv._tcp.unit.tests",
|
||||
"name": "_imap._tcp.unit.tests",
|
||||
"data": {
|
||||
"service": "_srv",
|
||||
"service": "_imap",
|
||||
"proto": "_tcp",
|
||||
"name": "unit.tests",
|
||||
"priority": 12,
|
||||
"weight": 20,
|
||||
"port": 30,
|
||||
"target": "foo-2.unit.tests"
|
||||
"priority": 0,
|
||||
"weight": 0,
|
||||
"port": 0,
|
||||
"target": "."
|
||||
},
|
||||
"proxiable": true,
|
||||
"proxied": false,
|
||||
@@ -202,15 +202,15 @@
|
||||
{
|
||||
"id": "fc12ab34cd5611334422ab3322997656",
|
||||
"type": "SRV",
|
||||
"name": "_srv._tcp.unit.tests",
|
||||
"name": "_pop3._tcp.unit.tests",
|
||||
"data": {
|
||||
"service": "_srv",
|
||||
"proto": "_tcp",
|
||||
"service": "_imap",
|
||||
"proto": "_pop3",
|
||||
"name": "unit.tests",
|
||||
"priority": 10,
|
||||
"weight": 20,
|
||||
"port": 30,
|
||||
"target": "foo-1.unit.tests"
|
||||
"priority": 0,
|
||||
"weight": 0,
|
||||
"port": 0,
|
||||
"target": "."
|
||||
},
|
||||
"proxiable": true,
|
||||
"proxied": false,
|
||||
@@ -227,10 +227,10 @@
|
||||
],
|
||||
"result_info": {
|
||||
"page": 2,
|
||||
"per_page": 11,
|
||||
"total_pages": 2,
|
||||
"per_page": 10,
|
||||
"total_pages": 3,
|
||||
"count": 10,
|
||||
"total_count": 20
|
||||
"total_count": 24
|
||||
},
|
||||
"success": true,
|
||||
"errors": [],
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"result": [
|
||||
{
|
||||
"id": "fc12ab34cd5611334422ab3322997656",
|
||||
"type": "SRV",
|
||||
"name": "_srv._tcp.unit.tests",
|
||||
"data": {
|
||||
"service": "_srv",
|
||||
"proto": "_tcp",
|
||||
"name": "unit.tests",
|
||||
"priority": 12,
|
||||
"weight": 20,
|
||||
"port": 30,
|
||||
"target": "foo-2.unit.tests"
|
||||
},
|
||||
"proxiable": true,
|
||||
"proxied": false,
|
||||
"ttl": 600,
|
||||
"locked": false,
|
||||
"zone_id": "ff12ab34cd5611334422ab3322997650",
|
||||
"zone_name": "unit.tests",
|
||||
"modified_on": "2017-03-11T18:01:43.940682Z",
|
||||
"created_on": "2017-03-11T18:01:43.940682Z",
|
||||
"meta": {
|
||||
"auto_added": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "fc12ab34cd5611334422ab3322997656",
|
||||
"type": "SRV",
|
||||
"name": "_srv._tcp.unit.tests",
|
||||
"data": {
|
||||
"service": "_srv",
|
||||
"proto": "_tcp",
|
||||
"name": "unit.tests",
|
||||
"priority": 10,
|
||||
"weight": 20,
|
||||
"port": 30,
|
||||
"target": "foo-1.unit.tests"
|
||||
},
|
||||
"proxiable": true,
|
||||
"proxied": false,
|
||||
"ttl": 600,
|
||||
"locked": false,
|
||||
"zone_id": "ff12ab34cd5611334422ab3322997650",
|
||||
"zone_name": "unit.tests",
|
||||
"modified_on": "2017-03-11T18:01:43.940682Z",
|
||||
"created_on": "2017-03-11T18:01:43.940682Z",
|
||||
"meta": {
|
||||
"auto_added": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "372e67954025e0ba6aaa6d586b9e0b59",
|
||||
"type": "LOC",
|
||||
"name": "loc.unit.tests",
|
||||
"content": "IN LOC 31 58 52.1 S 115 49 11.7 E 20m 10m 10m 2m",
|
||||
"proxiable": true,
|
||||
"proxied": false,
|
||||
"ttl": 300,
|
||||
"locked": false,
|
||||
"zone_id": "ff12ab34cd5611334422ab3322997650",
|
||||
"zone_name": "unit.tests",
|
||||
"created_on": "2020-01-28T05:20:00.12345Z",
|
||||
"modified_on": "2020-01-28T05:20:00.12345Z",
|
||||
"data": {
|
||||
"lat_degrees": 31,
|
||||
"lat_minutes": 58,
|
||||
"lat_seconds": 52.1,
|
||||
"lat_direction": "S",
|
||||
"long_degrees": 115,
|
||||
"long_minutes": 49,
|
||||
"long_seconds": 11.7,
|
||||
"long_direction": "E",
|
||||
"altitude": 20,
|
||||
"size": 10,
|
||||
"precision_horz": 10,
|
||||
"precision_vert": 2
|
||||
},
|
||||
"meta": {
|
||||
"auto_added": true,
|
||||
"source": "primary"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "372e67954025e0ba6aaa6d586b9e0b59",
|
||||
"type": "LOC",
|
||||
"name": "loc.unit.tests",
|
||||
"content": "IN LOC 53 14 10 N 2 18 26 W 20m 10m 1000m 2m",
|
||||
"proxiable": true,
|
||||
"proxied": false,
|
||||
"ttl": 300,
|
||||
"locked": false,
|
||||
"zone_id": "ff12ab34cd5611334422ab3322997650",
|
||||
"zone_name": "unit.tests",
|
||||
"created_on": "2020-01-28T05:20:00.12345Z",
|
||||
"modified_on": "2020-01-28T05:20:00.12345Z",
|
||||
"data": {
|
||||
"lat_degrees": 53,
|
||||
"lat_minutes": 13,
|
||||
"lat_seconds": 10,
|
||||
"lat_direction": "N",
|
||||
"long_degrees": 2,
|
||||
"long_minutes": 18,
|
||||
"long_seconds": 26,
|
||||
"long_direction": "W",
|
||||
"altitude": 20,
|
||||
"size": 10,
|
||||
"precision_horz": 1000,
|
||||
"precision_vert": 2
|
||||
},
|
||||
"meta": {
|
||||
"auto_added": true,
|
||||
"source": "primary"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result_info": {
|
||||
"page": 3,
|
||||
"per_page": 10,
|
||||
"total_pages": 3,
|
||||
"count": 4,
|
||||
"total_count": 24
|
||||
},
|
||||
"success": true,
|
||||
"errors": [],
|
||||
"messages": []
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"result": [
|
||||
{
|
||||
"id": "2b1ec1793185213139f22059a165376e",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd0.unit.tests/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "always_use_https"
|
||||
}
|
||||
],
|
||||
"priority": 4,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-29T17:14:28.000000Z",
|
||||
"modified_on": "2021-06-29T17:15:33.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2b1ec1793185213139f22059a165376f",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd0.unit.tests/*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests/",
|
||||
"status_code": 301
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 3,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-29T17:07:12.000000Z",
|
||||
"modified_on": "2021-06-29T17:15:12.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2b1ec1793185213139f22059a165377e",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd1.unit.tests/*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests/",
|
||||
"status_code": 302
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 2,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-28T22:42:27.000000Z",
|
||||
"modified_on": "2021-06-28T22:43:13.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2a9140b17ffb0e6aed826049eec970b8",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd2.unit.tests/*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests/",
|
||||
"status_code": 301
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 1,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
}
|
||||
],
|
||||
"success": true,
|
||||
"errors": [],
|
||||
"messages": []
|
||||
}
|
||||
+56
-37
@@ -64,6 +64,62 @@
|
||||
"roundRobinFailover": [],
|
||||
"pools": [],
|
||||
"poolsDetail": []
|
||||
}, {
|
||||
"id": 1898527,
|
||||
"type": "SRV",
|
||||
"recordType": "srv",
|
||||
"name": "_imap._tcp",
|
||||
"recordOption": "roundRobin",
|
||||
"noAnswer": false,
|
||||
"note": "",
|
||||
"ttl": 600,
|
||||
"gtdRegion": 1,
|
||||
"parentId": 123123,
|
||||
"parent": "domain",
|
||||
"source": "Domain",
|
||||
"modifiedTs": 1565149714387,
|
||||
"value": [{
|
||||
"value": ".",
|
||||
"priority": 0,
|
||||
"weight": 0,
|
||||
"port": 0,
|
||||
"disableFlag": false
|
||||
}],
|
||||
"roundRobin": [{
|
||||
"value": ".",
|
||||
"priority": 0,
|
||||
"weight": 0,
|
||||
"port": 0,
|
||||
"disableFlag": false
|
||||
}]
|
||||
}, {
|
||||
"id": 1898528,
|
||||
"type": "SRV",
|
||||
"recordType": "srv",
|
||||
"name": "_pop3._tcp",
|
||||
"recordOption": "roundRobin",
|
||||
"noAnswer": false,
|
||||
"note": "",
|
||||
"ttl": 600,
|
||||
"gtdRegion": 1,
|
||||
"parentId": 123123,
|
||||
"parent": "domain",
|
||||
"source": "Domain",
|
||||
"modifiedTs": 1565149714387,
|
||||
"value": [{
|
||||
"value": ".",
|
||||
"priority": 0,
|
||||
"weight": 0,
|
||||
"port": 0,
|
||||
"disableFlag": false
|
||||
}],
|
||||
"roundRobin": [{
|
||||
"value": ".",
|
||||
"priority": 0,
|
||||
"weight": 0,
|
||||
"port": 0,
|
||||
"disableFlag": false
|
||||
}]
|
||||
}, {
|
||||
"id": 1808527,
|
||||
"type": "SRV",
|
||||
@@ -523,43 +579,6 @@
|
||||
"roundRobinFailover": [],
|
||||
"pools": [],
|
||||
"poolsDetail": []
|
||||
}, {
|
||||
"id": 1808603,
|
||||
"type": "ANAME",
|
||||
"recordType": "aname",
|
||||
"name": "sub",
|
||||
"recordOption": "roundRobin",
|
||||
"noAnswer": false,
|
||||
"note": "",
|
||||
"ttl": 1800,
|
||||
"gtdRegion": 1,
|
||||
"parentId": 123123,
|
||||
"parent": "domain",
|
||||
"source": "Domain",
|
||||
"modifiedTs": 1565153387855,
|
||||
"value": [{
|
||||
"value": "aname.unit.tests.",
|
||||
"disableFlag": false
|
||||
}],
|
||||
"roundRobin": [{
|
||||
"value": "aname.unit.tests.",
|
||||
"disableFlag": false
|
||||
}],
|
||||
"geolocation": null,
|
||||
"recordFailover": {
|
||||
"disabled": false,
|
||||
"failoverType": 1,
|
||||
"failoverTypeStr": "Normal (always lowest level)",
|
||||
"values": []
|
||||
},
|
||||
"failover": {
|
||||
"disabled": false,
|
||||
"failoverType": 1,
|
||||
"failoverTypeStr": "Normal (always lowest level)",
|
||||
"values": []
|
||||
},
|
||||
"pools": [],
|
||||
"poolsDetail": []
|
||||
}, {
|
||||
"id": 1808520,
|
||||
"type": "A",
|
||||
|
||||
+22
@@ -76,6 +76,28 @@
|
||||
"weight": null,
|
||||
"flags": null,
|
||||
"tag": null
|
||||
}, {
|
||||
"id": 11189896,
|
||||
"type": "SRV",
|
||||
"name": "_imap._tcp",
|
||||
"data": ".",
|
||||
"priority": 0,
|
||||
"port": 0,
|
||||
"ttl": 600,
|
||||
"weight": 0,
|
||||
"flags": null,
|
||||
"tag": null
|
||||
}, {
|
||||
"id": 11189897,
|
||||
"type": "SRV",
|
||||
"name": "_pop3._tcp",
|
||||
"data": ".",
|
||||
"priority": 0,
|
||||
"port": 0,
|
||||
"ttl": 600,
|
||||
"weight": 0,
|
||||
"flags": null,
|
||||
"tag": null
|
||||
}],
|
||||
"links": {
|
||||
"pages": {
|
||||
|
||||
-14
@@ -320,20 +320,6 @@
|
||||
"name": "",
|
||||
"value": "aname.unit.tests.",
|
||||
"id": 11189895,
|
||||
"type": "ANAME"
|
||||
}, {
|
||||
"failover": false,
|
||||
"monitor": false,
|
||||
"sourceId": 123123,
|
||||
"dynamicDns": false,
|
||||
"failed": false,
|
||||
"gtdLocation": "DEFAULT",
|
||||
"hardLink": false,
|
||||
"ttl": 1800,
|
||||
"source": 1,
|
||||
"name": "sub",
|
||||
"value": "aname",
|
||||
"id": 11189896,
|
||||
"type": "ANAME"
|
||||
}, {
|
||||
"failover": false,
|
||||
|
||||
Vendored
+24
-2
@@ -264,10 +264,32 @@
|
||||
"rdata": "v=DKIM1;k=rsa;s=email;h=sha256;p=A\/kinda+of\/long\/string+with+numb3rs",
|
||||
"geozone_id": "0",
|
||||
"last_mod": "2020-01-01 01:01:01"
|
||||
},
|
||||
{
|
||||
"id": "12340025",
|
||||
"domain": "unit.tests",
|
||||
"host": "_imap._tcp",
|
||||
"ttl": "600",
|
||||
"prio": "0",
|
||||
"type": "SRV",
|
||||
"rdata": "0 0 0 .",
|
||||
"geozone_id": "0",
|
||||
"last_mod": "2020-01-01 01:01:01"
|
||||
},
|
||||
{
|
||||
"id": "12340026",
|
||||
"domain": "unit.tests",
|
||||
"host": "_pop3._tcp",
|
||||
"ttl": "600",
|
||||
"prio": "0",
|
||||
"type": "SRV",
|
||||
"rdata": "0 0 0 .",
|
||||
"geozone_id": "0",
|
||||
"last_mod": "2020-01-01 01:01:01"
|
||||
}
|
||||
],
|
||||
"count": 24,
|
||||
"total": 24,
|
||||
"count": 26,
|
||||
"total": 26,
|
||||
"start": 0,
|
||||
"max": 1000,
|
||||
"status": 200
|
||||
|
||||
Vendored
+18
-2
@@ -9,6 +9,22 @@
|
||||
"name": "_srv._tcp.unit.tests",
|
||||
"ttl": 600
|
||||
},
|
||||
{
|
||||
"rdata": [
|
||||
"0 0 0 ."
|
||||
],
|
||||
"type": "SRV",
|
||||
"name": "_imap._tcp.unit.tests",
|
||||
"ttl": 600
|
||||
},
|
||||
{
|
||||
"rdata": [
|
||||
"0 0 0 ."
|
||||
],
|
||||
"type": "SRV",
|
||||
"name": "_pop3._tcp.unit.tests",
|
||||
"ttl": 600
|
||||
},
|
||||
{
|
||||
"rdata": [
|
||||
"2601:644:500:e210:62f8:1dff:feb8:947a"
|
||||
@@ -151,7 +167,7 @@
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"totalElements": 16,
|
||||
"totalElements": 18,
|
||||
"showAll": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+154
@@ -0,0 +1,154 @@
|
||||
[
|
||||
{
|
||||
"rrset_type": "A",
|
||||
"rrset_ttl": 300,
|
||||
"rrset_name": "@",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/%40/A",
|
||||
"rrset_values": [
|
||||
"1.2.3.4",
|
||||
"1.2.3.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "CAA",
|
||||
"rrset_ttl": 3600,
|
||||
"rrset_name": "@",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/%40/CAA",
|
||||
"rrset_values": [
|
||||
"0 issue \"ca.unit.tests\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SSHFP",
|
||||
"rrset_ttl": 3600,
|
||||
"rrset_name": "@",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/%40/SSHFP",
|
||||
"rrset_values": [
|
||||
"1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49",
|
||||
"1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "AAAA",
|
||||
"rrset_ttl": 600,
|
||||
"rrset_name": "aaaa",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/aaaa/AAAA",
|
||||
"rrset_values": [
|
||||
"2601:644:500:e210:62f8:1dff:feb8:947a"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "CNAME",
|
||||
"rrset_ttl": 300,
|
||||
"rrset_name": "cname",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/cname/CNAME",
|
||||
"rrset_values": [
|
||||
"unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "DNAME",
|
||||
"rrset_ttl": 300,
|
||||
"rrset_name": "dname",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/dname/DNAME",
|
||||
"rrset_values": [
|
||||
"unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "CNAME",
|
||||
"rrset_ttl": 3600,
|
||||
"rrset_name": "excluded",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/excluded/CNAME",
|
||||
"rrset_values": [
|
||||
"unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "MX",
|
||||
"rrset_ttl": 300,
|
||||
"rrset_name": "mx",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/mx/MX",
|
||||
"rrset_values": [
|
||||
"10 smtp-4.unit.tests.",
|
||||
"20 smtp-2.unit.tests.",
|
||||
"30 smtp-3.unit.tests.",
|
||||
"40 smtp-1.unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "PTR",
|
||||
"rrset_ttl": 300,
|
||||
"rrset_name": "ptr",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/ptr/PTR",
|
||||
"rrset_values": [
|
||||
"foo.bar.com."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SPF",
|
||||
"rrset_ttl": 600,
|
||||
"rrset_name": "spf",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/spf/SPF",
|
||||
"rrset_values": [
|
||||
"\"v=spf1 ip4:192.168.0.1/16-all\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "TXT",
|
||||
"rrset_ttl": 600,
|
||||
"rrset_name": "txt",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/txt/TXT",
|
||||
"rrset_values": [
|
||||
"\"Bah bah black sheep\"",
|
||||
"\"have you any wool.\"",
|
||||
"\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "A",
|
||||
"rrset_ttl": 300,
|
||||
"rrset_name": "www",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/www/A",
|
||||
"rrset_values": [
|
||||
"2.2.3.6"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "A",
|
||||
"rrset_ttl": 300,
|
||||
"rrset_name": "www.sub",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/www.sub/A",
|
||||
"rrset_values": [
|
||||
"2.2.3.6"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SRV",
|
||||
"rrset_ttl": 600,
|
||||
"rrset_name": "_imap._tcp",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_imap._tcp/SRV",
|
||||
"rrset_values": [
|
||||
"0 0 0 ."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SRV",
|
||||
"rrset_ttl": 600,
|
||||
"rrset_name": "_pop3._tcp",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_pop3._tcp/SRV",
|
||||
"rrset_values": [
|
||||
"0 0 0 ."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SRV",
|
||||
"rrset_ttl": 600,
|
||||
"rrset_name": "_srv._tcp",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_srv._tcp/SRV",
|
||||
"rrset_values": [
|
||||
"10 20 30 foo-1.unit.tests.",
|
||||
"12 20 30 foo-2.unit.tests."
|
||||
]
|
||||
}
|
||||
]
|
||||
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
[
|
||||
{
|
||||
"rrset_type": "A",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "@",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/%40/A",
|
||||
"rrset_values": [
|
||||
"217.70.184.38"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "MX",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "@",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/%40/MX",
|
||||
"rrset_values": [
|
||||
"10 spool.mail.gandi.net.",
|
||||
"50 fb.mail.gandi.net."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "TXT",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "@",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/%40/TXT",
|
||||
"rrset_values": [
|
||||
"\"v=spf1 include:_mailcust.gandi.net ?all\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "CNAME",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "webmail",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/webmail/CNAME",
|
||||
"rrset_values": [
|
||||
"webmail.gandi.net."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "CNAME",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "www",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/www/CNAME",
|
||||
"rrset_values": [
|
||||
"webredir.vip.gandi.net."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SRV",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "_imap._tcp",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_imap._tcp/SRV",
|
||||
"rrset_values": [
|
||||
"0 0 0 ."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SRV",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "_imaps._tcp",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_imaps._tcp/SRV",
|
||||
"rrset_values": [
|
||||
"0 1 993 mail.gandi.net."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SRV",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "_pop3._tcp",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_pop3._tcp/SRV",
|
||||
"rrset_values": [
|
||||
"0 0 0 ."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SRV",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "_pop3s._tcp",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_pop3s._tcp/SRV",
|
||||
"rrset_values": [
|
||||
"10 1 995 mail.gandi.net."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "SRV",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "_submission._tcp",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_submission._tcp/SRV",
|
||||
"rrset_values": [
|
||||
"0 1 465 mail.gandi.net."
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "CDS",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "sub",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/sub/CDS",
|
||||
"rrset_values": [
|
||||
"32128 13 1 6823D9BB1B03DF714DD0EB163E20B341C96D18C0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"rrset_type": "CNAME",
|
||||
"rrset_ttl": 10800,
|
||||
"rrset_name": "relative",
|
||||
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/relative/CNAME",
|
||||
"rrset_values": [
|
||||
"target"
|
||||
]
|
||||
}
|
||||
]
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"domain_keys_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/keys",
|
||||
"fqdn": "unit.tests",
|
||||
"automatic_snapshots": true,
|
||||
"domain_records_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records",
|
||||
"domain_href": "https://api.gandi.net/v5/livedns/domains/unit.tests"
|
||||
}
|
||||
Vendored
+245
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"rrsets": [
|
||||
{
|
||||
"name": "unit.tests",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"1.2.3.4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"1.2.3.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "unit.tests",
|
||||
"type": "NS",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"ns2.gcdn.services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"ns1.gcorelabs.net"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "_imap._tcp",
|
||||
"type": "SRV",
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "_pop3._tcp",
|
||||
"type": "SRV",
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "_srv._tcp",
|
||||
"type": "SRV",
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
12,
|
||||
20,
|
||||
30,
|
||||
"foo-2.unit.tests"
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
"foo-1.unit.tests"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "aaaa.unit.tests",
|
||||
"type": "AAAA",
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"2601:644:500:e210:62f8:1dff:feb8:947a"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cname.unit.tests",
|
||||
"type": "CNAME",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"unit.tests."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "excluded.unit.tests",
|
||||
"type": "CNAME",
|
||||
"ttl": 3600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"unit.tests."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mx.unit.tests",
|
||||
"type": "MX",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
40,
|
||||
"smtp-1.unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
20,
|
||||
"smtp-2.unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
30,
|
||||
"smtp-3.unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
10,
|
||||
"smtp-4.unit.tests."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ptr.unit.tests.",
|
||||
"type": "PTR",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"foo.bar.com"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sub.unit.tests",
|
||||
"type": "NS",
|
||||
"ttl": 3600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"6.2.3.4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"7.2.3.4"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "txt.unit.tests",
|
||||
"type": "TXT",
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"Bah bah black sheep"
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"have you any wool."
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "www.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"2.2.3.6"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "www.sub.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"2.2.3.6"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spf.sub.unit.tests.",
|
||||
"type": "SPF",
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"v=spf1 ip4:192.168.0.1/16-all"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+428
@@ -0,0 +1,428 @@
|
||||
{
|
||||
"rrsets": [
|
||||
{
|
||||
"name": "unit.tests",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"1.2.3.4"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "unit.tests",
|
||||
"type": "NS",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"ns2.gcdn.services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"ns1.gcorelabs.net"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "_imap._tcp",
|
||||
"type": "SRV",
|
||||
"ttl": 1200,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "_pop3._tcp",
|
||||
"type": "SRV",
|
||||
"ttl": 1200,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "_srv._tcp",
|
||||
"type": "SRV",
|
||||
"ttl": 1200,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
12,
|
||||
20,
|
||||
30,
|
||||
"foo-2.unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
"foo-1.unit.tests."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "aaaa.unit.tests",
|
||||
"type": "AAAA",
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"2601:644:500:e210:62f8:1dff:feb8:947a"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cname.unit.tests",
|
||||
"type": "CNAME",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"unit.tests."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mx.unit.tests",
|
||||
"type": "MX",
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
40,
|
||||
"smtp-1.unit.tests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
20,
|
||||
"smtp-2.unit.tests."
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ptr.unit.tests.",
|
||||
"type": "PTR",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"foo.bar.com"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sub.unit.tests",
|
||||
"type": "NS",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"6.2.3.4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"7.2.3.4"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "txt.unit.tests",
|
||||
"type": "TXT",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"\"Bah bah black sheep\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"\"have you any wool.\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\""
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "www.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"2.2.3.6"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "www.sub.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"2.2.3.6"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "geo-A-single.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"filters": [
|
||||
{
|
||||
"type": "geodns"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"strict": false,
|
||||
"type": "default"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"type": "first_n"
|
||||
}
|
||||
],
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"7.7.7.7"
|
||||
],
|
||||
"meta": {
|
||||
"countries": [
|
||||
"RU"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"8.8.8.8"
|
||||
],
|
||||
"meta": {
|
||||
"countries": [
|
||||
"RU"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"9.9.9.9"
|
||||
],
|
||||
"meta": {
|
||||
"continents": [
|
||||
"EU"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"10.10.10.10"
|
||||
],
|
||||
"meta": {
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "geo-no-def.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"filters": [
|
||||
{
|
||||
"type": "geodns"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"strict": false,
|
||||
"type": "default"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"type": "first_n"
|
||||
}
|
||||
],
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"7.7.7.7"
|
||||
],
|
||||
"meta": {
|
||||
"countries": [
|
||||
"RU"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "geo-CNAME.unit.tests.",
|
||||
"type": "CNAME",
|
||||
"ttl": 300,
|
||||
"filters": [
|
||||
{
|
||||
"type": "geodns"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"strict": false,
|
||||
"type": "default"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"type": "first_n"
|
||||
}
|
||||
],
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"ru-1.unit.tests"
|
||||
],
|
||||
"meta": {
|
||||
"countries": [
|
||||
"RU"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"ru-2.unit.tests"
|
||||
],
|
||||
"meta": {
|
||||
"countries": [
|
||||
"RU"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"eu.unit.tests"
|
||||
],
|
||||
"meta": {
|
||||
"continents": [
|
||||
"EU"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
"any.unit.tests."
|
||||
],
|
||||
"meta": {
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "geo-ignore-len-filters.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"filters": [
|
||||
{
|
||||
"limit": 1,
|
||||
"type": "first_n"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"strict": false,
|
||||
"type": "default"
|
||||
}
|
||||
],
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"7.7.7.7"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "geo-ignore-types.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"filters": [
|
||||
{
|
||||
"type": "geodistance"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"type": "first_n"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"strict": false,
|
||||
"type": "default"
|
||||
}
|
||||
],
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"7.7.7.7"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "geo-ignore-limits.unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"filters": [
|
||||
{
|
||||
"type": "geodns"
|
||||
},
|
||||
{
|
||||
"limit": 2,
|
||||
"strict": false,
|
||||
"type": "default"
|
||||
},
|
||||
{
|
||||
"limit": 1,
|
||||
"type": "first_n"
|
||||
}
|
||||
],
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"7.7.7.7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"id": 27757,
|
||||
"name": "unit.test",
|
||||
"nx_ttl": 300,
|
||||
"retry": 5400,
|
||||
"refresh": 0,
|
||||
"expiry": 1209600,
|
||||
"contact": "[email protected]",
|
||||
"serial": 1614752868,
|
||||
"primary_server": "ns1.gcorelabs.net",
|
||||
"records": [
|
||||
{
|
||||
"id": 12419,
|
||||
"name": "unit.test",
|
||||
"type": "ns",
|
||||
"ttl": 300,
|
||||
"short_answers": [
|
||||
"[ns2.gcdn.services]",
|
||||
"[ns1.gcorelabs.net]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dns_servers": [
|
||||
"ns1.gcorelabs.net",
|
||||
"ns2.gcdn.services"
|
||||
]
|
||||
}
|
||||
Vendored
+223
@@ -0,0 +1,223 @@
|
||||
{
|
||||
"records": [
|
||||
{
|
||||
"id": "SOA",
|
||||
"type": "SOA",
|
||||
"name": "@",
|
||||
"value": "hydrogen.ns.hetzner.com. dns.hetzner.com. 1 86400 10800 3600000 3600",
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "NS:sub:0",
|
||||
"type": "NS",
|
||||
"name": "sub",
|
||||
"value": "6.2.3.4",
|
||||
"ttl": 3600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "NS:sub:1",
|
||||
"type": "NS",
|
||||
"name": "sub",
|
||||
"value": "7.2.3.4",
|
||||
"ttl": 3600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "SRV:_srv._tcp:0",
|
||||
"type": "SRV",
|
||||
"name": "_srv._tcp",
|
||||
"value": "10 20 30 foo-1.unit.tests",
|
||||
"ttl": 600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "SRV:_srv._tcp:1",
|
||||
"type": "SRV",
|
||||
"name": "_srv._tcp",
|
||||
"value": "12 20 30 foo-2.unit.tests",
|
||||
"ttl": 600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "TXT:txt:0",
|
||||
"type": "TXT",
|
||||
"name": "txt",
|
||||
"value": "\"Bah bah black sheep\"",
|
||||
"ttl": 600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "TXT:txt:1",
|
||||
"type": "TXT",
|
||||
"name": "txt",
|
||||
"value": "\"have you any wool.\"",
|
||||
"ttl": 600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "A:@:0",
|
||||
"type": "A",
|
||||
"name": "@",
|
||||
"value": "1.2.3.4",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "A:@:1",
|
||||
"type": "A",
|
||||
"name": "@",
|
||||
"value": "1.2.3.5",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "A:www:0",
|
||||
"type": "A",
|
||||
"name": "www",
|
||||
"value": "2.2.3.6",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "MX:mx:0",
|
||||
"type": "MX",
|
||||
"name": "mx",
|
||||
"value": "10 smtp-4.unit.tests",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "MX:mx:1",
|
||||
"type": "MX",
|
||||
"name": "mx",
|
||||
"value": "20 smtp-2.unit.tests",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "MX:mx:2",
|
||||
"type": "MX",
|
||||
"name": "mx",
|
||||
"value": "30 smtp-3.unit.tests",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "MX:mx:3",
|
||||
"type": "MX",
|
||||
"name": "mx",
|
||||
"value": "40 smtp-1.unit.tests",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "AAAA:aaaa:0",
|
||||
"type": "AAAA",
|
||||
"name": "aaaa",
|
||||
"value": "2601:644:500:e210:62f8:1dff:feb8:947a",
|
||||
"ttl": 600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "CNAME:cname:0",
|
||||
"type": "CNAME",
|
||||
"name": "cname",
|
||||
"value": "unit.tests",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "A:www.sub:0",
|
||||
"type": "A",
|
||||
"name": "www.sub",
|
||||
"value": "2.2.3.6",
|
||||
"ttl": 300,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "TXT:txt:2",
|
||||
"type": "TXT",
|
||||
"name": "txt",
|
||||
"value": "v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs",
|
||||
"ttl": 600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "CAA:@:0",
|
||||
"type": "CAA",
|
||||
"name": "@",
|
||||
"value": "0 issue \"ca.unit.tests\"",
|
||||
"ttl": 3600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "CNAME:included:0",
|
||||
"type": "CNAME",
|
||||
"name": "included",
|
||||
"value": "unit.tests",
|
||||
"ttl": 3600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "SRV:_imap._tcp:0",
|
||||
"type": "SRV",
|
||||
"name": "_imap._tcp",
|
||||
"value": "0 0 0 .",
|
||||
"ttl": 600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
},
|
||||
{
|
||||
"id": "SRV:_pop3._tcp:0",
|
||||
"type": "SRV",
|
||||
"name": "_pop3._tcp",
|
||||
"value": "0 0 0 .",
|
||||
"ttl": 600,
|
||||
"zone_id": "unit.tests",
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"zones": [
|
||||
{
|
||||
"id": "unit.tests",
|
||||
"name": "unit.tests",
|
||||
"ttl": 3600,
|
||||
"registrar": "",
|
||||
"legacy_dns_host": "",
|
||||
"legacy_ns": [],
|
||||
"ns": [],
|
||||
"created": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"verified": "",
|
||||
"modified": "0000-00-00 00:00:00.000 +0000 UTC",
|
||||
"project": "",
|
||||
"owner": "",
|
||||
"permission": "",
|
||||
"zone_type": {
|
||||
"id": "",
|
||||
"name": "",
|
||||
"description": "",
|
||||
"prices": null
|
||||
},
|
||||
"status": "verified",
|
||||
"paused": false,
|
||||
"is_secondary_dns": false,
|
||||
"txt_verification": {
|
||||
"name": "",
|
||||
"token": ""
|
||||
},
|
||||
"records_count": null
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"per_page": 100,
|
||||
"previous_page": 1,
|
||||
"next_page": 1,
|
||||
"last_page": 1,
|
||||
"total_entries": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+2
@@ -5,6 +5,8 @@
|
||||
@ 3600 SSHFP 1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73
|
||||
@ 3600 SSHFP 1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49
|
||||
@ 3600 CAA 0 issue ca.unit.tests
|
||||
_imap._tcp 600 SRV 0 0 0 .
|
||||
_pop3._tcp 600 SRV 0 0 0 .
|
||||
_srv._tcp 600 SRV 10 20 30 foo-1.unit.tests.
|
||||
_srv._tcp 600 SRV 12 20 30 foo-2.unit.tests.
|
||||
aaaa 600 AAAA 2601:644:500:e210:62f8:1dff:feb8:947a
|
||||
|
||||
+40
@@ -32,6 +32,22 @@
|
||||
"ttl": 300,
|
||||
"type": "MX"
|
||||
},
|
||||
{
|
||||
"comments": [],
|
||||
"name": "loc.unit.tests.",
|
||||
"records": [
|
||||
{
|
||||
"content": "31 58 52.100 S 115 49 11.700 E 20.00m 10.00m 10.00m 2.00m",
|
||||
"disabled": false
|
||||
},
|
||||
{
|
||||
"content": "53 13 10.000 N 2 18 26.000 W 20.00m 10.00m 1000.00m 2.00m",
|
||||
"disabled": false
|
||||
}
|
||||
],
|
||||
"ttl": 300,
|
||||
"type": "LOC"
|
||||
},
|
||||
{
|
||||
"comments": [],
|
||||
"name": "sub.unit.tests.",
|
||||
@@ -59,6 +75,30 @@
|
||||
"ttl": 300,
|
||||
"type": "A"
|
||||
},
|
||||
{
|
||||
"comments": [],
|
||||
"name": "_imap._tcp.unit.tests.",
|
||||
"records": [
|
||||
{
|
||||
"content": "0 0 0 .",
|
||||
"disabled": false
|
||||
}
|
||||
],
|
||||
"ttl": 600,
|
||||
"type": "SRV"
|
||||
},
|
||||
{
|
||||
"comments": [],
|
||||
"name": "_pop3._tcp.unit.tests.",
|
||||
"records": [
|
||||
{
|
||||
"content": "0 0 0 .",
|
||||
"disabled": false
|
||||
}
|
||||
],
|
||||
"ttl": 600,
|
||||
"type": "SRV"
|
||||
},
|
||||
{
|
||||
"comments": [],
|
||||
"name": "_srv._tcp.unit.tests.",
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@
|
||||
}
|
||||
],
|
||||
"resultInfo": {
|
||||
"totalCount": 12,
|
||||
"totalCount": 13,
|
||||
"offset": 0,
|
||||
"returnedCount": 10
|
||||
}
|
||||
|
||||
+19
-2
@@ -24,11 +24,28 @@
|
||||
"order": "FIXED",
|
||||
"description": "octodns1.test."
|
||||
}
|
||||
},
|
||||
{
|
||||
"ownerName": "octodns1.test.",
|
||||
"rrtype": "APEXALIAS (65282)",
|
||||
"ttl": 3600,
|
||||
"rdata": [
|
||||
"www.octodns1.test."
|
||||
]
|
||||
},
|
||||
{
|
||||
"ownerName": "host1.octodns1.test.",
|
||||
"rrtype": "RRSET (70)",
|
||||
"ttl": 3600,
|
||||
"rdata": [
|
||||
"E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
|
||||
]
|
||||
}
|
||||
|
||||
],
|
||||
"resultInfo": {
|
||||
"totalCount": 12,
|
||||
"totalCount": 13,
|
||||
"offset": 10,
|
||||
"returnedCount": 2
|
||||
"returnedCount": 3
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
"dnssecStatus": "UNSIGNED",
|
||||
"status": "ACTIVE",
|
||||
"owner": "phelpstest",
|
||||
"resourceRecordCount": 5,
|
||||
"resourceRecordCount": 6,
|
||||
"lastModifiedDateTime": "2020-06-19T01:05Z"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,6 +7,10 @@ from __future__ import absolute_import, division, print_function, \
|
||||
|
||||
from shutil import rmtree
|
||||
from tempfile import mkdtemp
|
||||
from logging import getLogger
|
||||
|
||||
from octodns.processor.base import BaseProcessor
|
||||
from octodns.provider.base import BaseProvider
|
||||
|
||||
|
||||
class SimpleSource(object):
|
||||
@@ -90,3 +94,29 @@ class TemporaryDirectory(object):
|
||||
rmtree(self.dirname)
|
||||
else:
|
||||
raise Exception(self.dirname)
|
||||
|
||||
|
||||
class WantsConfigProcessor(BaseProcessor):
|
||||
|
||||
def __init__(self, name, some_config):
|
||||
super(WantsConfigProcessor, self).__init__(name)
|
||||
|
||||
|
||||
class PlannableProvider(BaseProvider):
|
||||
log = getLogger('PlannableProvider')
|
||||
|
||||
SUPPORTS_GEO = False
|
||||
SUPPORTS_DYNAMIC = False
|
||||
SUPPORTS = set(('A',))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PlannableProvider, self).__init__(*args, **kwargs)
|
||||
|
||||
def populate(self, zone, source=False, target=False, lenient=False):
|
||||
pass
|
||||
|
||||
def supports(self, record):
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
+219
-17
@@ -8,16 +8,19 @@ from __future__ import absolute_import, division, print_function, \
|
||||
from os import environ
|
||||
from os.path import dirname, join
|
||||
from six import text_type
|
||||
from unittest import TestCase
|
||||
|
||||
from octodns.record import Record
|
||||
from octodns.manager import _AggregateTarget, MainThreadExecutor, Manager, \
|
||||
ManagerException
|
||||
from octodns.processor.base import BaseProcessor
|
||||
from octodns.record import Create, Delete, Record
|
||||
from octodns.yaml import safe_load
|
||||
from octodns.zone import Zone
|
||||
|
||||
from mock import MagicMock, patch
|
||||
from unittest import TestCase
|
||||
|
||||
from helpers import DynamicProvider, GeoProvider, NoSshFpProvider, \
|
||||
SimpleProvider, TemporaryDirectory
|
||||
PlannableProvider, SimpleProvider, TemporaryDirectory
|
||||
|
||||
config_dir = join(dirname(__file__), 'config')
|
||||
|
||||
@@ -118,12 +121,12 @@ class TestManager(TestCase):
|
||||
environ['YAML_TMP_DIR'] = tmpdir.dirname
|
||||
tc = Manager(get_config_filename('simple.yaml')) \
|
||||
.sync(dry_run=False)
|
||||
self.assertEquals(21, tc)
|
||||
self.assertEquals(26, tc)
|
||||
|
||||
# try with just one of the zones
|
||||
tc = Manager(get_config_filename('simple.yaml')) \
|
||||
.sync(dry_run=False, eligible_zones=['unit.tests.'])
|
||||
self.assertEquals(15, tc)
|
||||
self.assertEquals(20, tc)
|
||||
|
||||
# the subzone, with 2 targets
|
||||
tc = Manager(get_config_filename('simple.yaml')) \
|
||||
@@ -138,18 +141,18 @@ class TestManager(TestCase):
|
||||
# Again with force
|
||||
tc = Manager(get_config_filename('simple.yaml')) \
|
||||
.sync(dry_run=False, force=True)
|
||||
self.assertEquals(21, tc)
|
||||
self.assertEquals(26, tc)
|
||||
|
||||
# Again with max_workers = 1
|
||||
tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \
|
||||
.sync(dry_run=False, force=True)
|
||||
self.assertEquals(21, tc)
|
||||
self.assertEquals(26, tc)
|
||||
|
||||
# Include meta
|
||||
tc = Manager(get_config_filename('simple.yaml'), max_workers=1,
|
||||
include_meta=True) \
|
||||
.sync(dry_run=False, force=True)
|
||||
self.assertEquals(25, tc)
|
||||
self.assertEquals(30, tc)
|
||||
|
||||
def test_eligible_sources(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
@@ -167,6 +170,38 @@ class TestManager(TestCase):
|
||||
.sync(eligible_targets=['foo'])
|
||||
self.assertEquals(0, tc)
|
||||
|
||||
def test_aliases(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
environ['YAML_TMP_DIR'] = tmpdir.dirname
|
||||
# Alias zones with a valid target.
|
||||
tc = Manager(get_config_filename('simple-alias-zone.yaml')) \
|
||||
.sync()
|
||||
self.assertEquals(0, tc)
|
||||
|
||||
# Alias zone with an invalid target.
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
tc = Manager(get_config_filename('unknown-source-zone.yaml')) \
|
||||
.sync()
|
||||
self.assertEquals('Invalid alias zone alias.tests.: source zone '
|
||||
'does-not-exists.tests. does not exist',
|
||||
text_type(ctx.exception))
|
||||
|
||||
# Alias zone that points to another alias zone.
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
tc = Manager(get_config_filename('alias-zone-loop.yaml')) \
|
||||
.sync()
|
||||
self.assertEquals('Invalid alias zone alias-loop.tests.: source '
|
||||
'zone alias.tests. is an alias zone',
|
||||
text_type(ctx.exception))
|
||||
|
||||
# Sync an alias without the zone it refers to
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
tc = Manager(get_config_filename('simple-alias-zone.yaml')) \
|
||||
.sync(eligible_zones=["alias.tests."])
|
||||
self.assertEquals('Zone alias.tests. cannot be sync without zone '
|
||||
'unit.tests. sinced it is aliased',
|
||||
text_type(ctx.exception))
|
||||
|
||||
def test_compare(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
environ['YAML_TMP_DIR'] = tmpdir.dirname
|
||||
@@ -183,13 +218,13 @@ class TestManager(TestCase):
|
||||
fh.write('---\n{}')
|
||||
|
||||
changes = manager.compare(['in'], ['dump'], 'unit.tests.')
|
||||
self.assertEquals(15, len(changes))
|
||||
self.assertEquals(20, len(changes))
|
||||
|
||||
# Compound sources with varying support
|
||||
changes = manager.compare(['in', 'nosshfp'],
|
||||
['dump'],
|
||||
'unit.tests.')
|
||||
self.assertEquals(14, len(changes))
|
||||
self.assertEquals(19, len(changes))
|
||||
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
manager.compare(['nope'], ['dump'], 'unit.tests.')
|
||||
@@ -286,6 +321,41 @@ class TestManager(TestCase):
|
||||
.validate_configs()
|
||||
self.assertTrue('unknown source' in text_type(ctx.exception))
|
||||
|
||||
# Alias zone using an invalid source zone.
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
Manager(get_config_filename('unknown-source-zone.yaml')) \
|
||||
.validate_configs()
|
||||
self.assertTrue('does not exist' in
|
||||
text_type(ctx.exception))
|
||||
|
||||
# Alias zone that points to another alias zone.
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
Manager(get_config_filename('alias-zone-loop.yaml')) \
|
||||
.validate_configs()
|
||||
self.assertTrue('is an alias zone' in
|
||||
text_type(ctx.exception))
|
||||
|
||||
# Valid config file using an alias zone.
|
||||
Manager(get_config_filename('simple-alias-zone.yaml')) \
|
||||
.validate_configs()
|
||||
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
Manager(get_config_filename('unknown-processor.yaml')) \
|
||||
.validate_configs()
|
||||
self.assertTrue('unknown processor' in text_type(ctx.exception))
|
||||
|
||||
def test_get_zone(self):
|
||||
Manager(get_config_filename('simple.yaml')).get_zone('unit.tests.')
|
||||
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
Manager(get_config_filename('simple.yaml')).get_zone('unit.tests')
|
||||
self.assertTrue('missing ending dot' in text_type(ctx.exception))
|
||||
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
Manager(get_config_filename('simple.yaml')) \
|
||||
.get_zone('unknown-zone.tests.')
|
||||
self.assertTrue('Unknown zone name' in text_type(ctx.exception))
|
||||
|
||||
def test_populate_lenient_fallback(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
environ['YAML_TMP_DIR'] = tmpdir.dirname
|
||||
@@ -294,20 +364,152 @@ class TestManager(TestCase):
|
||||
|
||||
class NoLenient(SimpleProvider):
|
||||
|
||||
def populate(self, zone, source=False):
|
||||
def populate(self, zone):
|
||||
pass
|
||||
|
||||
# This should be ok, we'll fall back to not passing it
|
||||
manager._populate_and_plan('unit.tests.', [NoLenient()], [])
|
||||
manager._populate_and_plan('unit.tests.', [], [NoLenient()], [])
|
||||
|
||||
class NoZone(SimpleProvider):
|
||||
class OtherType(SimpleProvider):
|
||||
|
||||
def populate(self, lenient=False):
|
||||
pass
|
||||
def populate(self, zone, lenient=False):
|
||||
raise TypeError('something else')
|
||||
|
||||
# This will blow up, we don't fallback for source
|
||||
with self.assertRaises(TypeError):
|
||||
manager._populate_and_plan('unit.tests.', [NoZone()], [])
|
||||
with self.assertRaises(TypeError) as ctx:
|
||||
manager._populate_and_plan('unit.tests.', [], [OtherType()],
|
||||
[])
|
||||
self.assertEquals('something else', text_type(ctx.exception))
|
||||
|
||||
def test_plan_processors_fallback(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
environ['YAML_TMP_DIR'] = tmpdir.dirname
|
||||
# Only allow a target that doesn't exist
|
||||
manager = Manager(get_config_filename('simple.yaml'))
|
||||
|
||||
class NoProcessors(SimpleProvider):
|
||||
|
||||
def plan(self, zone):
|
||||
pass
|
||||
|
||||
# This should be ok, we'll fall back to not passing it
|
||||
manager._populate_and_plan('unit.tests.', [], [],
|
||||
[NoProcessors()])
|
||||
|
||||
class OtherType(SimpleProvider):
|
||||
|
||||
def plan(self, zone, processors):
|
||||
raise TypeError('something else')
|
||||
|
||||
# This will blow up, we don't fallback for source
|
||||
with self.assertRaises(TypeError) as ctx:
|
||||
manager._populate_and_plan('unit.tests.', [], [],
|
||||
[OtherType()])
|
||||
self.assertEquals('something else', text_type(ctx.exception))
|
||||
|
||||
@patch('octodns.manager.Manager._get_named_class')
|
||||
def test_sync_passes_file_handle(self, mock):
|
||||
plan_output_mock = MagicMock()
|
||||
plan_output_class_mock = MagicMock()
|
||||
plan_output_class_mock.return_value = plan_output_mock
|
||||
mock.return_value = plan_output_class_mock
|
||||
fh_mock = MagicMock()
|
||||
|
||||
Manager(get_config_filename('plan-output-filehandle.yaml')
|
||||
).sync(plan_output_fh=fh_mock)
|
||||
|
||||
# Since we only care about the fh kwarg, and different _PlanOutputs are
|
||||
# are free to require arbitrary kwargs anyway, we concern ourselves
|
||||
# with checking the value of fh only.
|
||||
plan_output_mock.run.assert_called()
|
||||
_, kwargs = plan_output_mock.run.call_args
|
||||
self.assertEqual(fh_mock, kwargs.get('fh'))
|
||||
|
||||
def test_processor_config(self):
|
||||
# Smoke test loading a valid config
|
||||
manager = Manager(get_config_filename('processors.yaml'))
|
||||
self.assertEquals(['noop'], list(manager.processors.keys()))
|
||||
# This zone specifies a valid processor
|
||||
manager.sync(['unit.tests.'])
|
||||
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
# This zone specifies a non-existant processor
|
||||
manager.sync(['bad.unit.tests.'])
|
||||
self.assertTrue('Zone bad.unit.tests., unknown processor: '
|
||||
'doesnt-exist' in text_type(ctx.exception))
|
||||
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
Manager(get_config_filename('processors-missing-class.yaml'))
|
||||
self.assertTrue('Processor no-class is missing class' in
|
||||
text_type(ctx.exception))
|
||||
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
Manager(get_config_filename('processors-wants-config.yaml'))
|
||||
self.assertTrue('Incorrect processor config for wants-config' in
|
||||
text_type(ctx.exception))
|
||||
|
||||
def test_processors(self):
|
||||
manager = Manager(get_config_filename('simple.yaml'))
|
||||
|
||||
targets = [PlannableProvider('prov')]
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
record = Record.new(zone, 'a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
})
|
||||
|
||||
# muck with sources
|
||||
class MockProcessor(BaseProcessor):
|
||||
|
||||
def process_source_zone(self, zone, sources):
|
||||
zone = zone.copy()
|
||||
zone.add_record(record)
|
||||
return zone
|
||||
|
||||
mock = MockProcessor('mock')
|
||||
plans, zone = manager._populate_and_plan('unit.tests.', [mock], [],
|
||||
targets)
|
||||
# Our mock was called and added the record
|
||||
self.assertEquals(record, list(zone.records)[0])
|
||||
# We got a create for the thing added to the expected state (source)
|
||||
self.assertIsInstance(plans[0][1].changes[0], Create)
|
||||
|
||||
# muck with targets
|
||||
class MockProcessor(BaseProcessor):
|
||||
|
||||
def process_target_zone(self, zone, target):
|
||||
zone = zone.copy()
|
||||
zone.add_record(record)
|
||||
return zone
|
||||
|
||||
mock = MockProcessor('mock')
|
||||
plans, zone = manager._populate_and_plan('unit.tests.', [mock], [],
|
||||
targets)
|
||||
# No record added since it's target this time
|
||||
self.assertFalse(zone.records)
|
||||
# We got a delete for the thing added to the existing state (target)
|
||||
self.assertIsInstance(plans[0][1].changes[0], Delete)
|
||||
|
||||
# muck with plans
|
||||
class MockProcessor(BaseProcessor):
|
||||
|
||||
def process_target_zone(self, zone, target):
|
||||
zone = zone.copy()
|
||||
zone.add_record(record)
|
||||
return zone
|
||||
|
||||
def process_plan(self, plans, sources, target):
|
||||
# get rid of the change
|
||||
plans.changes.pop(0)
|
||||
|
||||
mock = MockProcessor('mock')
|
||||
plans, zone = manager._populate_and_plan('unit.tests.', [mock], [],
|
||||
targets)
|
||||
# We planned a delete again, but this time removed it from the plan, so
|
||||
# no plans
|
||||
self.assertFalse(plans)
|
||||
|
||||
|
||||
class TestMainThreadExecutor(TestCase):
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
unicode_literals
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from octodns.processor.acme import AcmeMangingProcessor
|
||||
from octodns.record import Record
|
||||
from octodns.zone import Zone
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
records = {
|
||||
'root-unowned': Record.new(zone, '_acme-challenge', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'value': 'magic bit',
|
||||
}),
|
||||
'sub-unowned': Record.new(zone, '_acme-challenge.sub-unowned', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'value': 'magic bit',
|
||||
}),
|
||||
'not-txt': Record.new(zone, '_acme-challenge.not-txt', {
|
||||
'ttl': 30,
|
||||
'type': 'AAAA',
|
||||
'value': '::1',
|
||||
}),
|
||||
'not-acme': Record.new(zone, 'not-acme', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'value': 'Hello World!',
|
||||
}),
|
||||
'managed': Record.new(zone, '_acme-challenge.managed', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'value': 'magic bit',
|
||||
}),
|
||||
'owned': Record.new(zone, '_acme-challenge.owned', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'values': ['*octoDNS*', 'magic bit'],
|
||||
}),
|
||||
'going-away': Record.new(zone, '_acme-challenge.going-away', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'values': ['*octoDNS*', 'magic bit'],
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
class TestAcmeMangingProcessor(TestCase):
|
||||
|
||||
def test_process_zones(self):
|
||||
acme = AcmeMangingProcessor('acme')
|
||||
|
||||
source = Zone(zone.name, [])
|
||||
# Unrelated stuff that should be untouched
|
||||
source.add_record(records['not-txt'])
|
||||
source.add_record(records['not-acme'])
|
||||
# A managed acme that will have ownership value added
|
||||
source.add_record(records['managed'])
|
||||
|
||||
got = acme.process_source_zone(source)
|
||||
self.assertEquals([
|
||||
'_acme-challenge.managed',
|
||||
'_acme-challenge.not-txt',
|
||||
'not-acme',
|
||||
], sorted([r.name for r in got.records]))
|
||||
managed = None
|
||||
for record in got.records:
|
||||
if record.name.endswith('managed'):
|
||||
managed = record
|
||||
break
|
||||
self.assertTrue(managed)
|
||||
# Ownership was marked with an extra value
|
||||
self.assertEquals(['*octoDNS*', 'magic bit'], record.values)
|
||||
|
||||
existing = Zone(zone.name, [])
|
||||
# Unrelated stuff that should be untouched
|
||||
existing.add_record(records['not-txt'])
|
||||
existing.add_record(records['not-acme'])
|
||||
# Stuff that will be ignored
|
||||
existing.add_record(records['root-unowned'])
|
||||
existing.add_record(records['sub-unowned'])
|
||||
# A managed acme that needs ownership value added
|
||||
existing.add_record(records['managed'])
|
||||
# A managed acme that has ownershp managed
|
||||
existing.add_record(records['owned'])
|
||||
# A managed acme that needs to go away
|
||||
existing.add_record(records['going-away'])
|
||||
|
||||
got = acme.process_target_zone(existing)
|
||||
self.assertEquals([
|
||||
'_acme-challenge.going-away',
|
||||
'_acme-challenge.managed',
|
||||
'_acme-challenge.not-txt',
|
||||
'_acme-challenge.owned',
|
||||
'not-acme'
|
||||
], sorted([r.name for r in got.records]))
|
||||
@@ -0,0 +1,90 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
unicode_literals
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from octodns.processor.filter import TypeAllowlistFilter, TypeRejectlistFilter
|
||||
from octodns.record import Record
|
||||
from octodns.zone import Zone
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
for record in [
|
||||
Record.new(zone, 'a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
}),
|
||||
Record.new(zone, 'aaaa', {
|
||||
'ttl': 30,
|
||||
'type': 'AAAA',
|
||||
'value': '::1',
|
||||
}),
|
||||
Record.new(zone, 'txt', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'value': 'Hello World!',
|
||||
}),
|
||||
Record.new(zone, 'a2', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '2.3.4.5',
|
||||
}),
|
||||
Record.new(zone, 'txt2', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'value': 'That will do',
|
||||
}),
|
||||
]:
|
||||
zone.add_record(record)
|
||||
|
||||
|
||||
class TestTypeAllowListFilter(TestCase):
|
||||
|
||||
def test_basics(self):
|
||||
filter_a = TypeAllowlistFilter('only-a', set(('A')))
|
||||
|
||||
got = filter_a.process_source_zone(zone.copy())
|
||||
self.assertEquals(['a', 'a2'], sorted([r.name for r in got.records]))
|
||||
|
||||
filter_aaaa = TypeAllowlistFilter('only-aaaa', ('AAAA',))
|
||||
got = filter_aaaa.process_source_zone(zone.copy())
|
||||
self.assertEquals(['aaaa'], sorted([r.name for r in got.records]))
|
||||
|
||||
filter_txt = TypeAllowlistFilter('only-txt', ['TXT'])
|
||||
got = filter_txt.process_target_zone(zone.copy())
|
||||
self.assertEquals(['txt', 'txt2'],
|
||||
sorted([r.name for r in got.records]))
|
||||
|
||||
filter_a_aaaa = TypeAllowlistFilter('only-aaaa', set(('A', 'AAAA')))
|
||||
got = filter_a_aaaa.process_target_zone(zone.copy())
|
||||
self.assertEquals(['a', 'a2', 'aaaa'],
|
||||
sorted([r.name for r in got.records]))
|
||||
|
||||
|
||||
class TestTypeRejectListFilter(TestCase):
|
||||
|
||||
def test_basics(self):
|
||||
filter_a = TypeRejectlistFilter('not-a', set(('A')))
|
||||
|
||||
got = filter_a.process_source_zone(zone.copy())
|
||||
self.assertEquals(['aaaa', 'txt', 'txt2'],
|
||||
sorted([r.name for r in got.records]))
|
||||
|
||||
filter_aaaa = TypeRejectlistFilter('not-aaaa', ('AAAA',))
|
||||
got = filter_aaaa.process_source_zone(zone.copy())
|
||||
self.assertEquals(['a', 'a2', 'txt', 'txt2'],
|
||||
sorted([r.name for r in got.records]))
|
||||
|
||||
filter_txt = TypeRejectlistFilter('not-txt', ['TXT'])
|
||||
got = filter_txt.process_target_zone(zone.copy())
|
||||
self.assertEquals(['a', 'a2', 'aaaa'],
|
||||
sorted([r.name for r in got.records]))
|
||||
|
||||
filter_a_aaaa = TypeRejectlistFilter('not-a-aaaa', set(('A', 'AAAA')))
|
||||
got = filter_a_aaaa.process_target_zone(zone.copy())
|
||||
self.assertEquals(['txt', 'txt2'],
|
||||
sorted([r.name for r in got.records]))
|
||||
@@ -0,0 +1,146 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
unicode_literals
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from octodns.processor.ownership import OwnershipProcessor
|
||||
from octodns.record import Delete, Record
|
||||
from octodns.zone import Zone
|
||||
|
||||
from helpers import PlannableProvider
|
||||
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
records = {}
|
||||
for record in [
|
||||
Record.new(zone, '', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'values': [
|
||||
'1.2.3.4',
|
||||
'5.6.7.8',
|
||||
],
|
||||
}),
|
||||
Record.new(zone, 'the-a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
}),
|
||||
Record.new(zone, 'the-aaaa', {
|
||||
'ttl': 30,
|
||||
'type': 'AAAA',
|
||||
'value': '::1',
|
||||
}),
|
||||
Record.new(zone, 'the-txt', {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'value': 'Hello World!',
|
||||
}),
|
||||
Record.new(zone, '*', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '4.3.2.1',
|
||||
}),
|
||||
]:
|
||||
records[record.name] = record
|
||||
zone.add_record(record)
|
||||
|
||||
|
||||
class TestOwnershipProcessor(TestCase):
|
||||
|
||||
def test_process_source_zone(self):
|
||||
ownership = OwnershipProcessor('ownership')
|
||||
|
||||
got = ownership.process_source_zone(zone.copy())
|
||||
self.assertEquals([
|
||||
'',
|
||||
'*',
|
||||
'_owner.a',
|
||||
'_owner.a._wildcard',
|
||||
'_owner.a.the-a',
|
||||
'_owner.aaaa.the-aaaa',
|
||||
'_owner.txt.the-txt',
|
||||
'the-a',
|
||||
'the-aaaa',
|
||||
'the-txt',
|
||||
], sorted([r.name for r in got.records]))
|
||||
|
||||
found = False
|
||||
for record in got.records:
|
||||
if record.name.startswith(ownership.txt_name):
|
||||
self.assertEquals([ownership.txt_value], record.values)
|
||||
# test _is_ownership while we're in here
|
||||
self.assertTrue(ownership._is_ownership(record))
|
||||
found = True
|
||||
else:
|
||||
self.assertFalse(ownership._is_ownership(record))
|
||||
self.assertTrue(found)
|
||||
|
||||
def test_process_plan(self):
|
||||
ownership = OwnershipProcessor('ownership')
|
||||
provider = PlannableProvider('helper')
|
||||
|
||||
# No plan, is a quick noop
|
||||
self.assertFalse(ownership.process_plan(None))
|
||||
|
||||
# Nothing exists create both records and ownership
|
||||
ownership_added = ownership.process_source_zone(zone.copy())
|
||||
plan = provider.plan(ownership_added)
|
||||
self.assertTrue(plan)
|
||||
# Double the number of records
|
||||
self.assertEquals(len(records) * 2, len(plan.changes))
|
||||
# Now process the plan, shouldn't make any changes, we're creating
|
||||
# everything
|
||||
got = ownership.process_plan(plan)
|
||||
self.assertTrue(got)
|
||||
self.assertEquals(len(records) * 2, len(got.changes))
|
||||
|
||||
# Something extra exists and doesn't have ownership TXT, leave it
|
||||
# alone, we don't own it.
|
||||
extra_a = Record.new(zone, 'extra-a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '4.4.4.4',
|
||||
})
|
||||
plan.existing.add_record(extra_a)
|
||||
# If we'd done a "real" plan we'd have a delete for the extra thing.
|
||||
plan.changes.append(Delete(extra_a))
|
||||
# Process the plan, shouldn't make any changes since the extra bit is
|
||||
# something we don't own
|
||||
got = ownership.process_plan(plan)
|
||||
self.assertTrue(got)
|
||||
self.assertEquals(len(records) * 2, len(got.changes))
|
||||
|
||||
# Something extra exists and does have an ownership record so we will
|
||||
# delete it...
|
||||
copy = Zone('unit.tests.', [])
|
||||
for record in records.values():
|
||||
if record.name != 'the-a':
|
||||
copy.add_record(record)
|
||||
# New ownership, without the `the-a`
|
||||
ownership_added = ownership.process_source_zone(copy)
|
||||
self.assertEquals(len(records) * 2 - 2, len(ownership_added.records))
|
||||
plan = provider.plan(ownership_added)
|
||||
# Fake the extra existing by adding the record, its ownership, and the
|
||||
# two delete changes.
|
||||
the_a = records['the-a']
|
||||
plan.existing.add_record(the_a)
|
||||
name = '{}.a.the-a'.format(ownership.txt_name)
|
||||
the_a_ownership = Record.new(zone, name, {
|
||||
'ttl': 30,
|
||||
'type': 'TXT',
|
||||
'value': ownership.txt_value,
|
||||
})
|
||||
plan.existing.add_record(the_a_ownership)
|
||||
plan.changes.append(Delete(the_a))
|
||||
plan.changes.append(Delete(the_a_ownership))
|
||||
# Finally process the plan, should be a noop and we should get the same
|
||||
# plan out, meaning the planned deletes were allowed to happen.
|
||||
got = ownership.process_plan(plan)
|
||||
self.assertTrue(got)
|
||||
self.assertEquals(plan, got)
|
||||
self.assertEquals(len(plan.changes), len(got.changes))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,15 @@ from __future__ import absolute_import, division, print_function, \
|
||||
unicode_literals
|
||||
|
||||
from logging import getLogger
|
||||
from mock import MagicMock, call
|
||||
from six import text_type
|
||||
from unittest import TestCase
|
||||
|
||||
from octodns.record import Create, Delete, Record, Update
|
||||
from octodns.processor.base import BaseProcessor
|
||||
from octodns.provider import SupportsException
|
||||
from octodns.provider.base import BaseProvider
|
||||
from octodns.provider.plan import Plan, UnsafePlan
|
||||
from octodns.record import Create, Delete, Record, Update
|
||||
from octodns.zone import Zone
|
||||
|
||||
|
||||
@@ -20,8 +23,9 @@ class HelperProvider(BaseProvider):
|
||||
|
||||
SUPPORTS = set(('A',))
|
||||
id = 'test'
|
||||
strict_supports = False
|
||||
|
||||
def __init__(self, extra_changes, apply_disabled=False,
|
||||
def __init__(self, extra_changes=[], apply_disabled=False,
|
||||
include_change_callback=None):
|
||||
self.__extra_changes = extra_changes
|
||||
self.apply_disabled = apply_disabled
|
||||
@@ -43,6 +47,29 @@ class HelperProvider(BaseProvider):
|
||||
pass
|
||||
|
||||
|
||||
class TrickyProcessor(BaseProcessor):
|
||||
|
||||
def __init__(self, name, add_during_process_target_zone):
|
||||
super(TrickyProcessor, self).__init__(name)
|
||||
self.add_during_process_target_zone = add_during_process_target_zone
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.existing = None
|
||||
self.target = None
|
||||
|
||||
def process_target_zone(self, existing, target):
|
||||
self.existing = existing
|
||||
self.target = target
|
||||
|
||||
new = existing.copy()
|
||||
for record in existing.records:
|
||||
new.add_record(record, replace=True)
|
||||
for record in self.add_during_process_target_zone:
|
||||
new.add_record(record, replace=True)
|
||||
return new
|
||||
|
||||
|
||||
class TestBaseProvider(TestCase):
|
||||
|
||||
def test_base_provider(self):
|
||||
@@ -138,6 +165,45 @@ class TestBaseProvider(TestCase):
|
||||
self.assertTrue(plan)
|
||||
self.assertEquals(1, len(plan.changes))
|
||||
|
||||
def test_plan_with_processors(self):
|
||||
zone = Zone('unit.tests.', [])
|
||||
|
||||
record = Record.new(zone, 'a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
})
|
||||
provider = HelperProvider()
|
||||
# Processor that adds a record to the zone, which planning will then
|
||||
# delete since it won't know anything about it
|
||||
tricky = TrickyProcessor('tricky', [record])
|
||||
plan = provider.plan(zone, processors=[tricky])
|
||||
self.assertTrue(plan)
|
||||
self.assertEquals(1, len(plan.changes))
|
||||
self.assertIsInstance(plan.changes[0], Delete)
|
||||
# Called processor stored its params
|
||||
self.assertTrue(tricky.existing)
|
||||
self.assertEquals(zone.name, tricky.existing.name)
|
||||
|
||||
# Chain of processors happen one after the other
|
||||
other = Record.new(zone, 'b', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '5.6.7.8',
|
||||
})
|
||||
# Another processor will add its record, thus 2 deletes
|
||||
another = TrickyProcessor('tricky', [other])
|
||||
plan = provider.plan(zone, processors=[tricky, another])
|
||||
self.assertTrue(plan)
|
||||
self.assertEquals(2, len(plan.changes))
|
||||
self.assertIsInstance(plan.changes[0], Delete)
|
||||
self.assertIsInstance(plan.changes[1], Delete)
|
||||
# 2nd processor stored its params, and we'll see the record the
|
||||
# first one added
|
||||
self.assertTrue(another.existing)
|
||||
self.assertEquals(zone.name, another.existing.name)
|
||||
self.assertEquals(1, len(another.existing.records))
|
||||
|
||||
def test_apply(self):
|
||||
ignored = Zone('unit.tests.', [])
|
||||
|
||||
@@ -167,6 +233,20 @@ class TestBaseProvider(TestCase):
|
||||
# We filtered out the only change
|
||||
self.assertFalse(plan)
|
||||
|
||||
def test_process_desired_zone(self):
|
||||
zone1 = Zone('unit.tests.', [])
|
||||
record1 = Record.new(zone1, 'ptr', {
|
||||
'type': 'PTR',
|
||||
'ttl': 3600,
|
||||
'values': ['foo.com.', 'bar.com.'],
|
||||
})
|
||||
zone1.add_record(record1)
|
||||
|
||||
zone2 = HelperProvider('hasptr')._process_desired_zone(zone1)
|
||||
record2 = list(zone2.records)[0]
|
||||
|
||||
self.assertEqual(len(record2.values), 1)
|
||||
|
||||
def test_safe_none(self):
|
||||
# No changes is safe
|
||||
Plan(None, None, [], True).raise_if_unsafe()
|
||||
@@ -366,3 +446,27 @@ class TestBaseProvider(TestCase):
|
||||
delete_pcent_threshold=safe_pcent).raise_if_unsafe()
|
||||
|
||||
self.assertTrue('Too many deletes' in text_type(ctx.exception))
|
||||
|
||||
def test_supports_warn_or_except(self):
|
||||
class MinimalProvider(BaseProvider):
|
||||
SUPPORTS = set()
|
||||
SUPPORTS_GEO = False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.log = MagicMock()
|
||||
super(MinimalProvider, self).__init__('minimal', **kwargs)
|
||||
|
||||
normal = MinimalProvider(strict_supports=False)
|
||||
# Should log and not expect
|
||||
normal.supports_warn_or_except('Hello World!', 'Goodbye')
|
||||
normal.log.warning.assert_called_once()
|
||||
normal.log.warning.assert_has_calls([
|
||||
call('Hello World!; Goodbye')
|
||||
])
|
||||
|
||||
strict = MinimalProvider(strict_supports=True)
|
||||
# Should log and not expect
|
||||
with self.assertRaises(SupportsException) as ctx:
|
||||
strict.supports_warn_or_except('Hello World!', 'Will not see')
|
||||
self.assertEquals('minimal: Hello World!', text_type(ctx.exception))
|
||||
strict.log.warning.assert_not_called()
|
||||
|
||||
@@ -166,9 +166,15 @@ class TestCloudflareProvider(TestCase):
|
||||
json={'result': [], 'result_info': {'count': 0,
|
||||
'per_page': 0}})
|
||||
|
||||
base = '{}/234234243423aaabb334342aaa343435'.format(base)
|
||||
|
||||
# pagerules/URLFWD
|
||||
with open('tests/fixtures/cloudflare-pagerules.json') as fh:
|
||||
mock.get('{}/pagerules?status=active'.format(base),
|
||||
status_code=200, text=fh.read())
|
||||
|
||||
# records
|
||||
base = '{}/234234243423aaabb334342aaa343435/dns_records' \
|
||||
.format(base)
|
||||
base = '{}/dns_records'.format(base)
|
||||
with open('tests/fixtures/cloudflare-dns_records-'
|
||||
'page-1.json') as fh:
|
||||
mock.get('{}?page=1'.format(base), status_code=200,
|
||||
@@ -177,19 +183,23 @@ class TestCloudflareProvider(TestCase):
|
||||
'page-2.json') as fh:
|
||||
mock.get('{}?page=2'.format(base), status_code=200,
|
||||
text=fh.read())
|
||||
with open('tests/fixtures/cloudflare-dns_records-'
|
||||
'page-3.json') as fh:
|
||||
mock.get('{}?page=3'.format(base), status_code=200,
|
||||
text=fh.read())
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(13, len(zone.records))
|
||||
self.assertEquals(19, len(zone.records))
|
||||
|
||||
changes = self.expected.changes(zone, provider)
|
||||
|
||||
self.assertEquals(0, len(changes))
|
||||
self.assertEquals(4, len(changes))
|
||||
|
||||
# re-populating the same zone/records comes out of cache, no calls
|
||||
again = Zone('unit.tests.', [])
|
||||
provider.populate(again)
|
||||
self.assertEquals(13, len(again.records))
|
||||
self.assertEquals(19, len(again.records))
|
||||
|
||||
def test_apply(self):
|
||||
provider = CloudflareProvider('test', 'email', 'token', retry_period=0)
|
||||
@@ -203,12 +213,12 @@ class TestCloudflareProvider(TestCase):
|
||||
'id': 42,
|
||||
}
|
||||
}, # zone create
|
||||
] + [None] * 22 # individual record creates
|
||||
] + [None] * 27 # individual record creates
|
||||
|
||||
# non-existent zone, create everything
|
||||
plan = provider.plan(self.expected)
|
||||
self.assertEquals(13, len(plan.changes))
|
||||
self.assertEquals(13, provider.apply(plan))
|
||||
self.assertEquals(17, len(plan.changes))
|
||||
self.assertEquals(17, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
provider._request.assert_has_calls([
|
||||
@@ -232,9 +242,31 @@ class TestCloudflareProvider(TestCase):
|
||||
'name': 'txt.unit.tests',
|
||||
'ttl': 600
|
||||
}),
|
||||
# create at least one pagerules
|
||||
call('POST', '/zones/42/pagerules', data={
|
||||
'targets': [
|
||||
{
|
||||
'target': 'url',
|
||||
'constraint': {
|
||||
'operator': 'matches',
|
||||
'value': 'urlfwd.unit.tests/'
|
||||
}
|
||||
}
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'id': 'forwarding_url',
|
||||
'value': {
|
||||
'url': 'http://www.unit.tests',
|
||||
'status_code': 302
|
||||
}
|
||||
}
|
||||
],
|
||||
'status': 'active'
|
||||
}),
|
||||
], True)
|
||||
# expected number of total calls
|
||||
self.assertEquals(23, provider._request.call_count)
|
||||
self.assertEquals(29, provider._request.call_count)
|
||||
|
||||
provider._request.reset_mock()
|
||||
|
||||
@@ -307,6 +339,56 @@ class TestCloudflareProvider(TestCase):
|
||||
"auto_added": False
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2a9140b17ffb0e6aed826049eec970b7",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd.unit.tests/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests",
|
||||
"status_code": 302
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 1,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2a9141b18ffb0e6aed826050eec970b8",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwdother.unit.tests/target"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://target.unit.tests",
|
||||
"status_code": 301
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 2,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
},
|
||||
])
|
||||
|
||||
# we don't care about the POST/create return values
|
||||
@@ -315,7 +397,7 @@ class TestCloudflareProvider(TestCase):
|
||||
# Test out the create rate-limit handling, then 9 successes
|
||||
provider._request.side_effect = [
|
||||
CloudflareRateLimitError('{}'),
|
||||
] + ([None] * 3)
|
||||
] + ([None] * 5)
|
||||
|
||||
wanted = Zone('unit.tests.', [])
|
||||
wanted.add_record(Record.new(wanted, 'nc', {
|
||||
@@ -328,14 +410,31 @@ class TestCloudflareProvider(TestCase):
|
||||
'type': 'A',
|
||||
'value': '3.2.3.4'
|
||||
}))
|
||||
wanted.add_record(Record.new(wanted, 'urlfwd', {
|
||||
'ttl': 300,
|
||||
'type': 'URLFWD',
|
||||
'value': {
|
||||
'path': '/*', # path change
|
||||
'target': 'https://www.unit.tests/', # target change
|
||||
'code': 301, # status_code change
|
||||
'masking': '2',
|
||||
'query': 0,
|
||||
}
|
||||
}))
|
||||
|
||||
plan = provider.plan(wanted)
|
||||
# only see the delete & ttl update, below min-ttl is filtered out
|
||||
self.assertEquals(2, len(plan.changes))
|
||||
self.assertEquals(2, provider.apply(plan))
|
||||
self.assertEquals(4, len(plan.changes))
|
||||
self.assertEquals(4, provider.apply(plan))
|
||||
self.assertTrue(plan.exists)
|
||||
# creates a the new value and then deletes all the old
|
||||
provider._request.assert_has_calls([
|
||||
call('DELETE', '/zones/42/'
|
||||
'pagerules/2a9141b18ffb0e6aed826050eec970b8'),
|
||||
call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/'
|
||||
'dns_records/fc12ab34cd5611334422ab3322997653'),
|
||||
call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/'
|
||||
'dns_records/fc12ab34cd5611334422ab3322997654'),
|
||||
call('PUT', '/zones/42/dns_records/'
|
||||
'fc12ab34cd5611334422ab3322997655', data={
|
||||
'content': '3.2.3.4',
|
||||
@@ -344,10 +443,28 @@ class TestCloudflareProvider(TestCase):
|
||||
'proxied': False,
|
||||
'ttl': 300
|
||||
}),
|
||||
call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/'
|
||||
'dns_records/fc12ab34cd5611334422ab3322997653'),
|
||||
call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/'
|
||||
'dns_records/fc12ab34cd5611334422ab3322997654')
|
||||
call('PUT', '/zones/42/pagerules/'
|
||||
'2a9140b17ffb0e6aed826049eec970b7', data={
|
||||
'targets': [
|
||||
{
|
||||
'target': 'url',
|
||||
'constraint': {
|
||||
'operator': 'matches',
|
||||
'value': 'urlfwd.unit.tests/*'
|
||||
}
|
||||
}
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'id': 'forwarding_url',
|
||||
'value': {
|
||||
'url': 'https://www.unit.tests/',
|
||||
'status_code': 301
|
||||
}
|
||||
}
|
||||
],
|
||||
'status': 'active',
|
||||
}),
|
||||
])
|
||||
|
||||
def test_update_add_swap(self):
|
||||
@@ -496,6 +613,56 @@ class TestCloudflareProvider(TestCase):
|
||||
"auto_added": False
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2a9140b17ffb0e6aed826049eec974b7",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd1.unit.tests/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests",
|
||||
"status_code": 302
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 1,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2a9141b18ffb0e6aed826054eec970b8",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd1.unit.tests/target"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://target.unit.tests",
|
||||
"status_code": 301
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 2,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
},
|
||||
])
|
||||
|
||||
provider._request = Mock()
|
||||
@@ -509,6 +676,8 @@ class TestCloudflareProvider(TestCase):
|
||||
}, # zone create
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
]
|
||||
|
||||
# Add something and delete something
|
||||
@@ -519,14 +688,46 @@ class TestCloudflareProvider(TestCase):
|
||||
# This matches the zone data above, one to delete, one to leave
|
||||
'values': ['ns1.foo.bar.', 'ns2.foo.bar.'],
|
||||
})
|
||||
exstingurlfwd = Record.new(zone, 'urlfwd1', {
|
||||
'ttl': 300,
|
||||
'type': 'URLFWD',
|
||||
'values': [
|
||||
{
|
||||
'path': '/',
|
||||
'target': 'https://www.unit.tests',
|
||||
'code': 302,
|
||||
'masking': '2',
|
||||
'query': 0,
|
||||
},
|
||||
{
|
||||
'path': '/target',
|
||||
'target': 'https://target.unit.tests',
|
||||
'code': 301,
|
||||
'masking': '2',
|
||||
'query': 0,
|
||||
}
|
||||
]
|
||||
})
|
||||
new = Record.new(zone, '', {
|
||||
'ttl': 300,
|
||||
'type': 'NS',
|
||||
# This leaves one and deletes one
|
||||
'value': 'ns2.foo.bar.',
|
||||
})
|
||||
newurlfwd = Record.new(zone, 'urlfwd1', {
|
||||
'ttl': 300,
|
||||
'type': 'URLFWD',
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'https://www.unit.tests',
|
||||
'code': 302,
|
||||
'masking': '2',
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
change = Update(existing, new)
|
||||
plan = Plan(zone, zone, [change], True)
|
||||
changeurlfwd = Update(exstingurlfwd, newurlfwd)
|
||||
plan = Plan(zone, zone, [change, changeurlfwd], True)
|
||||
provider._apply(plan)
|
||||
|
||||
# Get zones, create zone, create a record, delete a record
|
||||
@@ -544,7 +745,31 @@ class TestCloudflareProvider(TestCase):
|
||||
'ttl': 300
|
||||
}),
|
||||
call('DELETE', '/zones/42/dns_records/'
|
||||
'fc12ab34cd5611334422ab3322997653')
|
||||
'fc12ab34cd5611334422ab3322997653'),
|
||||
call('PUT', '/zones/42/pagerules/'
|
||||
'2a9140b17ffb0e6aed826049eec974b7', data={
|
||||
'targets': [
|
||||
{
|
||||
'target': 'url',
|
||||
'constraint': {
|
||||
'operator': 'matches',
|
||||
'value': 'urlfwd1.unit.tests/'
|
||||
}
|
||||
}
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'id': 'forwarding_url',
|
||||
'value': {
|
||||
'url': 'https://www.unit.tests',
|
||||
'status_code': 302
|
||||
}
|
||||
}
|
||||
],
|
||||
'status': 'active'
|
||||
}),
|
||||
call('DELETE', '/zones/42/pagerules/'
|
||||
'2a9141b18ffb0e6aed826054eec970b8'),
|
||||
])
|
||||
|
||||
def test_ptr(self):
|
||||
@@ -566,6 +791,52 @@ class TestCloudflareProvider(TestCase):
|
||||
'content': 'foo.bar.com.'
|
||||
}, list(ptr_record_contents)[0])
|
||||
|
||||
def test_loc(self):
|
||||
self.maxDiff = None
|
||||
provider = CloudflareProvider('test', 'email', 'token')
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
# LOC record
|
||||
loc_record = Record.new(zone, 'example', {
|
||||
'ttl': 300,
|
||||
'type': 'LOC',
|
||||
'value': {
|
||||
'lat_degrees': 31,
|
||||
'lat_minutes': 58,
|
||||
'lat_seconds': 52.1,
|
||||
'lat_direction': 'S',
|
||||
'long_degrees': 115,
|
||||
'long_minutes': 49,
|
||||
'long_seconds': 11.7,
|
||||
'long_direction': 'E',
|
||||
'altitude': 20,
|
||||
'size': 10,
|
||||
'precision_horz': 10,
|
||||
'precision_vert': 2,
|
||||
}
|
||||
})
|
||||
|
||||
loc_record_contents = provider._gen_data(loc_record)
|
||||
self.assertEquals({
|
||||
'name': 'example.unit.tests',
|
||||
'ttl': 300,
|
||||
'type': 'LOC',
|
||||
'data': {
|
||||
'lat_degrees': 31,
|
||||
'lat_minutes': 58,
|
||||
'lat_seconds': 52.1,
|
||||
'lat_direction': 'S',
|
||||
'long_degrees': 115,
|
||||
'long_minutes': 49,
|
||||
'long_seconds': 11.7,
|
||||
'long_direction': 'E',
|
||||
'altitude': 20,
|
||||
'size': 10,
|
||||
'precision_horz': 10,
|
||||
'precision_vert': 2,
|
||||
}
|
||||
}, list(loc_record_contents)[0])
|
||||
|
||||
def test_srv(self):
|
||||
provider = CloudflareProvider('test', 'email', 'token')
|
||||
|
||||
@@ -697,6 +968,23 @@ class TestCloudflareProvider(TestCase):
|
||||
},
|
||||
'type': 'SRV',
|
||||
}),
|
||||
('31 58 52.1 S 115 49 11.7 E 20 10 10 2', {
|
||||
'data': {
|
||||
'lat_degrees': 31,
|
||||
'lat_minutes': 58,
|
||||
'lat_seconds': 52.1,
|
||||
'lat_direction': 'S',
|
||||
'long_degrees': 115,
|
||||
'long_minutes': 49,
|
||||
'long_seconds': 11.7,
|
||||
'long_direction': 'E',
|
||||
'altitude': 20,
|
||||
'size': 10,
|
||||
'precision_horz': 10,
|
||||
'precision_vert': 2,
|
||||
},
|
||||
'type': 'LOC',
|
||||
}),
|
||||
):
|
||||
self.assertEqual(expected, provider._gen_key(data))
|
||||
|
||||
@@ -1343,3 +1631,11 @@ class TestCloudflareProvider(TestCase):
|
||||
with self.assertRaises(CloudflareRateLimitError) as ctx:
|
||||
provider.zone_records(zone)
|
||||
self.assertEquals('last', text_type(ctx.exception))
|
||||
|
||||
def test_ttl_mapping(self):
|
||||
provider = CloudflareProvider('test', 'email', 'token')
|
||||
|
||||
self.assertEquals(120, provider._ttl_data(120))
|
||||
self.assertEquals(120, provider._ttl_data(120))
|
||||
self.assertEquals(3600, provider._ttl_data(3600))
|
||||
self.assertEquals(300, provider._ttl_data(1))
|
||||
|
||||
@@ -172,7 +172,7 @@ class TestConstellixProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 5
|
||||
n = len(self.expected.records) - 8
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
|
||||
|
||||
@@ -83,14 +83,14 @@ class TestDigitalOceanProvider(TestCase):
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(12, len(zone.records))
|
||||
self.assertEquals(14, len(zone.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEquals(0, len(changes))
|
||||
|
||||
# 2nd populate makes no network calls/all from cache
|
||||
again = Zone('unit.tests.', [])
|
||||
provider.populate(again)
|
||||
self.assertEquals(12, len(again.records))
|
||||
self.assertEquals(14, len(again.records))
|
||||
|
||||
# bust the cache
|
||||
del provider._zone_records[zone.name]
|
||||
@@ -163,7 +163,7 @@ class TestDigitalOceanProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 7
|
||||
n = len(self.expected.records) - 10
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
@@ -190,6 +190,24 @@ class TestDigitalOceanProvider(TestCase):
|
||||
'flags': 0, 'name': '@',
|
||||
'tag': 'issue',
|
||||
'ttl': 3600, 'type': 'CAA'}),
|
||||
call('POST', '/domains/unit.tests/records', data={
|
||||
'name': '_imap._tcp',
|
||||
'weight': 0,
|
||||
'data': '.',
|
||||
'priority': 0,
|
||||
'ttl': 600,
|
||||
'type': 'SRV',
|
||||
'port': 0
|
||||
}),
|
||||
call('POST', '/domains/unit.tests/records', data={
|
||||
'name': '_pop3._tcp',
|
||||
'weight': 0,
|
||||
'data': '.',
|
||||
'priority': 0,
|
||||
'ttl': 600,
|
||||
'type': 'SRV',
|
||||
'port': 0
|
||||
}),
|
||||
call('POST', '/domains/unit.tests/records', data={
|
||||
'name': '_srv._tcp',
|
||||
'weight': 20,
|
||||
@@ -200,7 +218,7 @@ class TestDigitalOceanProvider(TestCase):
|
||||
'port': 30
|
||||
}),
|
||||
])
|
||||
self.assertEquals(24, provider._client._request.call_count)
|
||||
self.assertEquals(26, provider._client._request.call_count)
|
||||
|
||||
provider._client._request.reset_mock()
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ class TestDnsimpleProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded
|
||||
n = len(self.expected.records) - 3
|
||||
n = len(self.expected.records) - 8
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
@@ -44,12 +44,6 @@ class TestDnsMadeEasyProvider(TestCase):
|
||||
'value': 'aname.unit.tests.'
|
||||
}))
|
||||
|
||||
expected.add_record(Record.new(expected, 'sub', {
|
||||
'ttl': 1800,
|
||||
'type': 'ALIAS',
|
||||
'value': 'aname.unit.tests.'
|
||||
}))
|
||||
|
||||
for record in list(expected.records):
|
||||
if record.name == 'sub' and record._type == 'NS':
|
||||
expected._remove_record(record)
|
||||
@@ -108,14 +102,14 @@ class TestDnsMadeEasyProvider(TestCase):
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(15, len(zone.records))
|
||||
self.assertEquals(14, len(zone.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEquals(0, len(changes))
|
||||
|
||||
# 2nd populate makes no network calls/all from cache
|
||||
again = Zone('unit.tests.', [])
|
||||
provider.populate(again)
|
||||
self.assertEquals(15, len(again.records))
|
||||
self.assertEquals(14, len(again.records))
|
||||
|
||||
# bust the cache
|
||||
del provider._zone_records[zone.name]
|
||||
@@ -140,7 +134,7 @@ class TestDnsMadeEasyProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 5
|
||||
n = len(self.expected.records) - 10
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
|
||||
@@ -180,7 +174,7 @@ class TestDnsMadeEasyProvider(TestCase):
|
||||
'port': 30
|
||||
}),
|
||||
])
|
||||
self.assertEquals(27, provider._client._request.call_count)
|
||||
self.assertEquals(26, provider._client._request.call_count)
|
||||
|
||||
provider._client._request.reset_mock()
|
||||
|
||||
|
||||
@@ -80,14 +80,14 @@ class TestEasyDNSProvider(TestCase):
|
||||
text=fh.read())
|
||||
|
||||
provider.populate(zone)
|
||||
self.assertEquals(13, len(zone.records))
|
||||
self.assertEquals(15, len(zone.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEquals(0, len(changes))
|
||||
|
||||
# 2nd populate makes no network calls/all from cache
|
||||
again = Zone('unit.tests.', [])
|
||||
provider.populate(again)
|
||||
self.assertEquals(13, len(again.records))
|
||||
self.assertEquals(15, len(again.records))
|
||||
|
||||
# bust the cache
|
||||
del provider._zone_records[zone.name]
|
||||
@@ -374,12 +374,12 @@ class TestEasyDNSProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 6
|
||||
n = len(self.expected.records) - 9
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
self.assertEquals(23, provider._client._request.call_count)
|
||||
self.assertEquals(25, provider._client._request.call_count)
|
||||
|
||||
provider._client._request.reset_mock()
|
||||
|
||||
|
||||
@@ -77,14 +77,14 @@ class TestEdgeDnsProvider(TestCase):
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(16, len(zone.records))
|
||||
self.assertEquals(18, len(zone.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEquals(0, len(changes))
|
||||
|
||||
# 2nd populate makes no network calls/all from cache
|
||||
again = Zone('unit.tests.', [])
|
||||
provider.populate(again)
|
||||
self.assertEquals(16, len(again.records))
|
||||
self.assertEquals(18, len(again.records))
|
||||
|
||||
# bust the cache
|
||||
del provider._zone_records[zone.name]
|
||||
@@ -105,7 +105,7 @@ class TestEdgeDnsProvider(TestCase):
|
||||
mock.delete(ANY, status_code=204)
|
||||
|
||||
changes = provider.apply(plan)
|
||||
self.assertEquals(29, changes)
|
||||
self.assertEquals(31, changes)
|
||||
|
||||
# Test against a zone that doesn't exist yet
|
||||
with requests_mock() as mock:
|
||||
@@ -118,7 +118,7 @@ class TestEdgeDnsProvider(TestCase):
|
||||
mock.delete(ANY, status_code=204)
|
||||
|
||||
changes = provider.apply(plan)
|
||||
self.assertEquals(14, changes)
|
||||
self.assertEquals(16, changes)
|
||||
|
||||
# Test against a zone that doesn't exist yet, but gid not provided
|
||||
with requests_mock() as mock:
|
||||
@@ -132,7 +132,7 @@ class TestEdgeDnsProvider(TestCase):
|
||||
mock.delete(ANY, status_code=204)
|
||||
|
||||
changes = provider.apply(plan)
|
||||
self.assertEquals(14, changes)
|
||||
self.assertEquals(16, changes)
|
||||
|
||||
# Test against a zone that doesn't exist, but cid not provided
|
||||
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
unicode_literals
|
||||
|
||||
from mock import Mock, call
|
||||
from os.path import dirname, join
|
||||
from requests import HTTPError
|
||||
from requests_mock import ANY, mock as requests_mock
|
||||
from six import text_type
|
||||
from unittest import TestCase
|
||||
|
||||
from octodns.record import Record
|
||||
from octodns.provider.gandi import GandiProvider, GandiClientBadRequest, \
|
||||
GandiClientUnauthorized, GandiClientForbidden, GandiClientNotFound, \
|
||||
GandiClientUnknownDomainName
|
||||
from octodns.provider.yaml import YamlProvider
|
||||
from octodns.zone import Zone
|
||||
|
||||
|
||||
class TestGandiProvider(TestCase):
|
||||
expected = Zone('unit.tests.', [])
|
||||
source = YamlProvider('test', join(dirname(__file__), 'config'))
|
||||
source.populate(expected)
|
||||
|
||||
# We remove this record from the test zone as Gandi API reject it
|
||||
# (rightfully).
|
||||
expected._remove_record(Record.new(expected, 'sub', {
|
||||
'ttl': 1800,
|
||||
'type': 'NS',
|
||||
'values': [
|
||||
'6.2.3.4.',
|
||||
'7.2.3.4.'
|
||||
]
|
||||
}))
|
||||
|
||||
def test_populate(self):
|
||||
|
||||
provider = GandiProvider('test_id', 'token')
|
||||
|
||||
# 400 - Bad Request.
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=400,
|
||||
text='{"status": "error", "errors": [{"location": '
|
||||
'"body", "name": "items", "description": '
|
||||
'"\'6.2.3.4.\': invalid hostname (param: '
|
||||
'{\'rrset_type\': u\'NS\', \'rrset_ttl\': 3600, '
|
||||
'\'rrset_name\': u\'sub\', \'rrset_values\': '
|
||||
'[u\'6.2.3.4.\', u\'7.2.3.4.\']})"}, {"location": '
|
||||
'"body", "name": "items", "description": '
|
||||
'"\'7.2.3.4.\': invalid hostname (param: '
|
||||
'{\'rrset_type\': u\'NS\', \'rrset_ttl\': 3600, '
|
||||
'\'rrset_name\': u\'sub\', \'rrset_values\': '
|
||||
'[u\'6.2.3.4.\', u\'7.2.3.4.\']})"}]}')
|
||||
|
||||
with self.assertRaises(GandiClientBadRequest) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertIn('"status": "error"', text_type(ctx.exception))
|
||||
|
||||
# 401 - Unauthorized.
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=401,
|
||||
text='{"code":401,"message":"The server could not verify '
|
||||
'that you authorized to access the document you '
|
||||
'requested. Either you supplied the wrong '
|
||||
'credentials (e.g., bad api key), or your access '
|
||||
'token has expired","object":"HTTPUnauthorized",'
|
||||
'"cause":"Unauthorized"}')
|
||||
|
||||
with self.assertRaises(GandiClientUnauthorized) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertIn('"cause":"Unauthorized"', text_type(ctx.exception))
|
||||
|
||||
# 403 - Forbidden.
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=403,
|
||||
text='{"code":403,"message":"Access was denied to this '
|
||||
'resource.","object":"HTTPForbidden","cause":'
|
||||
'"Forbidden"}')
|
||||
|
||||
with self.assertRaises(GandiClientForbidden) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertIn('"cause":"Forbidden"', text_type(ctx.exception))
|
||||
|
||||
# 404 - Not Found.
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=404,
|
||||
text='{"code": 404, "message": "The resource could not '
|
||||
'be found.", "object": "HTTPNotFound", "cause": '
|
||||
'"Not Found"}')
|
||||
|
||||
with self.assertRaises(GandiClientNotFound) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider._client.zone(zone)
|
||||
self.assertIn('"cause": "Not Found"', text_type(ctx.exception))
|
||||
|
||||
# General error
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=502, text='Things caught fire')
|
||||
|
||||
with self.assertRaises(HTTPError) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(502, ctx.exception.response.status_code)
|
||||
|
||||
# No diffs == no changes
|
||||
with requests_mock() as mock:
|
||||
base = 'https://api.gandi.net/v5/livedns/domains/unit.tests' \
|
||||
'/records'
|
||||
with open('tests/fixtures/gandi-no-changes.json') as fh:
|
||||
mock.get(base, text=fh.read())
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(16, len(zone.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEquals(0, len(changes))
|
||||
|
||||
del provider._zone_records[zone.name]
|
||||
|
||||
# Default Gandi zone file.
|
||||
with requests_mock() as mock:
|
||||
base = 'https://api.gandi.net/v5/livedns/domains/unit.tests' \
|
||||
'/records'
|
||||
with open('tests/fixtures/gandi-records.json') as fh:
|
||||
mock.get(base, text=fh.read())
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(11, len(zone.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEquals(24, len(changes))
|
||||
|
||||
# 2nd populate makes no network calls/all from cache
|
||||
again = Zone('unit.tests.', [])
|
||||
provider.populate(again)
|
||||
self.assertEquals(11, len(again.records))
|
||||
|
||||
# bust the cache
|
||||
del provider._zone_records[zone.name]
|
||||
|
||||
def test_apply(self):
|
||||
provider = GandiProvider('test_id', 'token')
|
||||
|
||||
# Zone does not exists but can be created.
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=404,
|
||||
text='{"code": 404, "message": "The resource could not '
|
||||
'be found.", "object": "HTTPNotFound", "cause": '
|
||||
'"Not Found"}')
|
||||
mock.post(ANY, status_code=201,
|
||||
text='{"message": "Domain Created"}')
|
||||
|
||||
plan = provider.plan(self.expected)
|
||||
provider.apply(plan)
|
||||
|
||||
# Zone does not exists and can't be created.
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=404,
|
||||
text='{"code": 404, "message": "The resource could not '
|
||||
'be found.", "object": "HTTPNotFound", "cause": '
|
||||
'"Not Found"}')
|
||||
mock.post(ANY, status_code=404,
|
||||
text='{"code": 404, "message": "The resource could not '
|
||||
'be found.", "object": "HTTPNotFound", "cause": '
|
||||
'"Not Found"}')
|
||||
|
||||
with self.assertRaises((GandiClientNotFound,
|
||||
GandiClientUnknownDomainName)) as ctx:
|
||||
plan = provider.plan(self.expected)
|
||||
provider.apply(plan)
|
||||
self.assertIn('This domain is not registered at Gandi.',
|
||||
text_type(ctx.exception))
|
||||
|
||||
resp = Mock()
|
||||
resp.json = Mock()
|
||||
provider._client._request = Mock(return_value=resp)
|
||||
|
||||
with open('tests/fixtures/gandi-zone.json') as fh:
|
||||
zone = fh.read()
|
||||
|
||||
# non-existent domain
|
||||
resp.json.side_effect = [
|
||||
GandiClientNotFound(resp), # no zone in populate
|
||||
GandiClientNotFound(resp), # no domain during apply
|
||||
zone
|
||||
]
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no LOC
|
||||
n = len(self.expected.records) - 6
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
provider._client._request.assert_has_calls([
|
||||
call('GET', '/livedns/domains/unit.tests/records'),
|
||||
call('GET', '/livedns/domains/unit.tests'),
|
||||
call('POST', '/livedns/domains', data={
|
||||
'fqdn': 'unit.tests',
|
||||
'zone': {}
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'www.sub',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'A',
|
||||
'rrset_values': ['2.2.3.6']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'www',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'A',
|
||||
'rrset_values': ['2.2.3.6']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'txt',
|
||||
'rrset_ttl': 600,
|
||||
'rrset_type': 'TXT',
|
||||
'rrset_values': [
|
||||
'Bah bah black sheep',
|
||||
'have you any wool.',
|
||||
'v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string'
|
||||
'+with+numb3rs'
|
||||
]
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'spf',
|
||||
'rrset_ttl': 600,
|
||||
'rrset_type': 'SPF',
|
||||
'rrset_values': ['v=spf1 ip4:192.168.0.1/16-all']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'ptr',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'PTR',
|
||||
'rrset_values': ['foo.bar.com.']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'mx',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'MX',
|
||||
'rrset_values': [
|
||||
'10 smtp-4.unit.tests.',
|
||||
'20 smtp-2.unit.tests.',
|
||||
'30 smtp-3.unit.tests.',
|
||||
'40 smtp-1.unit.tests.'
|
||||
]
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'excluded',
|
||||
'rrset_ttl': 3600,
|
||||
'rrset_type': 'CNAME',
|
||||
'rrset_values': ['unit.tests.']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'dname',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'DNAME',
|
||||
'rrset_values': ['unit.tests.']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'cname',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'CNAME',
|
||||
'rrset_values': ['unit.tests.']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'aaaa',
|
||||
'rrset_ttl': 600,
|
||||
'rrset_type': 'AAAA',
|
||||
'rrset_values': ['2601:644:500:e210:62f8:1dff:feb8:947a']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': '_srv._tcp',
|
||||
'rrset_ttl': 600,
|
||||
'rrset_type': 'SRV',
|
||||
'rrset_values': [
|
||||
'10 20 30 foo-1.unit.tests.',
|
||||
'12 20 30 foo-2.unit.tests.'
|
||||
]
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': '_pop3._tcp',
|
||||
'rrset_ttl': 600,
|
||||
'rrset_type': 'SRV',
|
||||
'rrset_values': [
|
||||
'0 0 0 .',
|
||||
]
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': '_imap._tcp',
|
||||
'rrset_ttl': 600,
|
||||
'rrset_type': 'SRV',
|
||||
'rrset_values': [
|
||||
'0 0 0 .',
|
||||
]
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': '@',
|
||||
'rrset_ttl': 3600,
|
||||
'rrset_type': 'SSHFP',
|
||||
'rrset_values': [
|
||||
'1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49',
|
||||
'1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73'
|
||||
]
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': '@',
|
||||
'rrset_ttl': 3600,
|
||||
'rrset_type': 'CAA',
|
||||
'rrset_values': ['0 issue "ca.unit.tests"']
|
||||
}),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': '@',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'A',
|
||||
'rrset_values': ['1.2.3.4', '1.2.3.5']
|
||||
})
|
||||
])
|
||||
# expected number of total calls
|
||||
self.assertEquals(19, provider._client._request.call_count)
|
||||
|
||||
provider._client._request.reset_mock()
|
||||
|
||||
# delete 1 and update 1
|
||||
provider._client.zone_records = Mock(return_value=[
|
||||
{
|
||||
'rrset_name': 'www',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'A',
|
||||
'rrset_values': ['1.2.3.4']
|
||||
},
|
||||
{
|
||||
'rrset_name': 'www',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'A',
|
||||
'rrset_values': ['2.2.3.4']
|
||||
},
|
||||
{
|
||||
'rrset_name': 'ttl',
|
||||
'rrset_ttl': 600,
|
||||
'rrset_type': 'A',
|
||||
'rrset_values': ['3.2.3.4']
|
||||
}
|
||||
])
|
||||
|
||||
# Domain exists, we don't care about return
|
||||
resp.json.side_effect = ['{}']
|
||||
|
||||
wanted = Zone('unit.tests.', [])
|
||||
wanted.add_record(Record.new(wanted, 'ttl', {
|
||||
'ttl': 300,
|
||||
'type': 'A',
|
||||
'value': '3.2.3.4'
|
||||
}))
|
||||
|
||||
plan = provider.plan(wanted)
|
||||
self.assertTrue(plan.exists)
|
||||
self.assertEquals(2, len(plan.changes))
|
||||
self.assertEquals(2, provider.apply(plan))
|
||||
|
||||
# recreate for update, and deletes for the 2 parts of the other
|
||||
provider._client._request.assert_has_calls([
|
||||
call('DELETE', '/livedns/domains/unit.tests/records/www/A'),
|
||||
call('DELETE', '/livedns/domains/unit.tests/records/ttl/A'),
|
||||
call('POST', '/livedns/domains/unit.tests/records', data={
|
||||
'rrset_name': 'ttl',
|
||||
'rrset_ttl': 300,
|
||||
'rrset_type': 'A',
|
||||
'rrset_values': ['3.2.3.4']
|
||||
})
|
||||
], any_order=True)
|
||||
@@ -0,0 +1,672 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
from __future__ import (
|
||||
absolute_import,
|
||||
division,
|
||||
print_function,
|
||||
unicode_literals,
|
||||
)
|
||||
|
||||
from mock import Mock, call
|
||||
from os.path import dirname, join
|
||||
from requests_mock import ANY, mock as requests_mock
|
||||
from six import text_type
|
||||
from unittest import TestCase
|
||||
|
||||
from octodns.record import Record, Update, Delete, Create
|
||||
from octodns.provider.gcore import (
|
||||
GCoreProvider,
|
||||
GCoreClientBadRequest,
|
||||
GCoreClientNotFound,
|
||||
GCoreClientException,
|
||||
)
|
||||
from octodns.provider.yaml import YamlProvider
|
||||
from octodns.zone import Zone
|
||||
|
||||
|
||||
class TestGCoreProvider(TestCase):
|
||||
expected = Zone("unit.tests.", [])
|
||||
source = YamlProvider("test", join(dirname(__file__), "config"))
|
||||
source.populate(expected)
|
||||
|
||||
default_filters = [
|
||||
{"type": "geodns"},
|
||||
{
|
||||
"type": "default",
|
||||
"limit": 1,
|
||||
"strict": False,
|
||||
},
|
||||
{"type": "first_n", "limit": 1},
|
||||
]
|
||||
|
||||
def test_populate(self):
|
||||
|
||||
provider = GCoreProvider("test_id", token="token")
|
||||
|
||||
# TC: 400 - Bad Request.
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=400, text='{"error":"bad body"}')
|
||||
|
||||
with self.assertRaises(GCoreClientBadRequest) as ctx:
|
||||
zone = Zone("unit.tests.", [])
|
||||
provider.populate(zone)
|
||||
self.assertIn('"error":"bad body"', text_type(ctx.exception))
|
||||
|
||||
# TC: 404 - Not Found.
|
||||
with requests_mock() as mock:
|
||||
mock.get(
|
||||
ANY, status_code=404, text='{"error":"zone is not found"}'
|
||||
)
|
||||
|
||||
with self.assertRaises(GCoreClientNotFound) as ctx:
|
||||
zone = Zone("unit.tests.", [])
|
||||
provider._client.zone(zone.name)
|
||||
self.assertIn(
|
||||
'"error":"zone is not found"', text_type(ctx.exception)
|
||||
)
|
||||
|
||||
# TC: General error
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=500, text="Things caught fire")
|
||||
|
||||
with self.assertRaises(GCoreClientException) as ctx:
|
||||
zone = Zone("unit.tests.", [])
|
||||
provider.populate(zone)
|
||||
self.assertEqual("Things caught fire", text_type(ctx.exception))
|
||||
|
||||
# TC: No credentials or token error
|
||||
with requests_mock() as mock:
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
GCoreProvider("test_id")
|
||||
self.assertEqual(
|
||||
"either token or login & password must be set",
|
||||
text_type(ctx.exception),
|
||||
)
|
||||
|
||||
# TC: Auth with login password
|
||||
with requests_mock() as mock:
|
||||
|
||||
def match_body(request):
|
||||
return {"username": "foo", "password": "bar"} == request.json()
|
||||
|
||||
auth_url = "http://api/auth/jwt/login"
|
||||
mock.post(
|
||||
auth_url,
|
||||
additional_matcher=match_body,
|
||||
status_code=200,
|
||||
json={"access": "access"},
|
||||
)
|
||||
|
||||
providerPassword = GCoreProvider(
|
||||
"test_id",
|
||||
url="http://dns",
|
||||
auth_url="http://api",
|
||||
login="foo",
|
||||
password="bar",
|
||||
)
|
||||
assert mock.called
|
||||
|
||||
# make sure token passed in header
|
||||
zone_rrset_url = "http://dns/zones/unit.tests/rrsets?all=true"
|
||||
mock.get(
|
||||
zone_rrset_url,
|
||||
request_headers={"Authorization": "Bearer access"},
|
||||
status_code=404,
|
||||
)
|
||||
zone = Zone("unit.tests.", [])
|
||||
assert not providerPassword.populate(zone)
|
||||
|
||||
# TC: No diffs == no changes
|
||||
with requests_mock() as mock:
|
||||
base = "https://dnsapi.gcorelabs.com/v2/zones/unit.tests/rrsets"
|
||||
with open("tests/fixtures/gcore-no-changes.json") as fh:
|
||||
mock.get(base, text=fh.read())
|
||||
|
||||
zone = Zone("unit.tests.", [])
|
||||
provider.populate(zone)
|
||||
self.assertEqual(14, len(zone.records))
|
||||
self.assertEqual(
|
||||
{
|
||||
"",
|
||||
"_imap._tcp",
|
||||
"_pop3._tcp",
|
||||
"_srv._tcp",
|
||||
"aaaa",
|
||||
"cname",
|
||||
"excluded",
|
||||
"mx",
|
||||
"ptr",
|
||||
"sub",
|
||||
"txt",
|
||||
"www",
|
||||
"www.sub",
|
||||
},
|
||||
{r.name for r in zone.records},
|
||||
)
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEqual(0, len(changes))
|
||||
|
||||
# TC: 4 create (dynamic) + 1 removed + 7 modified
|
||||
with requests_mock() as mock:
|
||||
base = "https://dnsapi.gcorelabs.com/v2/zones/unit.tests/rrsets"
|
||||
with open("tests/fixtures/gcore-records.json") as fh:
|
||||
mock.get(base, text=fh.read())
|
||||
|
||||
zone = Zone("unit.tests.", [])
|
||||
provider.populate(zone)
|
||||
self.assertEqual(16, len(zone.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEqual(11, len(changes))
|
||||
self.assertEqual(
|
||||
3, len([c for c in changes if isinstance(c, Create)])
|
||||
)
|
||||
self.assertEqual(
|
||||
1, len([c for c in changes if isinstance(c, Delete)])
|
||||
)
|
||||
self.assertEqual(
|
||||
7, len([c for c in changes if isinstance(c, Update)])
|
||||
)
|
||||
|
||||
# TC: no pools can be built
|
||||
with requests_mock() as mock:
|
||||
base = "https://dnsapi.gcorelabs.com/v2/zones/unit.tests/rrsets"
|
||||
mock.get(
|
||||
base,
|
||||
json={
|
||||
"rrsets": [
|
||||
{
|
||||
"name": "unit.tests.",
|
||||
"type": "A",
|
||||
"ttl": 300,
|
||||
"filters": self.default_filters,
|
||||
"resource_records": [{"content": ["7.7.7.7"]}],
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
zone = Zone("unit.tests.", [])
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
provider.populate(zone)
|
||||
|
||||
self.assertTrue(
|
||||
str(ctx.exception).startswith(
|
||||
"filter is enabled, but no pools where built for"
|
||||
),
|
||||
"{} - is not start from desired text".format(ctx.exception),
|
||||
)
|
||||
|
||||
def test_apply(self):
|
||||
provider = GCoreProvider("test_id", url="http://api", token="token")
|
||||
|
||||
# TC: Zone does not exists but can be created.
|
||||
with requests_mock() as mock:
|
||||
mock.get(
|
||||
ANY, status_code=404, text='{"error":"zone is not found"}'
|
||||
)
|
||||
mock.post(ANY, status_code=200, text='{"id":1234}')
|
||||
|
||||
plan = provider.plan(self.expected)
|
||||
provider.apply(plan)
|
||||
|
||||
# TC: Zone does not exists and can't be created.
|
||||
with requests_mock() as mock:
|
||||
mock.get(
|
||||
ANY, status_code=404, text='{"error":"zone is not found"}'
|
||||
)
|
||||
mock.post(
|
||||
ANY,
|
||||
status_code=400,
|
||||
text='{"error":"parent zone is already'
|
||||
' occupied by another client"}',
|
||||
)
|
||||
|
||||
with self.assertRaises(
|
||||
(GCoreClientNotFound, GCoreClientBadRequest)
|
||||
) as ctx:
|
||||
plan = provider.plan(self.expected)
|
||||
provider.apply(plan)
|
||||
self.assertIn(
|
||||
"parent zone is already occupied by another client",
|
||||
text_type(ctx.exception),
|
||||
)
|
||||
|
||||
resp = Mock()
|
||||
resp.json = Mock()
|
||||
provider._client._request = Mock(return_value=resp)
|
||||
|
||||
with open("tests/fixtures/gcore-zone.json") as fh:
|
||||
zone = fh.read()
|
||||
|
||||
# non-existent domain
|
||||
resp.json.side_effect = [
|
||||
GCoreClientNotFound(resp), # no zone in populate
|
||||
GCoreClientNotFound(resp), # no domain during apply
|
||||
zone,
|
||||
]
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# TC: create all
|
||||
self.assertEqual(13, len(plan.changes))
|
||||
self.assertEqual(13, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
provider._client._request.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
"GET",
|
||||
"http://api/zones/unit.tests/rrsets",
|
||||
params={"all": "true"},
|
||||
),
|
||||
call("GET", "http://api/zones/unit.tests"),
|
||||
call("POST", "http://api/zones", data={"name": "unit.tests"}),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/www.sub.unit.tests./A",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"resource_records": [{"content": ["2.2.3.6"]}],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/www.unit.tests./A",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"resource_records": [{"content": ["2.2.3.6"]}],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/txt.unit.tests./TXT",
|
||||
data={
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{"content": ["Bah bah black sheep"]},
|
||||
{"content": ["have you any wool."]},
|
||||
{
|
||||
"content": [
|
||||
"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+"
|
||||
"of/long/string+with+numb3rs"
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/sub.unit.tests./NS",
|
||||
data={
|
||||
"ttl": 3600,
|
||||
"resource_records": [
|
||||
{"content": ["6.2.3.4."]},
|
||||
{"content": ["7.2.3.4."]},
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/ptr.unit.tests./PTR",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{"content": ["foo.bar.com."]},
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/mx.unit.tests./MX",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{"content": [10, "smtp-4.unit.tests."]},
|
||||
{"content": [20, "smtp-2.unit.tests."]},
|
||||
{"content": [30, "smtp-3.unit.tests."]},
|
||||
{"content": [40, "smtp-1.unit.tests."]},
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/excluded.unit.tests./CNAME",
|
||||
data={
|
||||
"ttl": 3600,
|
||||
"resource_records": [{"content": ["unit.tests."]}],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/cname.unit.tests./CNAME",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"resource_records": [{"content": ["unit.tests."]}],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/aaaa.unit.tests./AAAA",
|
||||
data={
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": [
|
||||
"2601:644:500:e210:62f8:1dff:feb8:947a"
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/_srv._tcp.unit.tests./SRV",
|
||||
data={
|
||||
"ttl": 600,
|
||||
"resource_records": [
|
||||
{"content": [10, 20, 30, "foo-1.unit.tests."]},
|
||||
{"content": [12, 20, 30, "foo-2.unit.tests."]},
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/_pop3._tcp.unit.tests./SRV",
|
||||
data={
|
||||
"ttl": 600,
|
||||
"resource_records": [{"content": [0, 0, 0, "."]}],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/_imap._tcp.unit.tests./SRV",
|
||||
data={
|
||||
"ttl": 600,
|
||||
"resource_records": [{"content": [0, 0, 0, "."]}],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/unit.tests./A",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"resource_records": [
|
||||
{"content": ["1.2.3.4"]},
|
||||
{"content": ["1.2.3.5"]},
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
# expected number of total calls
|
||||
self.assertEqual(16, provider._client._request.call_count)
|
||||
|
||||
# TC: delete 1 and update 1
|
||||
provider._client._request.reset_mock()
|
||||
provider._client.zone_records = Mock(
|
||||
return_value=[
|
||||
{
|
||||
"name": "www",
|
||||
"ttl": 300,
|
||||
"type": "A",
|
||||
"resource_records": [{"content": ["1.2.3.4"]}],
|
||||
},
|
||||
{
|
||||
"name": "ttl",
|
||||
"ttl": 600,
|
||||
"type": "A",
|
||||
"resource_records": [{"content": ["3.2.3.4"]}],
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
# Domain exists, we don't care about return
|
||||
resp.json.side_effect = ["{}"]
|
||||
|
||||
wanted = Zone("unit.tests.", [])
|
||||
wanted.add_record(
|
||||
Record.new(
|
||||
wanted, "ttl", {"ttl": 300, "type": "A", "value": "3.2.3.4"}
|
||||
)
|
||||
)
|
||||
|
||||
plan = provider.plan(wanted)
|
||||
self.assertTrue(plan.exists)
|
||||
self.assertEqual(2, len(plan.changes))
|
||||
self.assertEqual(2, provider.apply(plan))
|
||||
|
||||
provider._client._request.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
"DELETE", "http://api/zones/unit.tests/www.unit.tests./A"
|
||||
),
|
||||
call(
|
||||
"PUT",
|
||||
"http://api/zones/unit.tests/ttl.unit.tests./A",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"resource_records": [{"content": ["3.2.3.4"]}],
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
# TC: create dynamics
|
||||
provider._client._request.reset_mock()
|
||||
provider._client.zone_records = Mock(return_value=[])
|
||||
|
||||
# Domain exists, we don't care about return
|
||||
resp.json.side_effect = ["{}"]
|
||||
|
||||
wanted = Zone("unit.tests.", [])
|
||||
wanted.add_record(
|
||||
Record.new(
|
||||
wanted,
|
||||
"geo-simple",
|
||||
{
|
||||
"ttl": 300,
|
||||
"type": "A",
|
||||
"value": "3.3.3.3",
|
||||
"dynamic": {
|
||||
"pools": {
|
||||
"pool-1": {
|
||||
"fallback": "other",
|
||||
"values": [
|
||||
{"value": "1.1.1.1"},
|
||||
{"value": "1.1.1.2"},
|
||||
],
|
||||
},
|
||||
"pool-2": {
|
||||
"fallback": "other",
|
||||
"values": [
|
||||
{"value": "2.2.2.1"},
|
||||
],
|
||||
},
|
||||
"other": {"values": [{"value": "3.3.3.3"}]},
|
||||
},
|
||||
"rules": [
|
||||
{"pool": "pool-1", "geos": ["EU-RU"]},
|
||||
{"pool": "pool-2", "geos": ["EU"]},
|
||||
{"pool": "other"},
|
||||
],
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
wanted.add_record(
|
||||
Record.new(
|
||||
wanted,
|
||||
"geo-defaults",
|
||||
{
|
||||
"ttl": 300,
|
||||
"type": "A",
|
||||
"value": "3.2.3.4",
|
||||
"dynamic": {
|
||||
"pools": {
|
||||
"pool-1": {
|
||||
"values": [
|
||||
{"value": "2.2.2.1"},
|
||||
],
|
||||
},
|
||||
},
|
||||
"rules": [
|
||||
{"pool": "pool-1", "geos": ["EU"]},
|
||||
],
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
wanted.add_record(
|
||||
Record.new(
|
||||
wanted,
|
||||
"cname-smpl",
|
||||
{
|
||||
"ttl": 300,
|
||||
"type": "CNAME",
|
||||
"value": "en.unit.tests.",
|
||||
"dynamic": {
|
||||
"pools": {
|
||||
"pool-1": {
|
||||
"fallback": "other",
|
||||
"values": [
|
||||
{"value": "ru-1.unit.tests."},
|
||||
{"value": "ru-2.unit.tests."},
|
||||
],
|
||||
},
|
||||
"pool-2": {
|
||||
"fallback": "other",
|
||||
"values": [
|
||||
{"value": "eu.unit.tests."},
|
||||
],
|
||||
},
|
||||
"other": {"values": [{"value": "en.unit.tests."}]},
|
||||
},
|
||||
"rules": [
|
||||
{"pool": "pool-1", "geos": ["EU-RU"]},
|
||||
{"pool": "pool-2", "geos": ["EU"]},
|
||||
{"pool": "other"},
|
||||
],
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
wanted.add_record(
|
||||
Record.new(
|
||||
wanted,
|
||||
"cname-dflt",
|
||||
{
|
||||
"ttl": 300,
|
||||
"type": "CNAME",
|
||||
"value": "en.unit.tests.",
|
||||
"dynamic": {
|
||||
"pools": {
|
||||
"pool-1": {
|
||||
"values": [
|
||||
{"value": "eu.unit.tests."},
|
||||
],
|
||||
},
|
||||
},
|
||||
"rules": [
|
||||
{"pool": "pool-1", "geos": ["EU"]},
|
||||
],
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
plan = provider.plan(wanted)
|
||||
self.assertTrue(plan.exists)
|
||||
self.assertEqual(4, len(plan.changes))
|
||||
self.assertEqual(4, provider.apply(plan))
|
||||
|
||||
provider._client._request.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/geo-simple.unit.tests./A",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"filters": self.default_filters,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": ["1.1.1.1"],
|
||||
"meta": {"countries": ["RU"]},
|
||||
},
|
||||
{
|
||||
"content": ["1.1.1.2"],
|
||||
"meta": {"countries": ["RU"]},
|
||||
},
|
||||
{
|
||||
"content": ["2.2.2.1"],
|
||||
"meta": {"continents": ["EU"]},
|
||||
},
|
||||
{
|
||||
"content": ["3.3.3.3"],
|
||||
"meta": {"default": True},
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/geo-defaults.unit.tests./A",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"filters": self.default_filters,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": ["2.2.2.1"],
|
||||
"meta": {"continents": ["EU"]},
|
||||
},
|
||||
{
|
||||
"content": ["3.2.3.4"],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/cname-smpl.unit.tests./CNAME",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"filters": self.default_filters,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": ["ru-1.unit.tests."],
|
||||
"meta": {"countries": ["RU"]},
|
||||
},
|
||||
{
|
||||
"content": ["ru-2.unit.tests."],
|
||||
"meta": {"countries": ["RU"]},
|
||||
},
|
||||
{
|
||||
"content": ["eu.unit.tests."],
|
||||
"meta": {"continents": ["EU"]},
|
||||
},
|
||||
{
|
||||
"content": ["en.unit.tests."],
|
||||
"meta": {"default": True},
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
call(
|
||||
"POST",
|
||||
"http://api/zones/unit.tests/cname-dflt.unit.tests./CNAME",
|
||||
data={
|
||||
"ttl": 300,
|
||||
"filters": self.default_filters,
|
||||
"resource_records": [
|
||||
{
|
||||
"content": ["eu.unit.tests."],
|
||||
"meta": {"continents": ["EU"]},
|
||||
},
|
||||
{
|
||||
"content": ["en.unit.tests."],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,341 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
unicode_literals
|
||||
|
||||
from mock import Mock, call
|
||||
from os.path import dirname, join
|
||||
from requests import HTTPError
|
||||
from requests_mock import ANY, mock as requests_mock
|
||||
from six import text_type
|
||||
from unittest import TestCase
|
||||
|
||||
from octodns.record import Record
|
||||
from octodns.provider.hetzner import HetznerClientNotFound, \
|
||||
HetznerProvider
|
||||
from octodns.provider.yaml import YamlProvider
|
||||
from octodns.zone import Zone
|
||||
|
||||
|
||||
class TestHetznerProvider(TestCase):
|
||||
expected = Zone('unit.tests.', [])
|
||||
source = YamlProvider('test', join(dirname(__file__), 'config'))
|
||||
source.populate(expected)
|
||||
|
||||
def test_populate(self):
|
||||
provider = HetznerProvider('test', 'token')
|
||||
|
||||
# Bad auth
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=401,
|
||||
text='{"message":"Invalid authentication credentials"}')
|
||||
|
||||
with self.assertRaises(Exception) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals('Unauthorized', text_type(ctx.exception))
|
||||
|
||||
# General error
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=502, text='Things caught fire')
|
||||
|
||||
with self.assertRaises(HTTPError) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(502, ctx.exception.response.status_code)
|
||||
|
||||
# Non-existent zone doesn't populate anything
|
||||
with requests_mock() as mock:
|
||||
mock.get(ANY, status_code=404,
|
||||
text='{"zone":{"id":"","name":"","ttl":0,"registrar":"",'
|
||||
'"legacy_dns_host":"","legacy_ns":null,"ns":null,'
|
||||
'"created":"","verified":"","modified":"","project":"",'
|
||||
'"owner":"","permission":"","zone_type":{"id":"",'
|
||||
'"name":"","description":"","prices":null},"status":"",'
|
||||
'"paused":false,"is_secondary_dns":false,'
|
||||
'"txt_verification":{"name":"","token":""},'
|
||||
'"records_count":0},"error":{'
|
||||
'"message":"zone not found","code":404}}')
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(set(), zone.records)
|
||||
|
||||
# No diffs == no changes
|
||||
with requests_mock() as mock:
|
||||
base = provider._client.BASE_URL
|
||||
with open('tests/fixtures/hetzner-zones.json') as fh:
|
||||
mock.get('{}/zones'.format(base), text=fh.read())
|
||||
with open('tests/fixtures/hetzner-records.json') as fh:
|
||||
mock.get('{}/records'.format(base), text=fh.read())
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(13, len(zone.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEquals(0, len(changes))
|
||||
|
||||
# 2nd populate makes no network calls/all from cache
|
||||
again = Zone('unit.tests.', [])
|
||||
provider.populate(again)
|
||||
self.assertEquals(13, len(again.records))
|
||||
|
||||
# bust the cache
|
||||
del provider._zone_records[zone.name]
|
||||
|
||||
def test_apply(self):
|
||||
provider = HetznerProvider('test', 'token')
|
||||
|
||||
resp = Mock()
|
||||
resp.json = Mock()
|
||||
provider._client._do = Mock(return_value=resp)
|
||||
|
||||
domain_after_creation = {'zone': {
|
||||
'id': 'unit.tests',
|
||||
'name': 'unit.tests',
|
||||
'ttl': 3600,
|
||||
}}
|
||||
|
||||
# non-existent domain, create everything
|
||||
resp.json.side_effect = [
|
||||
HetznerClientNotFound, # no zone in populate
|
||||
HetznerClientNotFound, # no zone during apply
|
||||
domain_after_creation,
|
||||
]
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 10
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
provider._client._do.assert_has_calls([
|
||||
# created the zone
|
||||
call('POST', '/zones', None, {
|
||||
'name': 'unit.tests',
|
||||
'ttl': None,
|
||||
}),
|
||||
# created all the records with their expected data
|
||||
call('POST', '/records', data={
|
||||
'name': '@',
|
||||
'ttl': 300,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': '@',
|
||||
'ttl': 300,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.5',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': '@',
|
||||
'ttl': 3600,
|
||||
'type': 'CAA',
|
||||
'value': '0 issue "ca.unit.tests"',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': '_imap._tcp',
|
||||
'ttl': 600,
|
||||
'type': 'SRV',
|
||||
'value': '0 0 0 .',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': '_pop3._tcp',
|
||||
'ttl': 600,
|
||||
'type': 'SRV',
|
||||
'value': '0 0 0 .',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': '_srv._tcp',
|
||||
'ttl': 600,
|
||||
'type': 'SRV',
|
||||
'value': '10 20 30 foo-1.unit.tests.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': '_srv._tcp',
|
||||
'ttl': 600,
|
||||
'type': 'SRV',
|
||||
'value': '12 20 30 foo-2.unit.tests.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'aaaa',
|
||||
'ttl': 600,
|
||||
'type': 'AAAA',
|
||||
'value': '2601:644:500:e210:62f8:1dff:feb8:947a',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'cname',
|
||||
'ttl': 300,
|
||||
'type': 'CNAME',
|
||||
'value': 'unit.tests.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'included',
|
||||
'ttl': 3600,
|
||||
'type': 'CNAME',
|
||||
'value': 'unit.tests.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'mx',
|
||||
'ttl': 300,
|
||||
'type': 'MX',
|
||||
'value': '10 smtp-4.unit.tests.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'mx',
|
||||
'ttl': 300,
|
||||
'type': 'MX',
|
||||
'value': '20 smtp-2.unit.tests.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'mx',
|
||||
'ttl': 300,
|
||||
'type': 'MX',
|
||||
'value': '30 smtp-3.unit.tests.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'mx',
|
||||
'ttl': 300,
|
||||
'type': 'MX',
|
||||
'value': '40 smtp-1.unit.tests.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'sub',
|
||||
'ttl': 3600,
|
||||
'type': 'NS',
|
||||
'value': '6.2.3.4.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'sub',
|
||||
'ttl': 3600,
|
||||
'type': 'NS',
|
||||
'value': '7.2.3.4.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'txt',
|
||||
'ttl': 600,
|
||||
'type': 'TXT',
|
||||
'value': 'Bah bah black sheep',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'txt',
|
||||
'ttl': 600,
|
||||
'type': 'TXT',
|
||||
'value': 'have you any wool.',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'txt',
|
||||
'ttl': 600,
|
||||
'type': 'TXT',
|
||||
'value': 'v=DKIM1;k=rsa;s=email;h=sha256;'
|
||||
'p=A/kinda+of/long/string+with+numb3rs',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'www',
|
||||
'ttl': 300,
|
||||
'type': 'A',
|
||||
'value': '2.2.3.6',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('POST', '/records', data={
|
||||
'name': 'www.sub',
|
||||
'ttl': 300,
|
||||
'type': 'A',
|
||||
'value': '2.2.3.6',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
])
|
||||
self.assertEquals(24, provider._client._do.call_count)
|
||||
|
||||
provider._client._do.reset_mock()
|
||||
|
||||
# delete 1 and update 1
|
||||
provider._client.zone_get = Mock(return_value={
|
||||
'id': 'unit.tests',
|
||||
'name': 'unit.tests',
|
||||
'ttl': 3600,
|
||||
})
|
||||
provider._client.zone_records_get = Mock(return_value=[
|
||||
{
|
||||
'type': 'A',
|
||||
'id': 'one',
|
||||
'created': '0000-00-00T00:00:00Z',
|
||||
'modified': '0000-00-00T00:00:00Z',
|
||||
'zone_id': 'unit.tests',
|
||||
'name': 'www',
|
||||
'value': '1.2.3.4',
|
||||
'ttl': 300,
|
||||
},
|
||||
{
|
||||
'type': 'A',
|
||||
'id': 'two',
|
||||
'created': '0000-00-00T00:00:00Z',
|
||||
'modified': '0000-00-00T00:00:00Z',
|
||||
'zone_id': 'unit.tests',
|
||||
'name': 'www',
|
||||
'value': '2.2.3.4',
|
||||
'ttl': 300,
|
||||
},
|
||||
{
|
||||
'type': 'A',
|
||||
'id': 'three',
|
||||
'created': '0000-00-00T00:00:00Z',
|
||||
'modified': '0000-00-00T00:00:00Z',
|
||||
'zone_id': 'unit.tests',
|
||||
'name': 'ttl',
|
||||
'value': '3.2.3.4',
|
||||
'ttl': 600,
|
||||
},
|
||||
])
|
||||
|
||||
# Domain exists, we don't care about return
|
||||
resp.json.side_effect = ['{}']
|
||||
|
||||
wanted = Zone('unit.tests.', [])
|
||||
wanted.add_record(Record.new(wanted, 'ttl', {
|
||||
'ttl': 300,
|
||||
'type': 'A',
|
||||
'value': '3.2.3.4',
|
||||
}))
|
||||
|
||||
plan = provider.plan(wanted)
|
||||
self.assertTrue(plan.exists)
|
||||
self.assertEquals(2, len(plan.changes))
|
||||
self.assertEquals(2, provider.apply(plan))
|
||||
# recreate for update, and delete for the 2 parts of the other
|
||||
provider._client._do.assert_has_calls([
|
||||
call('POST', '/records', data={
|
||||
'name': 'ttl',
|
||||
'ttl': 300,
|
||||
'type': 'A',
|
||||
'value': '3.2.3.4',
|
||||
'zone_id': 'unit.tests',
|
||||
}),
|
||||
call('DELETE', '/records/one'),
|
||||
call('DELETE', '/records/two'),
|
||||
call('DELETE', '/records/three'),
|
||||
], any_order=True)
|
||||
@@ -171,7 +171,7 @@ class TestMythicBeastsProvider(TestCase):
|
||||
|
||||
def test_command_generation(self):
|
||||
zone = Zone('unit.tests.', [])
|
||||
zone.add_record(Record.new(zone, 'prawf-alias', {
|
||||
zone.add_record(Record.new(zone, '', {
|
||||
'ttl': 60,
|
||||
'type': 'ALIAS',
|
||||
'value': 'alias.unit.tests.',
|
||||
@@ -228,7 +228,7 @@ class TestMythicBeastsProvider(TestCase):
|
||||
)
|
||||
|
||||
expected_commands = [
|
||||
'ADD prawf-alias.unit.tests 60 ANAME alias.unit.tests.',
|
||||
'ADD unit.tests 60 ANAME alias.unit.tests.',
|
||||
'ADD prawf-ns.unit.tests 300 NS alias.unit.tests.',
|
||||
'ADD prawf-ns.unit.tests 300 NS alias2.unit.tests.',
|
||||
'ADD prawf-a.unit.tests 60 A 1.2.3.4',
|
||||
@@ -378,8 +378,8 @@ class TestMythicBeastsProvider(TestCase):
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
|
||||
self.assertEquals(15, len(zone.records))
|
||||
self.assertEquals(15, len(self.expected.records))
|
||||
self.assertEquals(17, len(zone.records))
|
||||
self.assertEquals(17, len(self.expected.records))
|
||||
changes = self.expected.changes(zone, provider)
|
||||
self.assertEquals(0, len(changes))
|
||||
|
||||
@@ -445,7 +445,7 @@ class TestMythicBeastsProvider(TestCase):
|
||||
if isinstance(c, Update)]))
|
||||
self.assertEquals(1, len([c for c in plan.changes
|
||||
if isinstance(c, Delete)]))
|
||||
self.assertEquals(14, len([c for c in plan.changes
|
||||
self.assertEquals(16, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
self.assertEquals(16, provider.apply(plan))
|
||||
self.assertEquals(18, provider.apply(plan))
|
||||
self.assertTrue(plan.exists)
|
||||
|
||||
+776
-119
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,20 @@ class TestPowerDnsProvider(TestCase):
|
||||
provider._powerdns_version = None
|
||||
self.assertNotEquals(provider.powerdns_version, [4, 1, 10])
|
||||
|
||||
# Test version detection with pre-releases
|
||||
with requests_mock() as mock:
|
||||
# Reset version, so detection will try again
|
||||
provider._powerdns_version = None
|
||||
mock.get('http://non.existent:8081/api/v1/servers/localhost',
|
||||
status_code=200, json={'version': "4.4.0-alpha1"})
|
||||
self.assertEquals(provider.powerdns_version, [4, 4, 0])
|
||||
|
||||
provider._powerdns_version = None
|
||||
mock.get('http://non.existent:8081/api/v1/servers/localhost',
|
||||
status_code=200,
|
||||
json={'version': "4.5.0-alpha0.435.master.gcb114252b"})
|
||||
self.assertEquals(provider.powerdns_version, [4, 5, 0])
|
||||
|
||||
def test_provider_version_config(self):
|
||||
provider = PowerDnsProvider('test', 'non.existent', 'api-key',
|
||||
nameserver_values=['8.8.8.8.',
|
||||
@@ -171,8 +185,8 @@ class TestPowerDnsProvider(TestCase):
|
||||
expected = Zone('unit.tests.', [])
|
||||
source = YamlProvider('test', join(dirname(__file__), 'config'))
|
||||
source.populate(expected)
|
||||
expected_n = len(expected.records) - 2
|
||||
self.assertEquals(16, expected_n)
|
||||
expected_n = len(expected.records) - 4
|
||||
self.assertEquals(19, expected_n)
|
||||
|
||||
# No diffs == no changes
|
||||
with requests_mock() as mock:
|
||||
@@ -180,7 +194,7 @@ class TestPowerDnsProvider(TestCase):
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(16, len(zone.records))
|
||||
self.assertEquals(19, len(zone.records))
|
||||
changes = expected.changes(zone, provider)
|
||||
self.assertEquals(0, len(changes))
|
||||
|
||||
@@ -277,7 +291,7 @@ class TestPowerDnsProvider(TestCase):
|
||||
expected = Zone('unit.tests.', [])
|
||||
source = YamlProvider('test', join(dirname(__file__), 'config'))
|
||||
source.populate(expected)
|
||||
self.assertEquals(18, len(expected.records))
|
||||
self.assertEquals(23, len(expected.records))
|
||||
|
||||
# A small change to a single record
|
||||
with requests_mock() as mock:
|
||||
|
||||
@@ -394,6 +394,139 @@ class TestRoute53Provider(TestCase):
|
||||
|
||||
return (provider, stubber)
|
||||
|
||||
def test_process_desired_zone(self):
|
||||
provider, stubber = self._get_stubbed_fallback_auth_provider()
|
||||
|
||||
# No records, essentially a no-op
|
||||
desired = Zone('unit.tests.', [])
|
||||
got = provider._process_desired_zone(desired)
|
||||
self.assertEquals(desired.records, got.records)
|
||||
|
||||
# Record without any geos
|
||||
desired = Zone('unit.tests.', [])
|
||||
record = Record.new(desired, 'a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [{
|
||||
'value': '2.2.3.4',
|
||||
}],
|
||||
},
|
||||
},
|
||||
'rules': [{
|
||||
'pool': 'one',
|
||||
}],
|
||||
},
|
||||
})
|
||||
desired.add_record(record)
|
||||
got = provider._process_desired_zone(desired)
|
||||
self.assertEquals(desired.records, got.records)
|
||||
self.assertEquals(1, len(list(got.records)[0].dynamic.rules))
|
||||
self.assertFalse('geos' in list(got.records)[0].dynamic.rules[0].data)
|
||||
|
||||
# Record where all geos are supported
|
||||
desired = Zone('unit.tests.', [])
|
||||
record = Record.new(desired, 'a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [{
|
||||
'value': '1.2.3.4',
|
||||
}],
|
||||
},
|
||||
'two': {
|
||||
'values': [{
|
||||
'value': '2.2.3.4',
|
||||
}],
|
||||
},
|
||||
},
|
||||
'rules': [{
|
||||
'geos': ['EU', 'NA-US-OR'],
|
||||
'pool': 'two',
|
||||
}, {
|
||||
'pool': 'one',
|
||||
}],
|
||||
},
|
||||
})
|
||||
desired.add_record(record)
|
||||
got = provider._process_desired_zone(desired)
|
||||
self.assertEquals(2, len(list(got.records)[0].dynamic.rules))
|
||||
self.assertEquals(['EU', 'NA-US-OR'],
|
||||
list(got.records)[0].dynamic.rules[0].data['geos'])
|
||||
self.assertFalse('geos' in list(got.records)[0].dynamic.rules[1].data)
|
||||
|
||||
# Record with NA-CA-* only rule which is removed
|
||||
desired = Zone('unit.tests.', [])
|
||||
record = Record.new(desired, 'a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [{
|
||||
'value': '1.2.3.4',
|
||||
}],
|
||||
},
|
||||
'two': {
|
||||
'values': [{
|
||||
'value': '2.2.3.4',
|
||||
}],
|
||||
},
|
||||
},
|
||||
'rules': [{
|
||||
'geos': ['NA-CA-BC'],
|
||||
'pool': 'two',
|
||||
}, {
|
||||
'pool': 'one',
|
||||
}],
|
||||
},
|
||||
})
|
||||
desired.add_record(record)
|
||||
got = provider._process_desired_zone(desired)
|
||||
self.assertEquals(1, len(list(got.records)[0].dynamic.rules))
|
||||
self.assertFalse('geos' in list(got.records)[0].dynamic.rules[0].data)
|
||||
|
||||
# Record with NA-CA-* rule combined with other geos, filtered
|
||||
desired = Zone('unit.tests.', [])
|
||||
record = Record.new(desired, 'a', {
|
||||
'ttl': 30,
|
||||
'type': 'A',
|
||||
'value': '1.2.3.4',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [{
|
||||
'value': '1.2.3.4',
|
||||
}],
|
||||
},
|
||||
'two': {
|
||||
'values': [{
|
||||
'value': '2.2.3.4',
|
||||
}],
|
||||
},
|
||||
},
|
||||
'rules': [{
|
||||
'geos': ['EU', 'NA-CA-NB', 'NA-US-OR'],
|
||||
'pool': 'two',
|
||||
}, {
|
||||
'pool': 'one',
|
||||
}],
|
||||
},
|
||||
})
|
||||
desired.add_record(record)
|
||||
got = provider._process_desired_zone(desired)
|
||||
self.assertEquals(2, len(list(got.records)[0].dynamic.rules))
|
||||
self.assertEquals(['EU', 'NA-US-OR'],
|
||||
list(got.records)[0].dynamic.rules[0].data['geos'])
|
||||
self.assertFalse('geos' in list(got.records)[0].dynamic.rules[1].data)
|
||||
|
||||
def test_populate_with_fallback(self):
|
||||
provider, stubber = self._get_stubbed_fallback_auth_provider()
|
||||
|
||||
@@ -1166,6 +1299,31 @@ class TestRoute53Provider(TestCase):
|
||||
})
|
||||
stubber.add_response('change_tags_for_resource', {})
|
||||
|
||||
health_check_config = {
|
||||
'EnableSNI': False,
|
||||
'FailureThreshold': 6,
|
||||
'FullyQualifiedDomainName': '4.2.3.4',
|
||||
'IPAddress': '4.2.3.4',
|
||||
'MeasureLatency': True,
|
||||
'Port': 8080,
|
||||
'RequestInterval': 10,
|
||||
'ResourcePath': '/_status',
|
||||
'Type': 'HTTP'
|
||||
}
|
||||
stubber.add_response('create_health_check', {
|
||||
'HealthCheck': {
|
||||
'Id': '43',
|
||||
'CallerReference': self.caller_ref,
|
||||
'HealthCheckConfig': health_check_config,
|
||||
'HealthCheckVersion': 1,
|
||||
},
|
||||
'Location': 'http://url',
|
||||
}, {
|
||||
'CallerReference': ANY,
|
||||
'HealthCheckConfig': health_check_config,
|
||||
})
|
||||
stubber.add_response('change_tags_for_resource', {})
|
||||
|
||||
record = Record.new(self.expected, '', {
|
||||
'ttl': 61,
|
||||
'type': 'A',
|
||||
@@ -1191,6 +1349,11 @@ class TestRoute53Provider(TestCase):
|
||||
# when allowed to create we do
|
||||
id = provider.get_health_check_id(record, value, True)
|
||||
self.assertEquals('42', id)
|
||||
|
||||
# when allowed to create and when host is None
|
||||
record._octodns['healthcheck']['host'] = None
|
||||
id = provider.get_health_check_id(record, value, True)
|
||||
self.assertEquals('43', id)
|
||||
stubber.assert_no_pending_responses()
|
||||
|
||||
# A CNAME style healthcheck, without a value
|
||||
@@ -1962,6 +2125,163 @@ class TestRoute53Provider(TestCase):
|
||||
self.assertEquals(1, len(extra))
|
||||
stubber.assert_no_pending_responses()
|
||||
|
||||
def test_extra_change_dynamic_has_health_check_cname(self):
|
||||
provider, stubber = self._get_stubbed_provider()
|
||||
|
||||
list_hosted_zones_resp = {
|
||||
'HostedZones': [{
|
||||
'Name': 'unit.tests.',
|
||||
'Id': 'z42',
|
||||
'CallerReference': 'abc',
|
||||
}],
|
||||
'Marker': 'm',
|
||||
'IsTruncated': False,
|
||||
'MaxItems': '100',
|
||||
}
|
||||
stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
|
||||
|
||||
# record with geo and no health check returns change
|
||||
desired = Zone('unit.tests.', [])
|
||||
record = Record.new(desired, 'cname', {
|
||||
'ttl': 30,
|
||||
'type': 'CNAME',
|
||||
'value': 'cname.unit.tests.',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [{
|
||||
'value': 'one.cname.unit.tests.',
|
||||
}],
|
||||
},
|
||||
},
|
||||
'rules': [{
|
||||
'pool': 'one',
|
||||
}],
|
||||
},
|
||||
})
|
||||
desired.add_record(record)
|
||||
list_resource_record_sets_resp = {
|
||||
'ResourceRecordSets': [{
|
||||
# Not dynamic value and other name
|
||||
'Name': 'unit.tests.',
|
||||
'Type': 'CNAME',
|
||||
'GeoLocation': {
|
||||
'CountryCode': '*',
|
||||
},
|
||||
'ResourceRecords': [{
|
||||
'Value': 'cname.unit.tests.',
|
||||
}],
|
||||
'TTL': 61,
|
||||
# All the non-matches have a different Id so we'll fail if they
|
||||
# match
|
||||
'HealthCheckId': '33',
|
||||
}, {
|
||||
# Not dynamic value, matching name, other type
|
||||
'Name': 'cname.unit.tests.',
|
||||
'Type': 'AAAA',
|
||||
'ResourceRecords': [{
|
||||
'Value': '2001:0db8:3c4d:0015:0000:0000:1a2f:1a4b'
|
||||
}],
|
||||
'TTL': 61,
|
||||
'HealthCheckId': '33',
|
||||
}, {
|
||||
# default value pool
|
||||
'Name': '_octodns-default-value.cname.unit.tests.',
|
||||
'Type': 'CNAME',
|
||||
'GeoLocation': {
|
||||
'CountryCode': '*',
|
||||
},
|
||||
'ResourceRecords': [{
|
||||
'Value': 'cname.unit.tests.',
|
||||
}],
|
||||
'TTL': 61,
|
||||
'HealthCheckId': '33',
|
||||
}, {
|
||||
# different record
|
||||
'Name': '_octodns-two-value.other.unit.tests.',
|
||||
'Type': 'CNAME',
|
||||
'GeoLocation': {
|
||||
'CountryCode': '*',
|
||||
},
|
||||
'ResourceRecords': [{
|
||||
'Value': 'cname.unit.tests.',
|
||||
}],
|
||||
'TTL': 61,
|
||||
'HealthCheckId': '33',
|
||||
}, {
|
||||
# same everything, but different type
|
||||
'Name': '_octodns-one-value.cname.unit.tests.',
|
||||
'Type': 'AAAA',
|
||||
'ResourceRecords': [{
|
||||
'Value': '2001:0db8:3c4d:0015:0000:0000:1a2f:1a4b'
|
||||
}],
|
||||
'TTL': 61,
|
||||
'HealthCheckId': '33',
|
||||
}, {
|
||||
# same everything, sub
|
||||
'Name': '_octodns-one-value.sub.cname.unit.tests.',
|
||||
'Type': 'CNAME',
|
||||
'ResourceRecords': [{
|
||||
'Value': 'cname.unit.tests.',
|
||||
}],
|
||||
'TTL': 61,
|
||||
'HealthCheckId': '33',
|
||||
}, {
|
||||
# match
|
||||
'Name': '_octodns-one-value.cname.unit.tests.',
|
||||
'Type': 'CNAME',
|
||||
'ResourceRecords': [{
|
||||
'Value': 'one.cname.unit.tests.',
|
||||
}],
|
||||
'TTL': 61,
|
||||
'HealthCheckId': '42',
|
||||
}],
|
||||
'IsTruncated': False,
|
||||
'MaxItems': '100',
|
||||
}
|
||||
stubber.add_response('list_resource_record_sets',
|
||||
list_resource_record_sets_resp,
|
||||
{'HostedZoneId': 'z42'})
|
||||
|
||||
stubber.add_response('list_health_checks', {
|
||||
'HealthChecks': [{
|
||||
'Id': '42',
|
||||
'CallerReference': self.caller_ref,
|
||||
'HealthCheckConfig': {
|
||||
'Type': 'HTTPS',
|
||||
'FullyQualifiedDomainName': 'one.cname.unit.tests.',
|
||||
'ResourcePath': '/_dns',
|
||||
'Type': 'HTTPS',
|
||||
'Port': 443,
|
||||
'MeasureLatency': True,
|
||||
'RequestInterval': 10,
|
||||
},
|
||||
'HealthCheckVersion': 2,
|
||||
}],
|
||||
'IsTruncated': False,
|
||||
'MaxItems': '100',
|
||||
'Marker': '',
|
||||
})
|
||||
extra = provider._extra_changes(desired=desired, changes=[])
|
||||
self.assertEquals(0, len(extra))
|
||||
stubber.assert_no_pending_responses()
|
||||
|
||||
# change b/c of healthcheck path
|
||||
record._octodns['healthcheck'] = {
|
||||
'path': '/_ready'
|
||||
}
|
||||
extra = provider._extra_changes(desired=desired, changes=[])
|
||||
self.assertEquals(1, len(extra))
|
||||
stubber.assert_no_pending_responses()
|
||||
|
||||
# no change b/c healthcheck host ignored for dynamic cname
|
||||
record._octodns['healthcheck'] = {
|
||||
'host': 'foo.bar.io'
|
||||
}
|
||||
extra = provider._extra_changes(desired=desired, changes=[])
|
||||
self.assertEquals(0, len(extra))
|
||||
stubber.assert_no_pending_responses()
|
||||
|
||||
def _get_test_plan(self, max_changes):
|
||||
|
||||
provider = Route53Provider('test', 'abc', '123', max_changes)
|
||||
|
||||
@@ -56,10 +56,11 @@ class MockDomainService(DomainService):
|
||||
|
||||
_dns_entries.extend(entries_for(name, record))
|
||||
|
||||
# NS is not supported as a DNS Entry,
|
||||
# so it should cover the if statement
|
||||
# Add a non-supported type
|
||||
# so it triggers the "is supported" (transip.py:115) check and
|
||||
# give 100% code coverage
|
||||
_dns_entries.append(
|
||||
DnsEntry('@', '3600', 'NS', 'ns01.transip.nl.'))
|
||||
DnsEntry('@', '3600', 'BOGUS', 'ns01.transip.nl.'))
|
||||
|
||||
self.mockupEntries = _dns_entries
|
||||
|
||||
@@ -222,7 +223,7 @@ N4OiVz1I3rbZGYa396lpxO6ku8yCglisL1yrSP6DdEUp66ntpKVd
|
||||
provider._client = MockDomainService('unittest', self.bogus_key)
|
||||
plan = provider.plan(_expected)
|
||||
|
||||
self.assertEqual(12, plan.change_counts['Create'])
|
||||
self.assertEqual(15, plan.change_counts['Create'])
|
||||
self.assertEqual(0, plan.change_counts['Update'])
|
||||
self.assertEqual(0, plan.change_counts['Delete'])
|
||||
|
||||
@@ -235,7 +236,7 @@ N4OiVz1I3rbZGYa396lpxO6ku8yCglisL1yrSP6DdEUp66ntpKVd
|
||||
provider = TransipProvider('test', 'unittest', self.bogus_key)
|
||||
provider._client = MockDomainService('unittest', self.bogus_key)
|
||||
plan = provider.plan(_expected)
|
||||
self.assertEqual(12, len(plan.changes))
|
||||
self.assertEqual(15, len(plan.changes))
|
||||
changes = provider.apply(plan)
|
||||
self.assertEqual(changes, len(plan.changes))
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mock import Mock, call
|
||||
from os.path import dirname, join
|
||||
from requests import HTTPError
|
||||
from requests_mock import ANY, mock as requests_mock
|
||||
from six import text_type
|
||||
from six.moves.urllib import parse
|
||||
from unittest import TestCase
|
||||
from json import load as json_load
|
||||
|
||||
@@ -55,7 +58,8 @@ class TestUltraProvider(TestCase):
|
||||
self.assertEquals(1, mock.call_count)
|
||||
expected_payload = "grant_type=password&username=user&"\
|
||||
"password=rightpass"
|
||||
self.assertEquals(mock.last_request.text, expected_payload)
|
||||
self.assertEquals(parse.parse_qs(mock.last_request.text),
|
||||
parse.parse_qs(expected_payload))
|
||||
|
||||
def test_get_zones(self):
|
||||
provider = _get_provider()
|
||||
@@ -274,7 +278,7 @@ class TestUltraProvider(TestCase):
|
||||
|
||||
self.assertTrue(provider.populate(zone))
|
||||
self.assertEquals('octodns1.test.', zone.name)
|
||||
self.assertEquals(11, len(zone.records))
|
||||
self.assertEquals(12, len(zone.records))
|
||||
self.assertEquals(4, mock.call_count)
|
||||
|
||||
def test_apply(self):
|
||||
@@ -285,12 +289,12 @@ class TestUltraProvider(TestCase):
|
||||
provider._request.side_effect = [
|
||||
UltraNoZonesExistException('No Zones'),
|
||||
None, # zone create
|
||||
] + [None] * 13 # individual record creates
|
||||
] + [None] * 15 # individual record creates
|
||||
|
||||
# non-existent zone, create everything
|
||||
plan = provider.plan(self.expected)
|
||||
self.assertEquals(13, len(plan.changes))
|
||||
self.assertEquals(13, provider.apply(plan))
|
||||
self.assertEquals(15, len(plan.changes))
|
||||
self.assertEquals(15, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
provider._request.assert_has_calls([
|
||||
@@ -320,7 +324,7 @@ class TestUltraProvider(TestCase):
|
||||
'p=A/kinda+of/long/string+with+numb3rs']}),
|
||||
], True)
|
||||
# expected number of total calls
|
||||
self.assertEquals(15, provider._request.call_count)
|
||||
self.assertEquals(17, provider._request.call_count)
|
||||
|
||||
# Create sample rrset payload to attempt to alter
|
||||
page1 = json_load(open('tests/fixtures/ultra-records-page-1.json'))
|
||||
@@ -352,8 +356,8 @@ class TestUltraProvider(TestCase):
|
||||
}))
|
||||
|
||||
plan = provider.plan(wanted)
|
||||
self.assertEquals(10, len(plan.changes))
|
||||
self.assertEquals(10, provider.apply(plan))
|
||||
self.assertEquals(11, len(plan.changes))
|
||||
self.assertEquals(11, provider.apply(plan))
|
||||
self.assertTrue(plan.exists)
|
||||
|
||||
provider._request.assert_has_calls([
|
||||
@@ -492,6 +496,15 @@ class TestUltraProvider(TestCase):
|
||||
Record.new(zone, 'txt',
|
||||
{'ttl': 60, 'type': 'TXT',
|
||||
'values': ['abc', 'def']})),
|
||||
|
||||
# ALIAS
|
||||
('', 'ALIAS',
|
||||
'/v2/zones/unit.tests./rrsets/APEXALIAS/unit.tests.',
|
||||
{'ttl': 60, 'rdata': ['target.unit.tests.']},
|
||||
Record.new(zone, '',
|
||||
{'ttl': 60, 'type': 'ALIAS',
|
||||
'value': 'target.unit.tests.'})),
|
||||
|
||||
):
|
||||
# Validate path and payload based on record meet expectations
|
||||
path, payload = provider._gen_data(expected_record)
|
||||
|
||||
@@ -35,10 +35,10 @@ class TestYamlProvider(TestCase):
|
||||
|
||||
# without it we see everything
|
||||
source.populate(zone)
|
||||
self.assertEquals(18, len(zone.records))
|
||||
self.assertEquals(23, len(zone.records))
|
||||
|
||||
source.populate(dynamic_zone)
|
||||
self.assertEquals(5, len(dynamic_zone.records))
|
||||
self.assertEquals(6, len(dynamic_zone.records))
|
||||
|
||||
# Assumption here is that a clean round-trip means that everything
|
||||
# worked as expected, data that went in came back out and could be
|
||||
@@ -58,21 +58,21 @@ class TestYamlProvider(TestCase):
|
||||
|
||||
# We add everything
|
||||
plan = target.plan(zone)
|
||||
self.assertEquals(15, len([c for c in plan.changes
|
||||
self.assertEquals(20, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
self.assertFalse(isfile(yaml_file))
|
||||
|
||||
# Now actually do it
|
||||
self.assertEquals(15, target.apply(plan))
|
||||
self.assertEquals(20, target.apply(plan))
|
||||
self.assertTrue(isfile(yaml_file))
|
||||
|
||||
# Dynamic plan
|
||||
plan = target.plan(dynamic_zone)
|
||||
self.assertEquals(5, len([c for c in plan.changes
|
||||
self.assertEquals(6, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
self.assertFalse(isfile(dynamic_yaml_file))
|
||||
# Apply it
|
||||
self.assertEquals(5, target.apply(plan))
|
||||
self.assertEquals(6, target.apply(plan))
|
||||
self.assertTrue(isfile(dynamic_yaml_file))
|
||||
|
||||
# There should be no changes after the round trip
|
||||
@@ -87,7 +87,7 @@ class TestYamlProvider(TestCase):
|
||||
|
||||
# A 2nd sync should still create everything
|
||||
plan = target.plan(zone)
|
||||
self.assertEquals(15, len([c for c in plan.changes
|
||||
self.assertEquals(20, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
|
||||
with open(yaml_file) as fh:
|
||||
@@ -106,9 +106,14 @@ class TestYamlProvider(TestCase):
|
||||
self.assertTrue('values' in data.pop('naptr'))
|
||||
self.assertTrue('values' in data.pop('sub'))
|
||||
self.assertTrue('values' in data.pop('txt'))
|
||||
self.assertTrue('values' in data.pop('loc'))
|
||||
self.assertTrue('values' in data.pop('urlfwd'))
|
||||
# these are stored as singular 'value'
|
||||
self.assertTrue('value' in data.pop('_imap._tcp'))
|
||||
self.assertTrue('value' in data.pop('_pop3._tcp'))
|
||||
self.assertTrue('value' in data.pop('aaaa'))
|
||||
self.assertTrue('value' in data.pop('cname'))
|
||||
self.assertTrue('value' in data.pop('dname'))
|
||||
self.assertTrue('value' in data.pop('included'))
|
||||
self.assertTrue('value' in data.pop('ptr'))
|
||||
self.assertTrue('value' in data.pop('spf'))
|
||||
@@ -144,6 +149,10 @@ class TestYamlProvider(TestCase):
|
||||
self.assertTrue('value' in dyna)
|
||||
# self.assertTrue('dynamic' in dyna)
|
||||
|
||||
dyna = data.pop('pool-only-in-fallback')
|
||||
self.assertTrue('value' in dyna)
|
||||
# self.assertTrue('dynamic' in dyna)
|
||||
|
||||
# make sure nothing is left
|
||||
self.assertEquals([], list(data.keys()))
|
||||
|
||||
@@ -206,18 +215,20 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
def test_zone_directory(self):
|
||||
source = SplitYamlProvider(
|
||||
'test', join(dirname(__file__), 'config/split'))
|
||||
'test', join(dirname(__file__), 'config/split'),
|
||||
extension='.tst')
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
|
||||
self.assertEqual(
|
||||
join(dirname(__file__), 'config/split/unit.tests.'),
|
||||
join(dirname(__file__), 'config/split', 'unit.tests.tst'),
|
||||
source._zone_directory(zone))
|
||||
|
||||
def test_apply_handles_existing_zone_directory(self):
|
||||
with TemporaryDirectory() as td:
|
||||
provider = SplitYamlProvider('test', join(td.dirname, 'config'))
|
||||
makedirs(join(td.dirname, 'config', 'does.exist.'))
|
||||
provider = SplitYamlProvider('test', join(td.dirname, 'config'),
|
||||
extension='.tst')
|
||||
makedirs(join(td.dirname, 'config', 'does.exist.tst'))
|
||||
|
||||
zone = Zone('does.exist.', [])
|
||||
self.assertTrue(isdir(provider._zone_directory(zone)))
|
||||
@@ -226,7 +237,8 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
def test_provider(self):
|
||||
source = SplitYamlProvider(
|
||||
'test', join(dirname(__file__), 'config/split'))
|
||||
'test', join(dirname(__file__), 'config/split'),
|
||||
extension='.tst')
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
dynamic_zone = Zone('dynamic.tests.', [])
|
||||
@@ -237,7 +249,7 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
# without it we see everything
|
||||
source.populate(zone)
|
||||
self.assertEquals(18, len(zone.records))
|
||||
self.assertEquals(20, len(zone.records))
|
||||
|
||||
source.populate(dynamic_zone)
|
||||
self.assertEquals(5, len(dynamic_zone.records))
|
||||
@@ -245,18 +257,19 @@ class TestSplitYamlProvider(TestCase):
|
||||
with TemporaryDirectory() as td:
|
||||
# Add some subdirs to make sure that it can create them
|
||||
directory = join(td.dirname, 'sub', 'dir')
|
||||
zone_dir = join(directory, 'unit.tests.')
|
||||
dynamic_zone_dir = join(directory, 'dynamic.tests.')
|
||||
target = SplitYamlProvider('test', directory)
|
||||
zone_dir = join(directory, 'unit.tests.tst')
|
||||
dynamic_zone_dir = join(directory, 'dynamic.tests.tst')
|
||||
target = SplitYamlProvider('test', directory,
|
||||
extension='.tst')
|
||||
|
||||
# We add everything
|
||||
plan = target.plan(zone)
|
||||
self.assertEquals(15, len([c for c in plan.changes
|
||||
self.assertEquals(17, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
self.assertFalse(isdir(zone_dir))
|
||||
|
||||
# Now actually do it
|
||||
self.assertEquals(15, target.apply(plan))
|
||||
self.assertEquals(17, target.apply(plan))
|
||||
|
||||
# Dynamic plan
|
||||
plan = target.plan(dynamic_zone)
|
||||
@@ -279,7 +292,7 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
# A 2nd sync should still create everything
|
||||
plan = target.plan(zone)
|
||||
self.assertEquals(15, len([c for c in plan.changes
|
||||
self.assertEquals(17, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
|
||||
yaml_file = join(zone_dir, '$unit.tests.yaml')
|
||||
@@ -294,7 +307,8 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
# These records are stored as plural "values." Check each file to
|
||||
# ensure correctness.
|
||||
for record_name in ('_srv._tcp', 'mx', 'naptr', 'sub', 'txt'):
|
||||
for record_name in ('_srv._tcp', 'mx', 'naptr', 'sub', 'txt',
|
||||
'urlfwd'):
|
||||
yaml_file = join(zone_dir, '{}.yaml'.format(record_name))
|
||||
self.assertTrue(isfile(yaml_file))
|
||||
with open(yaml_file) as fh:
|
||||
@@ -302,8 +316,8 @@ class TestSplitYamlProvider(TestCase):
|
||||
self.assertTrue('values' in data.pop(record_name))
|
||||
|
||||
# These are stored as singular "value." Again, check each file.
|
||||
for record_name in ('aaaa', 'cname', 'included', 'ptr', 'spf',
|
||||
'www.sub', 'www'):
|
||||
for record_name in ('aaaa', 'cname', 'dname', 'included', 'ptr',
|
||||
'spf', 'www.sub', 'www'):
|
||||
yaml_file = join(zone_dir, '{}.yaml'.format(record_name))
|
||||
self.assertTrue(isfile(yaml_file))
|
||||
with open(yaml_file) as fh:
|
||||
@@ -334,7 +348,8 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
def test_empty(self):
|
||||
source = SplitYamlProvider(
|
||||
'test', join(dirname(__file__), 'config/split'))
|
||||
'test', join(dirname(__file__), 'config/split'),
|
||||
extension='.tst')
|
||||
|
||||
zone = Zone('empty.', [])
|
||||
|
||||
@@ -344,7 +359,8 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
def test_unsorted(self):
|
||||
source = SplitYamlProvider(
|
||||
'test', join(dirname(__file__), 'config/split'))
|
||||
'test', join(dirname(__file__), 'config/split'),
|
||||
extension='.tst')
|
||||
|
||||
zone = Zone('unordered.', [])
|
||||
|
||||
@@ -355,14 +371,15 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
source = SplitYamlProvider(
|
||||
'test', join(dirname(__file__), 'config/split'),
|
||||
enforce_order=False)
|
||||
extension='.tst', enforce_order=False)
|
||||
# no exception
|
||||
source.populate(zone)
|
||||
self.assertEqual(2, len(zone.records))
|
||||
|
||||
def test_subzone_handling(self):
|
||||
source = SplitYamlProvider(
|
||||
'test', join(dirname(__file__), 'config/split'))
|
||||
'test', join(dirname(__file__), 'config/split'),
|
||||
extension='.tst')
|
||||
|
||||
# If we add `sub` as a sub-zone we'll reject `www.sub`
|
||||
zone = Zone('unit.tests.', ['sub'])
|
||||
@@ -386,8 +403,8 @@ class TestOverridingYamlProvider(TestCase):
|
||||
# Load the base, should see the 5 records
|
||||
base.populate(zone)
|
||||
got = {r.name: r for r in zone.records}
|
||||
self.assertEquals(5, len(got))
|
||||
# We get the "dynamic" A from the bae config
|
||||
self.assertEquals(6, len(got))
|
||||
# We get the "dynamic" A from the base config
|
||||
self.assertTrue('dynamic' in got['a'].data)
|
||||
# No added
|
||||
self.assertFalse('added' in got)
|
||||
@@ -395,7 +412,7 @@ class TestOverridingYamlProvider(TestCase):
|
||||
# Load the overrides, should replace one and add 1
|
||||
override.populate(zone)
|
||||
got = {r.name: r for r in zone.records}
|
||||
self.assertEquals(6, len(got))
|
||||
self.assertEquals(7, len(got))
|
||||
# 'a' was replaced with a generic record
|
||||
self.assertEquals({
|
||||
'ttl': 3600,
|
||||
|
||||
+999
-17
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,8 @@ import dns.zone
|
||||
from dns.exception import DNSException
|
||||
|
||||
from mock import patch
|
||||
from os.path import exists
|
||||
from shutil import copyfile
|
||||
from six import text_type
|
||||
from unittest import TestCase
|
||||
|
||||
@@ -21,7 +23,7 @@ from octodns.record import ValidationError
|
||||
class TestAxfrSource(TestCase):
|
||||
source = AxfrSource('test', 'localhost')
|
||||
|
||||
forward_zonefile = dns.zone.from_file('./tests/zones/unit.tests.',
|
||||
forward_zonefile = dns.zone.from_file('./tests/zones/unit.tests.tst',
|
||||
'unit.tests', relativize=False)
|
||||
|
||||
@patch('dns.zone.from_xfr')
|
||||
@@ -34,7 +36,7 @@ class TestAxfrSource(TestCase):
|
||||
]
|
||||
|
||||
self.source.populate(got)
|
||||
self.assertEquals(11, len(got.records))
|
||||
self.assertEquals(15, len(got.records))
|
||||
|
||||
with self.assertRaises(AxfrSourceZoneTransferFailed) as ctx:
|
||||
zone = Zone('unit.tests.', [])
|
||||
@@ -44,18 +46,45 @@ class TestAxfrSource(TestCase):
|
||||
|
||||
|
||||
class TestZoneFileSource(TestCase):
|
||||
source = ZoneFileSource('test', './tests/zones')
|
||||
source = ZoneFileSource('test', './tests/zones', file_extension='.tst')
|
||||
|
||||
def test_zonefiles_with_extension(self):
|
||||
source = ZoneFileSource('test', './tests/zones', '.extension')
|
||||
# Load zonefiles with a specified file extension
|
||||
valid = Zone('ext.unit.tests.', [])
|
||||
source.populate(valid)
|
||||
self.assertEquals(1, len(valid.records))
|
||||
|
||||
def test_zonefiles_without_extension(self):
|
||||
# Windows doesn't let files end with a `.` so we add a .tst to them in
|
||||
# the repo and then try and create the `.` version we need for the
|
||||
# default case (no extension.)
|
||||
copyfile('./tests/zones/unit.tests.tst', './tests/zones/unit.tests.')
|
||||
# Unfortunately copyfile silently works and create the file without
|
||||
# the `.` so we have to check to see if it did that
|
||||
if exists('./tests/zones/unit.tests'):
|
||||
# It did so we need to skip this test, that means windows won't
|
||||
# have full code coverage, but skipping the test is going out of
|
||||
# our way enough for a os-specific/oddball case.
|
||||
self.skipTest('Unable to create unit.tests. (ending with .) so '
|
||||
'skipping default filename testing.')
|
||||
|
||||
source = ZoneFileSource('test', './tests/zones')
|
||||
# Load zonefiles without a specified file extension
|
||||
valid = Zone('unit.tests.', [])
|
||||
source.populate(valid)
|
||||
self.assertEquals(15, len(valid.records))
|
||||
|
||||
def test_populate(self):
|
||||
# Valid zone file in directory
|
||||
valid = Zone('unit.tests.', [])
|
||||
self.source.populate(valid)
|
||||
self.assertEquals(11, len(valid.records))
|
||||
self.assertEquals(15, len(valid.records))
|
||||
|
||||
# 2nd populate does not read file again
|
||||
again = Zone('unit.tests.', [])
|
||||
self.source.populate(again)
|
||||
self.assertEquals(11, len(again.records))
|
||||
self.assertEquals(15, len(again.records))
|
||||
|
||||
# bust the cache
|
||||
del self.source._zone_records[valid.name]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from mock import patch
|
||||
from six import text_type
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
from octodns.source.envvar import EnvVarSource
|
||||
from octodns.source.envvar import EnvironmentVariableNotFoundException
|
||||
|
||||
@@ -355,3 +355,59 @@ class TestZone(TestCase):
|
||||
|
||||
self.assertTrue(zone_missing.changes(zone_normal, provider))
|
||||
self.assertFalse(zone_missing.changes(zone_included, provider))
|
||||
|
||||
def assertEqualsNameAndValues(self, a, b):
|
||||
a = dict([(r.name, r.values[0]) for r in a])
|
||||
b = dict([(r.name, r.values[0]) for r in b])
|
||||
self.assertEquals(a, b)
|
||||
|
||||
def test_copy(self):
|
||||
zone = Zone('unit.tests.', [])
|
||||
|
||||
a = ARecord(zone, 'a', {'ttl': 42, 'value': '1.1.1.1'})
|
||||
zone.add_record(a)
|
||||
b = ARecord(zone, 'b', {'ttl': 42, 'value': '1.1.1.2'})
|
||||
zone.add_record(b)
|
||||
|
||||
# Sanity check
|
||||
self.assertEqualsNameAndValues(set((a, b)), zone.records)
|
||||
|
||||
copy = zone.copy()
|
||||
# We have an origin set and it is the source/original zone
|
||||
self.assertEquals(zone, copy._origin)
|
||||
# Our records are zone's records to start (references)
|
||||
self.assertEqualsNameAndValues(zone.records, copy.records)
|
||||
|
||||
# If we try and change something that's already there we realize and
|
||||
# then get an error about a duplicate
|
||||
b_prime = ARecord(zone, 'b', {'ttl': 42, 'value': '1.1.1.3'})
|
||||
with self.assertRaises(DuplicateRecordException):
|
||||
copy.add_record(b_prime)
|
||||
self.assertIsNone(copy._origin)
|
||||
# Unchanged, straight copies
|
||||
self.assertEqualsNameAndValues(zone.records, copy.records)
|
||||
|
||||
# If we add with replace things will be realized and the record will
|
||||
# have changed
|
||||
copy = zone.copy()
|
||||
copy.add_record(b_prime, replace=True)
|
||||
self.assertIsNone(copy._origin)
|
||||
self.assertEqualsNameAndValues(set((a, b_prime)), copy.records)
|
||||
|
||||
# If we add another record, things are reliazed and it has been added
|
||||
copy = zone.copy()
|
||||
c = ARecord(zone, 'c', {'ttl': 42, 'value': '1.1.1.3'})
|
||||
copy.add_record(c)
|
||||
self.assertEqualsNameAndValues(set((a, b, c)), copy.records)
|
||||
|
||||
# If we remove a record, things are reliazed and it has been removed
|
||||
copy = zone.copy()
|
||||
copy.remove_record(a)
|
||||
self.assertEqualsNameAndValues(set((b,)), copy.records)
|
||||
|
||||
# Re-realizing is a noop
|
||||
copy = zone.copy()
|
||||
# Happens the first time
|
||||
self.assertTrue(copy.hydrate())
|
||||
# Doesn't the second
|
||||
self.assertFalse(copy.hydrate())
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
$ORIGIN ext.unit.tests.
|
||||
@ 3600 IN SOA ns1.ext.unit.tests. root.ext.unit.tests. (
|
||||
2018071501 ; Serial
|
||||
3600 ; Refresh (1 hour)
|
||||
600 ; Retry (10 minutes)
|
||||
604800 ; Expire (1 week)
|
||||
3600 ; NXDOMAIN ttl (1 hour)
|
||||
)
|
||||
|
||||
; NS Records
|
||||
@ 3600 IN NS ns1.ext.unit.tests.
|
||||
@ 3600 IN NS ns2.ext.unit.tests.
|
||||
@@ -13,9 +13,16 @@ $ORIGIN unit.tests.
|
||||
under 3600 IN NS ns1.unit.tests.
|
||||
under 3600 IN NS ns2.unit.tests.
|
||||
|
||||
; CAA Records
|
||||
caa 1800 IN CAA 0 issue "ca.unit.tests"
|
||||
caa 1800 IN CAA 0 iodef "mailto:[email protected]"
|
||||
|
||||
; SRV Records
|
||||
_srv._tcp 600 IN SRV 10 20 30 foo-1.unit.tests.
|
||||
_srv._tcp 600 IN SRV 10 20 30 foo-2.unit.tests.
|
||||
; NULL SRV Records
|
||||
_pop3._tcp 600 IN SRV 0 0 0 .
|
||||
_imap._tcp 600 IN SRV 0 0 0 .
|
||||
|
||||
; TXT Records
|
||||
txt 600 IN TXT "Bah bah black sheep"
|
||||
@@ -28,6 +35,10 @@ mx 300 IN MX 20 smtp-2.unit.tests.
|
||||
mx 300 IN MX 30 smtp-3.unit.tests.
|
||||
mx 300 IN MX 40 smtp-1.unit.tests.
|
||||
|
||||
; LOC Records
|
||||
loc 300 IN LOC 31 58 52.1 S 115 49 11.7 E 20m 10m 10m 2m
|
||||
loc 300 IN LOC 53 14 10 N 2 18 26 W 20m 10m 1000m 2m
|
||||
|
||||
; A Records
|
||||
@ 300 IN A 1.2.3.4
|
||||
@ 300 IN A 1.2.3.5
|
||||
Reference in New Issue
Block a user