Compare commits
14 commits
39c179af70
...
1cbd26d4cb
Author | SHA1 | Date | |
---|---|---|---|
1cbd26d4cb | |||
10e842385c | |||
4304729ba0 | |||
b19a598c38 | |||
dff84f48ab | |||
ff955d8479 | |||
ec27c03260 | |||
2e468c8569 | |||
8d29b5e639 | |||
696d2a2532 | |||
4fe8be6291 | |||
78448d2bde | |||
ff1a47cf8c | |||
91ba03af7f |
7 changed files with 86 additions and 61 deletions
|
@ -1,2 +1,2 @@
|
||||||
just 1.16.0
|
just 1.25.2
|
||||||
lefthook 1.4.6
|
lefthook 1.4.6
|
||||||
|
|
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
## [v0.3.5](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.4...v0.3.5) - 2024-03-04
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- add new tests [`a8ecfab`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/a8ecfab93096cd3201efcb06a65bc86a2444611a)
|
||||||
|
- fix typo in method call and add function for semicolons [`afe1826`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/afe182628a7b2fc784dc48bebaf9978c85d682d2)
|
||||||
|
- update ci files for github [`6b06cad`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6b06cada38751648b9365fd27b850aa2b6d250a8)
|
||||||
|
- unescape the changeset before comparison to live records to have an accurate changeset. fixes changes for txt records [`4fe8be6`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/4fe8be62918b5bea821f4f88a151918899b61e91)
|
||||||
|
- rollback changes to semicolon escaping [`91ba03a`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/91ba03af7f5f53a7f3b5218f0f4a905eebcdb914)
|
||||||
|
- revert: change from repr to string in changeset [`8d29b5e`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/8d29b5e639b25bb57b43e9e85bbe78e51d70b493)
|
||||||
|
- update logging [`ff1a47c`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/ff1a47cf8cda21695fb4e30910dd92d0f9cb8b63)
|
||||||
|
- update test cases [`f38a103`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f38a1036b79b9779a8480c2d1f79132370b83bb4)
|
||||||
|
- change from repr to string in changeset [`696d2a2`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/696d2a2532eef05fee95cddff627c9b09736ae52)
|
||||||
|
- only unescape txt and spf records [`78448d2`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/78448d2bde85fda1718c64e6cd8983b4fcb20fe0)
|
||||||
|
- fix semicolon escaping [`6e624a7`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6e624a79a75051295d1863f9af7e907d44a9b307)
|
||||||
|
- fix tests [`2e468c8`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/2e468c85699808ed9ba2edb35a6cc39591869346)
|
||||||
|
|
||||||
## [v0.3.4](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.3...v0.3.4) - 2024-02-29
|
## [v0.3.4](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.3...v0.3.4) - 2024-02-29
|
||||||
|
|
||||||
### Commits
|
### Commits
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
---
|
---
|
||||||
? ''
|
? ''
|
||||||
: ttl: 172800
|
: - ttl: 172800
|
||||||
type: NS
|
type: NS
|
||||||
values:
|
values:
|
||||||
- ns1.example.com.
|
- ns1.example.com.
|
||||||
- ns3.example.com.
|
- ns2.example.com.
|
||||||
|
- type: TXT
|
||||||
|
value: v=spf1 include:example.com -all
|
||||||
|
_ts3._udp:
|
||||||
|
type: SRV
|
||||||
|
value:
|
||||||
|
port: 9987
|
||||||
|
priority: 0
|
||||||
|
target: example.com.
|
||||||
|
weight: 5
|
||||||
|
abc:
|
||||||
|
type: CNAME
|
||||||
|
value: def.example.com.
|
||||||
ns1:
|
ns1:
|
||||||
type: A
|
type: A
|
||||||
value: 192.168.1.1
|
value: 192.168.1.1
|
||||||
|
|
|
@ -50,7 +50,7 @@ python = "3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pytest==7.4.4",
|
"pytest==7.4.4",
|
||||||
"coverage==7.4.3",
|
"coverage==7.4.3",
|
||||||
"octodns==1.5.0",
|
"octodns==1.6.0",
|
||||||
"octodns-spf==0.0.2",
|
"octodns-spf==0.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.3.4"
|
__version__ = "0.3.5"
|
||||||
|
|
|
@ -61,8 +61,8 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""initialize the NetboxDNSSource"""
|
"""initialize the NetBoxDNSProvider"""
|
||||||
self.log = logging.getLogger(f"NetboxDNSSource[{id}]")
|
self.log = logging.getLogger(f"NetBoxDNSProvider[{id}]")
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"__init__: {id=}, {url=}, {view=}, {replace_duplicates=}, {make_absolute=}, {disable_ptr=}, {args=}, {kwargs=}"
|
f"__init__: {id=}, {url=}, {view=}, {replace_duplicates=}, {make_absolute=}, {disable_ptr=}, {args=}, {kwargs=}"
|
||||||
)
|
)
|
||||||
|
@ -90,21 +90,14 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
|
|
||||||
return absolute_value
|
return absolute_value
|
||||||
|
|
||||||
@staticmethod
|
def _escape_semicolon(self, value: str) -> str:
|
||||||
def _fix_semicolon(value: str, escape: bool) -> str:
|
fixed = value.replace(";", r"\;")
|
||||||
"""escape and un-escape semicolons in record values for netbox/octodns
|
self.log.debug(rf"in='{value}', escaped='{fixed}'")
|
||||||
|
return fixed
|
||||||
@param value: the record value
|
|
||||||
@param escape: if set to true, all semicolons get escaped with a backslash.
|
|
||||||
if false it un-escapes all semicolons.
|
|
||||||
|
|
||||||
@return: the modified record value
|
|
||||||
"""
|
|
||||||
if escape:
|
|
||||||
fixed = value.replace(";", "\\;")
|
|
||||||
else:
|
|
||||||
fixed = value.replace("\\;", ";")
|
|
||||||
|
|
||||||
|
def _unescape_semicolon(self, value: str) -> str:
|
||||||
|
fixed = value.replace(r"\\", "\\").replace(r"\;", ";")
|
||||||
|
self.log.debug(rf"in='{value}', unescaped='{fixed}'")
|
||||||
return fixed
|
return fixed
|
||||||
|
|
||||||
def _get_nb_view(self, view: str | None | Literal[False]) -> dict[str, int | str]:
|
def _get_nb_view(self, view: str | None | Literal[False]) -> dict[str, int | str]:
|
||||||
|
@ -212,7 +205,7 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SPF" | "TXT":
|
case "SPF" | "TXT":
|
||||||
value = self._fix_semicolon(rcd_value, escape=True)
|
value = self._escape_semicolon(rcd_value)
|
||||||
|
|
||||||
case "SRV":
|
case "SRV":
|
||||||
value = {
|
value = {
|
||||||
|
@ -230,7 +223,7 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
self.log.error("invalid record type")
|
self.log.error("invalid record type")
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
self.log.debug(f"formatted record value={value}")
|
self.log.debug(rf"formatted record value={value}")
|
||||||
|
|
||||||
return value # type:ignore
|
return value # type:ignore
|
||||||
|
|
||||||
|
@ -265,7 +258,7 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
"ttl": rcd_ttl,
|
"ttl": rcd_ttl,
|
||||||
"values": [],
|
"values": [],
|
||||||
}
|
}
|
||||||
self.log.debug(f"working on record={rcd_data} -> {rcd_value}")
|
self.log.debug(rf"working on record={rcd_data}, value={rcd_value}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rcd_rdata = self._format_rdata(rcd_type, rcd_value)
|
rcd_rdata = self._format_rdata(rcd_type, rcd_value)
|
||||||
|
@ -277,7 +270,7 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
|
|
||||||
records[(rcd_name, rcd_type)]["values"].append(rcd_rdata)
|
records[(rcd_name, rcd_type)]["values"].append(rcd_rdata)
|
||||||
|
|
||||||
self.log.debug(f"record data={records[(rcd_name, rcd_type)]}")
|
self.log.debug(rf"record data={records[(rcd_name, rcd_type)]}")
|
||||||
|
|
||||||
return list(records.values())
|
return list(records.values())
|
||||||
|
|
||||||
|
@ -292,7 +285,7 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
|
|
||||||
@return: true if the zone exists, else false.
|
@return: true if the zone exists, else false.
|
||||||
"""
|
"""
|
||||||
self.log.info(f"populate -> '{zone.name}', target={target}, lenient={lenient}")
|
self.log.info(f"--> populate '{zone.name}', target={target}, lenient={lenient}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
records = self._format_nb_records(zone)
|
records = self._format_nb_records(zone)
|
||||||
|
@ -315,13 +308,12 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
def _format_changeset(self, change: Any) -> set[str]:
|
||||||
def _format_changeset(change: Any) -> set[str]:
|
|
||||||
"""format the changeset
|
"""format the changeset
|
||||||
|
|
||||||
@param change: the raw changes
|
@param change: the raw changes
|
||||||
|
|
||||||
@return: the formatted changeset
|
@return: the formatted/escaped changeset
|
||||||
"""
|
"""
|
||||||
match change:
|
match change:
|
||||||
case octodns.record.ValueMixin():
|
case octodns.record.ValueMixin():
|
||||||
|
@ -331,8 +323,14 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
case _:
|
case _:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
if change._type not in ["TXT", "SPF"]:
|
||||||
|
self.log.debug(f"{changeset=}")
|
||||||
return changeset
|
return changeset
|
||||||
|
|
||||||
|
unescaped_changeset = {self._unescape_semicolon(n) for n in changeset}
|
||||||
|
self.log.debug(f"{unescaped_changeset=}")
|
||||||
|
return unescaped_changeset
|
||||||
|
|
||||||
def _include_change(self, change: octodns.record.change.Change) -> bool:
|
def _include_change(self, change: octodns.record.change.Change) -> bool:
|
||||||
"""filter out record types which the provider can't create in netbox
|
"""filter out record types which the provider can't create in netbox
|
||||||
|
|
||||||
|
@ -341,7 +339,7 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
@return: false if the change should be discarded, true if it should be kept.
|
@return: false if the change should be discarded, true if it should be kept.
|
||||||
"""
|
"""
|
||||||
if change.record._type in ["SOA", "PTR", "NS"]:
|
if change.record._type in ["SOA", "PTR", "NS"]:
|
||||||
self.log.debug(f"record not supported as provider, ignoring: {change.record}")
|
self.log.debug(rf"record not supported as provider, ignoring: {change.record}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -353,7 +351,7 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
|
|
||||||
@return: none
|
@return: none
|
||||||
"""
|
"""
|
||||||
self.log.debug(f"_apply: zone={plan.desired.name}, changes={len(plan.changes)}")
|
self.log.debug(f"--> _apply zone={plan.desired.name}, changes={len(plan.changes)}")
|
||||||
|
|
||||||
nb_zone = self._get_nb_zone(plan.desired.name, view=self.nb_view)
|
nb_zone = self._get_nb_zone(plan.desired.name, view=self.nb_view)
|
||||||
|
|
||||||
|
@ -364,17 +362,15 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
|
|
||||||
new_changeset = self._format_changeset(change.new)
|
new_changeset = self._format_changeset(change.new)
|
||||||
for record in new_changeset:
|
for record in new_changeset:
|
||||||
nb_record: pynetbox.core.response.Record = (
|
self.log.debug(rf"ADD {change.new._type} {rcd_name} {record}")
|
||||||
self.api.plugins.netbox_dns.records.create(
|
self.api.plugins.netbox_dns.records.create(
|
||||||
zone=nb_zone.id,
|
zone=nb_zone.id,
|
||||||
name=rcd_name,
|
name=rcd_name,
|
||||||
type=change.new._type,
|
type=change.new._type,
|
||||||
ttl=change.new.ttl,
|
ttl=change.new.ttl,
|
||||||
value=self._fix_semicolon(record, escape=False),
|
value=record,
|
||||||
disable_ptr=self.disable_ptr,
|
disable_ptr=self.disable_ptr,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
self.log.debug(f"ADD {nb_record.type} {nb_record.name} {nb_record.value}")
|
|
||||||
|
|
||||||
case octodns.record.Delete():
|
case octodns.record.Delete():
|
||||||
nb_records: pynetbox.core.response.RecordSet = (
|
nb_records: pynetbox.core.response.RecordSet = (
|
||||||
|
@ -391,7 +387,7 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
if nb_record.value != record:
|
if nb_record.value != record:
|
||||||
continue
|
continue
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"DELETE {nb_record.type} {nb_record.name} {nb_record.value}"
|
rf"DELETE {nb_record.type} {nb_record.name} {nb_record.value}"
|
||||||
)
|
)
|
||||||
nb_record.delete()
|
nb_record.delete()
|
||||||
|
|
||||||
|
@ -414,23 +410,23 @@ class NetBoxDNSProvider(octodns.provider.base.BaseProvider):
|
||||||
for nb_record in nb_records:
|
for nb_record in nb_records:
|
||||||
if nb_record.value in to_delete:
|
if nb_record.value in to_delete:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"DELETE {nb_record.type} {nb_record.name} {nb_record.value}"
|
rf"DELETE {nb_record.type} {nb_record.name} {nb_record.value}"
|
||||||
)
|
)
|
||||||
nb_record.delete()
|
nb_record.delete()
|
||||||
if nb_record.value in to_update:
|
if nb_record.value in to_update:
|
||||||
nb_record.ttl = change.new.ttl
|
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"MODIFY {nb_record.type} {nb_record.name} {nb_record.value}"
|
rf"MODIFY (ttl) {nb_record.type} {nb_record.name} {nb_record.value}"
|
||||||
)
|
)
|
||||||
|
nb_record.ttl = change.new.ttl
|
||||||
nb_record.save()
|
nb_record.save()
|
||||||
|
|
||||||
for record in to_create:
|
for record in to_create:
|
||||||
|
self.log.debug(rf"ADD {change.new._type} {rcd_name} {record}")
|
||||||
nb_record = self.api.plugins.netbox_dns.records.create(
|
nb_record = self.api.plugins.netbox_dns.records.create(
|
||||||
zone=nb_zone.id,
|
zone=nb_zone.id,
|
||||||
name=rcd_name,
|
name=rcd_name,
|
||||||
type=change.new._type,
|
type=change.new._type,
|
||||||
ttl=change.new.ttl,
|
ttl=change.new.ttl,
|
||||||
value=self._fix_semicolon(record, escape=False),
|
value=record,
|
||||||
disable_ptr=self.disable_ptr,
|
disable_ptr=self.disable_ptr,
|
||||||
)
|
)
|
||||||
self.log.debug(f"ADD {nb_record.type} {nb_record.name} {nb_record.value}")
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ DEFAULT_CONFIG = {
|
||||||
def test_escape1():
|
def test_escape1():
|
||||||
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
||||||
rcd_value = r"v=TLSRPTv1; rua=mailto:tlsrpt@example.com"
|
rcd_value = r"v=TLSRPTv1; rua=mailto:tlsrpt@example.com"
|
||||||
value = nbdns._fix_semicolon(rcd_value, escape=True)
|
value = nbdns._escape_semicolon(rcd_value)
|
||||||
|
|
||||||
assert value == r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com"
|
assert value == r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com"
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ def test_escape1():
|
||||||
def test_escape2():
|
def test_escape2():
|
||||||
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
||||||
rcd_value = r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com"
|
rcd_value = r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com"
|
||||||
value = nbdns._fix_semicolon(rcd_value, escape=True)
|
value = nbdns._escape_semicolon(rcd_value)
|
||||||
|
|
||||||
assert value == r"v=TLSRPTv1\\; rua=mailto:tlsrpt@example.com"
|
assert value == r"v=TLSRPTv1\\; rua=mailto:tlsrpt@example.com"
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ def test_escape2():
|
||||||
def test_escape3():
|
def test_escape3():
|
||||||
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
||||||
rcd_value = r"t=y\;o=~\;"
|
rcd_value = r"t=y\;o=~\;"
|
||||||
value = nbdns._fix_semicolon(rcd_value, escape=True)
|
value = nbdns._escape_semicolon(rcd_value)
|
||||||
|
|
||||||
assert value == r"t=y\\;o=~\\;"
|
assert value == r"t=y\\;o=~\\;"
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ def test_escape3():
|
||||||
def test_escape4():
|
def test_escape4():
|
||||||
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
||||||
rcd_value = r"t=y;o=~;"
|
rcd_value = r"t=y;o=~;"
|
||||||
value = nbdns._fix_semicolon(rcd_value, escape=True)
|
value = nbdns._escape_semicolon(rcd_value)
|
||||||
|
|
||||||
assert value == r"t=y\;o=~\;"
|
assert value == r"t=y\;o=~\;"
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ def test_escape4():
|
||||||
def test_unescape1():
|
def test_unescape1():
|
||||||
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
||||||
rcd_value = r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com"
|
rcd_value = r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com"
|
||||||
value = nbdns._fix_semicolon(rcd_value, escape=False)
|
value = nbdns._unescape_semicolon(rcd_value)
|
||||||
|
|
||||||
assert value == r"v=TLSRPTv1; rua=mailto:tlsrpt@example.com"
|
assert value == r"v=TLSRPTv1; rua=mailto:tlsrpt@example.com"
|
||||||
|
|
||||||
|
@ -54,22 +54,22 @@ def test_unescape1():
|
||||||
def test_unescape2():
|
def test_unescape2():
|
||||||
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
||||||
rcd_value = r"v=TLSRPTv1\\; rua=mailto:tlsrpt@example.com"
|
rcd_value = r"v=TLSRPTv1\\; rua=mailto:tlsrpt@example.com"
|
||||||
value = nbdns._fix_semicolon(rcd_value, escape=False)
|
value = nbdns._unescape_semicolon(rcd_value)
|
||||||
|
|
||||||
assert value == r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com"
|
assert value == r"v=TLSRPTv1; rua=mailto:tlsrpt@example.com"
|
||||||
|
|
||||||
|
|
||||||
def test_unescape3():
|
def test_unescape3():
|
||||||
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
||||||
rcd_value = r"t=y\\;o=~\;"
|
rcd_value = r"t=y\\;o=~\;"
|
||||||
value = nbdns._fix_semicolon(rcd_value, escape=False)
|
value = nbdns._unescape_semicolon(rcd_value)
|
||||||
|
|
||||||
assert value == r"t=y\;o=~;"
|
assert value == r"t=y;o=~;"
|
||||||
|
|
||||||
|
|
||||||
def test_unescape4():
|
def test_unescape4():
|
||||||
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG)
|
||||||
rcd_value = r"t=y;o=~;"
|
rcd_value = r"t=y;o=~;"
|
||||||
value = nbdns._fix_semicolon(rcd_value, escape=False)
|
value = nbdns._unescape_semicolon(rcd_value)
|
||||||
|
|
||||||
assert value == r"t=y;o=~;"
|
assert value == r"t=y;o=~;"
|
||||||
|
|
Loading…
Reference in a new issue