Browse Source

add support for NS, MX, TXT, SRV, CNAME, PTR

pull/681/head
Yaroshevich, Denis 4 years ago
parent
commit
c6486ce3d1
5 changed files with 687 additions and 103 deletions
  1. +1
    -1
      README.md
  2. +112
    -5
      octodns/provider/gcore.py
  3. +244
    -55
      tests/fixtures/gcore-no-changes.json
  4. +203
    -24
      tests/fixtures/gcore-records.json
  5. +127
    -18
      tests/test_octodns_provider_gcore.py

+ 1
- 1
README.md View File

@ -196,7 +196,7 @@ The above command pulled the existing data out of Route53 and placed the results
| [EtcHostsProvider](/octodns/provider/etc_hosts.py) | | A, AAAA, ALIAS, CNAME | No | | | [EtcHostsProvider](/octodns/provider/etc_hosts.py) | | A, AAAA, ALIAS, CNAME | No | |
| [EnvVarSource](/octodns/source/envvar.py) | | TXT | No | read-only environment variable injection | | [EnvVarSource](/octodns/source/envvar.py) | | TXT | No | read-only environment variable injection |
| [GandiProvider](/octodns/provider/gandi.py) | | A, AAAA, ALIAS, CAA, CNAME, DNAME, MX, NS, PTR, SPF, SRV, SSHFP, TXT | No | | | [GandiProvider](/octodns/provider/gandi.py) | | A, AAAA, ALIAS, CAA, CNAME, DNAME, MX, NS, PTR, SPF, SRV, SSHFP, TXT | No | |
| [GCoreProvider](/octodns/provider/gcore.py) | | A, AAAA | No | |
| [GCoreProvider](/octodns/provider/gcore.py) | | A, AAAA, NS, MX, TXT, SRV, CNAME, PTR | No | |
| [GoogleCloudProvider](/octodns/provider/googlecloud.py) | google-cloud-dns | A, AAAA, CAA, CNAME, MX, NAPTR, NS, PTR, SPF, SRV, TXT | No | | | [GoogleCloudProvider](/octodns/provider/googlecloud.py) | google-cloud-dns | A, AAAA, CAA, CNAME, MX, NAPTR, NS, PTR, SPF, SRV, TXT | No | |
| [MythicBeastsProvider](/octodns/provider/mythicbeasts.py) | Mythic Beasts | A, AAAA, ALIAS, CNAME, MX, NS, SRV, SSHFP, CAA, TXT | No | | | [MythicBeastsProvider](/octodns/provider/mythicbeasts.py) | Mythic Beasts | A, AAAA, ALIAS, CNAME, MX, NS, SRV, SSHFP, CAA, TXT | No | |
| [Ns1Provider](/octodns/provider/ns1.py) | ns1-python | All | Yes | Missing `NA` geo target | | [Ns1Provider](/octodns/provider/ns1.py) | ns1-python | All | Yes | Missing `NA` geo target |


+ 112
- 5
octodns/provider/gcore.py View File

