mirror of
https://github.com/ansible/awx.git
synced 2026-02-28 08:18:43 -03:30
Added API validation of instance filter names. Fixes https://trello.com/c/VH9Vnd8z
This commit is contained in:
@@ -744,6 +744,89 @@ class InventorySourceOptions(BaseModel):
|
|||||||
('custom', _('Custom Script')),
|
('custom', _('Custom Script')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Use tools/scripts/get_ec2_filter_names.py to build this list.
|
||||||
|
INSTANCE_FILTER_NAMES = [
|
||||||
|
"architecture",
|
||||||
|
"association.allocation-id",
|
||||||
|
"association.association-id",
|
||||||
|
"association.ip-owner-id",
|
||||||
|
"association.public-ip",
|
||||||
|
"availability-zone",
|
||||||
|
"block-device-mapping.attach-time",
|
||||||
|
"block-device-mapping.delete-on-termination",
|
||||||
|
"block-device-mapping.device-name",
|
||||||
|
"block-device-mapping.status",
|
||||||
|
"block-device-mapping.volume-id",
|
||||||
|
"client-token",
|
||||||
|
"dns-name",
|
||||||
|
"group-id",
|
||||||
|
"group-name",
|
||||||
|
"hypervisor",
|
||||||
|
"iam-instance-profile.arn",
|
||||||
|
"image-id",
|
||||||
|
"instance-id",
|
||||||
|
"instance-lifecycle",
|
||||||
|
"instance-state-code",
|
||||||
|
"instance-state-name",
|
||||||
|
"instance-type",
|
||||||
|
"instance.group-id",
|
||||||
|
"instance.group-name",
|
||||||
|
"ip-address",
|
||||||
|
"kernel-id",
|
||||||
|
"key-name",
|
||||||
|
"launch-index",
|
||||||
|
"launch-time",
|
||||||
|
"monitoring-state",
|
||||||
|
"network-interface-private-dns-name",
|
||||||
|
"network-interface.addresses.association.ip-owner-id",
|
||||||
|
"network-interface.addresses.association.public-ip",
|
||||||
|
"network-interface.addresses.primary",
|
||||||
|
"network-interface.addresses.private-ip-address",
|
||||||
|
"network-interface.attachment.attach-time",
|
||||||
|
"network-interface.attachment.attachment-id",
|
||||||
|
"network-interface.attachment.delete-on-termination",
|
||||||
|
"network-interface.attachment.device-index",
|
||||||
|
"network-interface.attachment.instance-id",
|
||||||
|
"network-interface.attachment.instance-owner-id",
|
||||||
|
"network-interface.attachment.status",
|
||||||
|
"network-interface.availability-zone",
|
||||||
|
"network-interface.description",
|
||||||
|
"network-interface.group-id",
|
||||||
|
"network-interface.group-name",
|
||||||
|
"network-interface.mac-address",
|
||||||
|
"network-interface.network-interface.id",
|
||||||
|
"network-interface.owner-id",
|
||||||
|
"network-interface.requester-id",
|
||||||
|
"network-interface.requester-managed",
|
||||||
|
"network-interface.source-destination-check",
|
||||||
|
"network-interface.status",
|
||||||
|
"network-interface.subnet-id",
|
||||||
|
"network-interface.vpc-id",
|
||||||
|
"owner-id",
|
||||||
|
"placement-group-name",
|
||||||
|
"platform",
|
||||||
|
"private-dns-name",
|
||||||
|
"private-ip-address",
|
||||||
|
"product-code",
|
||||||
|
"product-code.type",
|
||||||
|
"ramdisk-id",
|
||||||
|
"reason",
|
||||||
|
"requester-id",
|
||||||
|
"reservation-id",
|
||||||
|
"root-device-name",
|
||||||
|
"root-device-type",
|
||||||
|
"source-dest-check",
|
||||||
|
"spot-instance-request-id",
|
||||||
|
"state-reason-code",
|
||||||
|
"state-reason-message",
|
||||||
|
"subnet-id",
|
||||||
|
"tag-key",
|
||||||
|
"tag-value",
|
||||||
|
"tenancy",
|
||||||
|
"virtualization-type",
|
||||||
|
"vpc-id"
|
||||||
|
]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
@@ -938,6 +1021,10 @@ class InventorySourceOptions(BaseModel):
|
|||||||
continue
|
continue
|
||||||
if not instance_filter_re.match(instance_filter):
|
if not instance_filter_re.match(instance_filter):
|
||||||
invalid_filters.append(instance_filter)
|
invalid_filters.append(instance_filter)
|
||||||
|
continue
|
||||||
|
instance_filter_name = instance_filter.split('=', 1)[0]
|
||||||
|
if instance_filter_name not in self.INSTANCE_FILTER_NAMES:
|
||||||
|
invalid_filters.append(instance_filter)
|
||||||
if invalid_filters:
|
if invalid_filters:
|
||||||
raise ValidationError('Invalid filter expression%s: %s' %
|
raise ValidationError('Invalid filter expression%s: %s' %
|
||||||
('' if len(invalid_filters) == 1 else 's',
|
('' if len(invalid_filters) == 1 else 's',
|
||||||
|
|||||||
@@ -1272,6 +1272,10 @@ class InventoryUpdatesTest(BaseTransactionTest):
|
|||||||
self.assertEqual(response['group_by'], '')
|
self.assertEqual(response['group_by'], '')
|
||||||
# Invalid string for instance filters.
|
# Invalid string for instance filters.
|
||||||
inv_src_data['instance_filters'] = 'tag-key_123=Name,'
|
inv_src_data['instance_filters'] = 'tag-key_123=Name,'
|
||||||
|
with self.current_user(self.super_django_user):
|
||||||
|
response = self.put(inv_src_url1, inv_src_data, expect=400)
|
||||||
|
# Invalid field name for instance filters.
|
||||||
|
inv_src_data['instance_filters'] = 'foo=bar,'
|
||||||
with self.current_user(self.super_django_user):
|
with self.current_user(self.super_django_user):
|
||||||
response = self.put(inv_src_url1, inv_src_data, expect=400)
|
response = self.put(inv_src_url1, inv_src_data, expect=400)
|
||||||
# Valid string for instance filters.
|
# Valid string for instance filters.
|
||||||
@@ -1514,11 +1518,13 @@ class InventoryUpdatesTest(BaseTransactionTest):
|
|||||||
for host in self.inventory.hosts.filter(active=True):
|
for host in self.inventory.hosts.filter(active=True):
|
||||||
self.assertEqual(host.variables_dict['ec2_instance_type'], instance_type)
|
self.assertEqual(host.variables_dict['ec2_instance_type'], instance_type)
|
||||||
|
|
||||||
# Try invalid instance filters: empty, only "=", more than one "=", whitespace
|
# Try invalid instance filters that should be ignored:
|
||||||
|
# empty filter, only "=", more than one "=", whitespace, invalid value
|
||||||
|
# for given filter name.
|
||||||
cache_path = tempfile.mkdtemp(prefix='awx_ec2_')
|
cache_path = tempfile.mkdtemp(prefix='awx_ec2_')
|
||||||
self._temp_paths.append(cache_path)
|
self._temp_paths.append(cache_path)
|
||||||
key_name = max(key_names.items(), key=lambda x: len(x[1]))[0]
|
key_name = max(key_names.items(), key=lambda x: len(x[1]))[0]
|
||||||
inventory_source.instance_filters = ',=,image-id=ami=12345678,instance-type=%s, key-name=%s' % (instance_type, key_name)
|
inventory_source.instance_filters = ',=,image-id=ami=12345678,instance-type=%s, key-name=%s, architecture=ppc' % (instance_type, key_name)
|
||||||
inventory_source.source_vars = '---\n\nnested_groups: false\ncache_path: %s\n' % cache_path
|
inventory_source.source_vars = '---\n\nnested_groups: false\ncache_path: %s\n' % cache_path
|
||||||
inventory_source.save()
|
inventory_source.save()
|
||||||
self.check_inventory_source(inventory_source, initial=False)
|
self.check_inventory_source(inventory_source, initial=False)
|
||||||
|
|||||||
21
tools/scripts/get_ec2_filter_names.py
Executable file
21
tools/scripts/get_ec2_filter_names.py
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
response = requests.get('http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html')
|
||||||
|
soup = BeautifulSoup(response.text)
|
||||||
|
|
||||||
|
section_h3 = soup.find(id='query-DescribeInstances-filters')
|
||||||
|
section_div = section_h3.find_parent('div', attrs={'class': 'section'})
|
||||||
|
|
||||||
|
filter_names = []
|
||||||
|
for term in section_div.select('div.variablelist dt span.term'):
|
||||||
|
filter_name = term.get_text()
|
||||||
|
if not filter_name.startswith('tag:'):
|
||||||
|
filter_names.append(filter_name)
|
||||||
|
filter_names.sort()
|
||||||
|
|
||||||
|
json.dump(filter_names, sys.stdout, indent=4)
|
||||||
Reference in New Issue
Block a user