Skip to content

Commit

Permalink
Merge pull request #70 from pattern-research/allen/append
Browse files Browse the repository at this point in the history
Add tag append parameter to sources
  • Loading branch information
ross authored Dec 13, 2023
2 parents 1b85a18 + b1a11f6 commit 4ce9042
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 96 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## v0.0.7 - 2023-??-?? -

### Important

* Add `append_to_names` tag append parameter to sources

## v0.0.6 - 2023-10-16 - Long overdue

### Important
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,16 @@ providers:
region: us-east-1
# Prefix for tag keys containing fqdn(s)
#tag_prefix: octodns
# String to append to all names and tag values
#append_to_names: mydomain.com.
#ttl: 3600
```

In general the account used will need read permissions on EC2 instances.

Records are driven off of the tags attached to the EC2 instances. The "Name" tag and any tags starting with `tag_prefix` are considered.

The value of the tag should be one or more fqdns separated by a `/` character.
The value of the tag should be one or more fqdns separated by a `/` character. You can append a string to the name and all tag values with `append_to_names`.

When a zone is being populated any fqdns matching the zone name will result in records. When the instance has a private IPv4 address an A record will be created. When the instance has an IPv6 address a AAAA record will be created.

Expand All @@ -92,14 +94,16 @@ providers:
region: us-east-1
# Prefix for tag keys containing fqdn(s)
#tag_prefix: octodns
# String to append to all names and tag values
#append_to_names: mydomain.com.
#ttl: 3600
```

In general the account used will need read permissions on ELB instances and tags.

Records are driven off of the ELB name and the tags attached to the ELB instances. Any tag with `tag_prefix` is considered.

The value of the tag should be one or more fqdns separated by a `/` character.
The value of the tag should be one or more fqdns separated by a `/` character. You can append a string to the name and all tag values with `append_to_names`.

When a zone is being populated any fqdns matching the zone name will result in records CNAME records with the target value being the DNSName of the ELB instance.

Expand Down
24 changes: 18 additions & 6 deletions octodns_route53/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,23 @@ def __init__(
client_max_attempts=None,
ttl=3600,
tag_prefix='octodns',
append_to_names="",
*args,
**kwargs,
):
self.log = getLogger(f'Ec2Source[{id}]')
self.log.info(
'__init__: id=%s, region=%s, access_key_id=%s, ttl=%d, tag_prefix=%s',
'__init__: id=%s, region=%s, access_key_id=%s, ttl=%d, tag_prefix=%s, append_to_names=%s',
id,
region,
access_key_id,
ttl,
tag_prefix,
append_to_names,
)
self.ttl = ttl
self.tag_prefix = tag_prefix
self.append_to_names = append_to_names

super().__init__(id, *args, **kwargs)

Expand Down Expand Up @@ -69,9 +72,12 @@ def instances(self):
key = tag['Key']
val = tag['Value']
if key == 'Name':
fqdns.append(val)
fqdns.append(val + self.append_to_names)
elif key.startswith(self.tag_prefix):
fqdns.extend(val.split('/'))
fqdns.extend(
fqdn + self.append_to_names
for fqdn in val.split('/')
)

fqdns = [f'{i}.' if i[-1] != '.' else i for i in fqdns]
instances[instance['InstanceId']] = {
Expand Down Expand Up @@ -199,20 +205,23 @@ def __init__(
client_max_attempts=None,
ttl=3600,
tag_prefix='octodns',
append_to_names="",
*args,
**kwargs,
):
self.log = getLogger(f'ElbSource[{id}]')
self.log.info(
'__init__: id=%s, region=%s, access_key_id=%s, ttl=%d, tag_prefix=%s',
'__init__: id=%s, region=%s, access_key_id=%s, ttl=%d, tag_prefix=%s, append_to_names=%s',
id,
region,
access_key_id,
ttl,
tag_prefix,
append_to_names,
)
self.ttl = ttl
self.tag_prefix = tag_prefix
self.append_to_names = append_to_names

super().__init__(id, *args, **kwargs)

Expand All @@ -238,7 +247,7 @@ def lbs(self):
arn = lb['LoadBalancerArn']
lbs[arn] = {
'dns_name': f'{lb["DNSName"]}.',
'fqdns': [lb['LoadBalancerName']],
'fqdns': [lb['LoadBalancerName'] + self.append_to_names],
}

# request tags and look through them for fqdns
Expand All @@ -252,7 +261,10 @@ def lbs(self):
key = tag['Key']
val = tag['Value']
if key.startswith(self.tag_prefix):
lb['fqdns'].extend(val.split('/'))
lb['fqdns'].extend(
fqdn + self.append_to_names
for fqdn in val.split('/')
)

for lb in lbs.values():
fqdns = lb['fqdns']
Expand Down
41 changes: 41 additions & 0 deletions tests/test_octodns_source_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,47 @@ def test_instances(self):
)
self.assertEqual(2, len(records))