@ -161,7 +161,7 @@ class GCoreProvider(BaseProvider):
SUPPORTS_GEO = False SUPPORTS_GEO = False
SUPPORTS_DYNAMIC = False SUPPORTS_DYNAMIC = False
SUPPORTS = set(("A", "AAAA"))
SUPPORTS = set(("A", "AAAA", "NS", "MX", "TXT", "SRV", "CNAME", "PTR"))
def __init__(self, id, *args, **kwargs): def __init__(self, id, *args, **kwargs):
token = kwargs.pop("token", None) token = kwargs.pop("token", None)
@ -183,7 +183,22 @@ class GCoreProvider(BaseProvider):
password=password, password=password,
) )
def _add_dot_if_need(self, value):
return "{}.".format(value) if not value.endswith(".") else value
def _data_for_single(self, _type, record): def _data_for_single(self, _type, record):
return {
"ttl": record["ttl"],
"type": _type,
"value": self._add_dot_if_need(
record["resource_records"][0]["content"][0]
),
}
_data_for_CNAME = _data_for_single
_data_for_PTR = _data_for_single
def _data_for_multiple(self, _type, record):
return { return {
"ttl": record["ttl"], "ttl": record["ttl"],
"type": _type, "type": _type,
@ -194,8 +209,62 @@ class GCoreProvider(BaseProvider):
], ],
} }
_data_for_A = _data_for_single
_data_for_AAAA = _data_for_single
_data_for_A = _data_for_multiple
_data_for_AAAA = _data_for_multiple
def _data_for_TXT(self, _type, record):
return {
"ttl": record["ttl"],
"type": _type,
"values": [
rr_value.replace(";", "\\;")
for resource_record in record["resource_records"]
for rr_value in resource_record["content"]
],
}
def _data_for_MX(self, _type, record):
return {
"ttl": record["ttl"],
"type": _type,
"values": [
dict(
preference=preference,
exchange=self._add_dot_if_need(exchange),
)
for preference, exchange in map(
lambda x: x["content"], record["resource_records"]
)
],
}
def _data_for_NS(self, _type, record):
return {
"ttl": record["ttl"],
"type": _type,
"values": [
self._add_dot_if_need(rr_value)
for resource_record in record["resource_records"]
for rr_value in resource_record["content"]
],
}
def _data_for_SRV(self, _type, record):
return {
"ttl": record["ttl"],
"type": _type,
"values": [
dict(
priority=priority,
weight=weight,
port=port,
target=self._add_dot_if_need(target),
)
for priority, weight, port, target in map(
lambda x: x["content"], record["resource_records"]
)
],
}
def zone_records(self, zone): def zone_records(self, zone):
try: try:
@ -241,6 +310,15 @@ class GCoreProvider(BaseProvider):
return exists return exists
def _params_for_single(self, record): def _params_for_single(self, record):
return {
"ttl": record.ttl,
"resource_records": [{"content": [record.value]}],
}
_params_for_CNAME = _params_for_single
_params_for_PTR = _params_for_single
def _params_for_multiple(self, record):
return { return {
"ttl": record.ttl, "ttl": record.ttl,
"resource_records": [ "resource_records": [
@ -248,8 +326,37 @@ class GCoreProvider(BaseProvider):
], ],
} }
_params_for_A = _params_for_single
_params_for_AAAA = _params_for_single
_params_for_A = _params_for_multiple
_params_for_AAAA = _params_for_multiple
_params_for_NS = _params_for_multiple
def _params_for_TXT(self, record):
# print(record.values)
return {
"ttl": record.ttl,
"resource_records": [
{"content": [value.replace("\\;", ";")]}
for value in record.values
],
}
def _params_for_MX(self, record):
return {
"ttl": record.ttl,
"resource_records": [
{"content": [rec.preference, rec.exchange]}
for rec in record.values
],
}
def _params_for_SRV(self, record):
return {
"ttl": record.ttl,
"resource_records": [
{"content": [rec.priority, rec.weight, rec.port, rec.target]}
for rec in record.values
],
}
def _apply_create(self, change): def _apply_create(self, change):
self.log.info("creating: %s", change) self.log.info("creating: %s", change)


+ 244
- 55
tests/fixtures/gcore-no-changes.json View File

@ -1,56 +1,245 @@
{ {
"rrsets": [{
"name": "unit.tests",
"type": "A",
"ttl": 300,
"resource_records": [{
"content": [
"1.2.3.4"
]
}, {
"content": [
"1.2.3.5"
]
}]
}, {
"name": "aaaa.unit.tests",
"type": "AAAA",
"ttl": 600,
"resource_records": [{
"content": [
"2601:644:500:e210:62f8:1dff:feb8:947a"
]
}]
}, {
"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": "unit.tests",
"type": "ns",
"ttl": 300,
"resource_records": [{
"content": [
"ns2.gcdn.services"
]
}, {
"content": [
"ns1.gcorelabs.net"
]
}]
}]
}
"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"
]
}
]
}
]
}

+ 203
- 24
tests/fixtures/gcore-records.json View File

@ -1,25 +1,204 @@
{ {
"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"
]
}]
}]
}
"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"
]
}
]
}
]
}

+ 127
- 18
tests/test_octodns_provider_gcore.py View File

@ -64,13 +64,13 @@ class TestGCoreProvider(TestCase):
with self.assertRaises(GCoreClientException) as ctx: with self.assertRaises(GCoreClientException) as ctx:
zone = Zone("unit.tests.", []) zone = Zone("unit.tests.", [])
provider.populate(zone) provider.populate(zone)
self.assertEquals("Things caught fire", text_type(ctx.exception))
self.assertEqual("Things caught fire", text_type(ctx.exception))
# No credentials or token error # No credentials or token error
with requests_mock() as mock: with requests_mock() as mock:
with self.assertRaises(ValueError) as ctx: with self.assertRaises(ValueError) as ctx:
GCoreProvider("test_id") GCoreProvider("test_id")
self.assertEquals(
self.assertEqual(
"either token or login & password must be set", "either token or login & password must be set",
text_type(ctx.exception), text_type(ctx.exception),
) )
@ -116,14 +116,29 @@ class TestGCoreProvider(TestCase):
zone = Zone("unit.tests.", []) zone = Zone("unit.tests.", [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(4, len(zone.records))
self.assertEquals(
{"aaaa", "www", "www.sub", ""}, {r.name for r in zone.records}
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) changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes))
self.assertEqual(0, len(changes))
# 3 removed + 1 modified
# 1 removed + 7 modified
with requests_mock() as mock: with requests_mock() as mock:
base = "https://dnsapi.gcorelabs.com/v2/zones/unit.tests/rrsets" base = "https://dnsapi.gcorelabs.com/v2/zones/unit.tests/rrsets"
with open("tests/fixtures/gcore-records.json") as fh: with open("tests/fixtures/gcore-records.json") as fh:
@ -131,14 +146,14 @@ class TestGCoreProvider(TestCase):
zone = Zone("unit.tests.", []) zone = Zone("unit.tests.", [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(1, len(zone.records))
self.assertEqual(13, len(zone.records))
changes = self.expected.changes(zone, provider) changes = self.expected.changes(zone, provider)
self.assertEquals(4, len(changes))
self.assertEquals(
3, len([c for c in changes if isinstance(c, Delete)])
self.assertEqual(8, len(changes))
self.assertEqual(
1, len([c for c in changes if isinstance(c, Delete)])
) )
self.assertEquals(
1, len([c for c in changes if isinstance(c, Update)])
self.assertEqual(
7, len([c for c in changes if isinstance(c, Update)])
) )
def test_apply(self): def test_apply(self):
@ -192,8 +207,8 @@ class TestGCoreProvider(TestCase):
plan = provider.plan(self.expected) plan = provider.plan(self.expected)
# create all # create all
self.assertEquals(4, len(plan.changes))
self.assertEquals(4, provider.apply(plan))
self.assertEqual(13, len(plan.changes))
self.assertEqual(13, provider.apply(plan))
self.assertFalse(plan.exists) self.assertFalse(plan.exists)
provider._client._request.assert_has_calls( provider._client._request.assert_has_calls(
@ -221,6 +236,73 @@ class TestGCoreProvider(TestCase):
"resource_records": [{"content": ["2.2.3.6"]}], "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( call(
"POST", "POST",
"http://api/zones/unit.tests/aaaa.unit.tests./AAAA", "http://api/zones/unit.tests/aaaa.unit.tests./AAAA",
@ -235,6 +317,33 @@ class TestGCoreProvider(TestCase):
], ],
}, },
), ),
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( call(
"POST", "POST",
"http://api/zones/unit.tests/unit.tests./A", "http://api/zones/unit.tests/unit.tests./A",
@ -249,7 +358,7 @@ class TestGCoreProvider(TestCase):
] ]
) )
# expected number of total calls # expected number of total calls
self.assertEquals(7, provider._client._request.call_count)
self.assertEqual(16, provider._client._request.call_count)
provider._client._request.reset_mock() provider._client._request.reset_mock()
@ -283,8 +392,8 @@ class TestGCoreProvider(TestCase):
plan = provider.plan(wanted) plan = provider.plan(wanted)
self.assertTrue(plan.exists) self.assertTrue(plan.exists)
self.assertEquals(2, len(plan.changes))
self.assertEquals(2, provider.apply(plan))
self.assertEqual(2, len(plan.changes))
self.assertEqual(2, provider.apply(plan))
provider._client._request.assert_has_calls( provider._client._request.assert_has_calls(
[ [


Loading…
Cancel
Save