def test_append(self):
source, stubber = self._get_stubbed_source(append_to_names="org.")

zone = Zone('unit.tests.org.', [])
in_addr_arpa = Zone('0.0.10.in-addr.arpa.', [])
ip6_arpa = Zone('0.0.c.f.ip6.arpa.', [])

stubber.add_response(
'describe_instances', {'Reservations': self.reservations}
)
source.populate(zone)

# Our append string assumes names end with . - no entries for iv4, 2nd,
# or 2nd-v6. So expect 3 A and 1 AAAA
records = {(r.name, r._type): r for r in zone.records}
self.assertEqual(['10.0.0.14'], records[('all', 'A')].values)
self.assertEqual(['10.0.1.99'], records[('iv4-other', 'A')].values)
self.assertEqual(['10.0.0.16'], records[('v4', 'A')].values)
self.assertEqual(['fc00::1'], records[('all', 'AAAA')].values)
self.assertEqual(['fc00::2'], records[('v6', 'AAAA')].values)
self.assertEqual(5, len(records))

# expect 3 ipv4 PTRs
source.populate(in_addr_arpa)
records = {r.name: r for r in in_addr_arpa.records}
self.assertEqual('all.unit.tests.org.', records['14'].value)
self.assertEqual('iv4.unit.testsorg.', records['15'].value)
self.assertEqual(
['2nd.unit.testsorg.', 'v4.unit.tests.org.'], records['16'].values
)
self.assertEqual(3, len(records))

# expect 3 ipv6 PTRs
source.populate(ip6_arpa)
records = {r.name.split('.', 1)[0]: r for r in ip6_arpa.records}
self.assertEqual('all.unit.tests.org.', records['1'].value)
self.assertEqual(
['2nd-v6.unit.testsorg.', 'v6.unit.tests.org.'], records['2'].values
)
self.assertEqual(2, len(records))

def test_conflicting_fqdns(self):
source, stubber = self._get_stubbed_source()

Expand Down
197 changes: 109 additions & 88 deletions tests/test_octodns_source_elb.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,87 @@


class TestElbSource(TestCase):
load_balancers = {
'LoadBalancers': [
{
# matches name
'DNSName': 'foo.aws.com',
'LoadBalancerArn': 'arn42',
'LoadBalancerName': 'service.unit.tests.',
},
{
# doesn't match
'DNSName': 'bar.aws.com',
'LoadBalancerArn': 'arn43',
'LoadBalancerName': 'this.doesnt.match.',
},
{
# matches, no trailing dot
'DNSName': 'baz.aws.com',
'LoadBalancerArn': 'arn44',
'LoadBalancerName': 'no-dot.unit.tests',
},
{
# name doesn't match, but tags will
'DNSName': 'blip.aws.com',
'LoadBalancerArn': 'arn45',
'LoadBalancerName': 'tags.will.match.',
},
{
# both name and tags match
'DNSName': 'bang.aws.com',
'LoadBalancerArn': 'arn46',
'LoadBalancerName': 'both.unit.tests.',
},
]
}
tags = {
'TagDescriptions': [
{
'ResourceArn': 'arn42',
'Tags': [{'Key': 'irrelevant', 'Value': 'doesnt matter'}],
},
{'ResourceArn': 'arn43'},
{'ResourceArn': 'arn44'},
{
'ResourceArn': 'arn45',
'Tags': [
{
'Key': 'octodns',
# multi-value: one matches w/dot. one matches w/o dot, one
# doesn't match
'Value': 'first.unit.tests./second.unit.tests/third.thing.',
}
],
},
{
'ResourceArn': 'arn46',
'Tags': [
{
'Key': 'octodns-1',
# matches
'Value': 'fourth.unit.tests.',
},
{
'Key': 'octodns-2',
# matches w/o dot
'Value': 'fifth.unit.tests',
},
{
'Key': 'octodns-2',
# doesn't match
'Value': 'sixth.doesnt.apply.',
},
{
'Key': 'octodns-3',
# apex match
'Value': 'unit.tests.',
},
],
},
]
}

def _get_stubbed_source(self, **kwargs):
source = ElbSource('test', 'us-east-1', 'abc', '123', **kwargs)

Expand Down Expand Up @@ -41,95 +122,9 @@ def test_lbs(self):

zone = Zone('unit.tests.', [])

stubber.add_response(
'describe_load_balancers',
{
'LoadBalancers': [
{
# matches name
'DNSName': 'foo.aws.com',
'LoadBalancerArn': 'arn42',
'LoadBalancerName': 'service.unit.tests.',
},
{
# doesn't match
'DNSName': 'bar.aws.com',
'LoadBalancerArn': 'arn43',
'LoadBalancerName': 'this.doesnt.match.',
},
{
# matches, no trailing dot
'DNSName': 'baz.aws.com',
'LoadBalancerArn': 'arn44',
'LoadBalancerName': 'no-dot.unit.tests',
},
{
# name doesn't match, but tags will
'DNSName': 'blip.aws.com',
'LoadBalancerArn': 'arn45',
'LoadBalancerName': 'tags.will.match.',
},
{
# both name and tags match
'DNSName': 'bang.aws.com',
'LoadBalancerArn': 'arn46',
'LoadBalancerName': 'both.unit.tests.',
},
]
},
)
stubber.add_response('describe_load_balancers', self.load_balancers)

stubber.add_response(
'describe_tags',
{
'TagDescriptions': [
{
'ResourceArn': 'arn42',
'Tags': [
{'Key': 'irrelevant', 'Value': 'doesnt matter'}
],
},
{'ResourceArn': 'arn43'},
{'ResourceArn': 'arn44'},
{
'ResourceArn': 'arn45',
'Tags': [
{
'Key': 'octodns',
# multi-value: one matches w/dot. one matches w/o dot, one
# doesn't match
'Value': 'first.unit.tests./second.unit.tests/third.thing.',
}
],
},
{
'ResourceArn': 'arn46',
'Tags': [
{
'Key': 'octodns-1',
# matches
'Value': 'fourth.unit.tests.',
},
{
'Key': 'octodns-2',
# matches w/o dot
'Value': 'fifth.unit.tests',
},
{
'Key': 'octodns-2',
# doesn't match
'Value': 'sixth.doesnt.apply.',
},
{
'Key': 'octodns-3',
# apex match
'Value': 'unit.tests.',
},
],
},
]
},
)
stubber.add_response('describe_tags', self.tags)

source.populate(zone)

Expand All @@ -152,5 +147,31 @@ def test_lbs(self):
for record in records.values():
self.assertEqual('CNAME', record._type)

def test_append(self):
source, stubber = self._get_stubbed_source(append_to_names="local.")

zone = Zone('unit.tests.local.', [])

stubber.add_response('describe_load_balancers', self.load_balancers)

stubber.add_response('describe_tags', self.tags)

source.populate(zone)

# Our append string assumes names end with . - no entries for no-dot,
# second, or fifth
records = {r.name: r for r in zone.records}
self.assertEqual('foo.aws.com.', records['service'].value)
self.assertEqual('blip.aws.com.', records['first'].value)
self.assertEqual('bang.aws.com.', records['both'].value)
self.assertEqual('bang.aws.com.', records['fourth'].value)
self.assertEqual('bang.aws.com.', records[''].value)
self.assertEqual(5, len(records))

record = records.pop('')
self.assertEqual('ALIAS', record._type)
for record in records.values():
self.assertEqual('CNAME', record._type)

def test_conflicting_fqdns(self):
pass

0 comments on commit 4ce9042

Please sign in to comment.