AC-403 Add API support for host:port format, port is automatically split into host variables as ansible_ssh_port.

This commit is contained in:
Chris Church
2013-10-22 00:41:49 -04:00
parent c585227df8
commit 16d6ec252f
2 changed files with 67 additions and 24 deletions

View File

@@ -523,9 +523,9 @@ class HostSerializer(BaseSerializerWithVariables):
d['groups'] = [{'id': g.id, 'name': g.name} for g in obj.groups.all()] d['groups'] = [{'id': g.id, 'name': g.name} for g in obj.groups.all()]
return d return d
def _validate_name(self, attrs, source): def _get_host_port_from_name(self, name):
name = unicode(attrs.get(source, ''))
# Allow hostname (except IPv6 for now) to specify the port # inline. # Allow hostname (except IPv6 for now) to specify the port # inline.
port = None
if name.count(':') == 1: if name.count(':') == 1:
name, port = name.split(':') name, port = name.split(':')
try: try:
@@ -533,20 +533,52 @@ class HostSerializer(BaseSerializerWithVariables):
if port < 1 or port > 65535: if port < 1 or port > 65535:
raise ValueError raise ValueError
except ValueError: except ValueError:
raise serializers.ValidationError('Invalid port specification') raise serializers.ValidationError('Invalid port specification: %s' % str(port))
for family in (socket.AF_INET, socket.AF_INET6): return name, port
try:
socket.inet_pton(family, name) def validate_name(self, attrs, source):
return attrs name = unicode(attrs.get(source, ''))
except socket.error: # Validate here only, update in main validate method.
pass host, port = self._get_host_port_from_name(name)
#for family in (socket.AF_INET, socket.AF_INET6):
# try:
# socket.inet_pton(family, name)
# return attrs
# except socket.error:
# pass
# Hostname should match the following regular expression and have at # Hostname should match the following regular expression and have at
# last one letter in the name (to catch invalid IPv4 addresses from # last one letter in the name (to catch invalid IPv4 addresses from
# above). # above).
valid_host_re = r'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' #valid_host_re = r'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
if re.match(valid_host_re, name) and re.match(r'^.*?[a-zA-Z].*?$', name): #if re.match(valid_host_re, name) and re.match(r'^.*?[a-zA-Z].*?$', name):
return attrs # return attrs
raise serializers.ValidationError('Invalid host name or IP') #raise serializers.ValidationError('Invalid host name or IP')
return attrs
def validate(self, attrs):
name = unicode(attrs.get('name', ''))
host, port = self._get_host_port_from_name(name)
if port:
attrs['name'] = host
if self.object:
variables = unicode(attrs.get('variables', self.object.variables) or '')
else:
variables = unicode(attrs.get('variables', ''))
try:
vars_dict = json.loads(variables.strip() or '{}')
vars_dict['ansible_ssh_port'] = port
attrs['variables'] = json.dumps(vars_dict)
except (ValueError, TypeError):
try:
vars_dict = yaml.safe_load(variables)
vars_dict['ansible_ssh_port'] = port
attrs['variables'] = yaml.dump(vars_dict)
except (yaml.YAMLError, TypeError):
raise serializers.ValidationError('Must be valid JSON or YAML')
return attrs
class GroupSerializer(BaseSerializerWithVariables): class GroupSerializer(BaseSerializerWithVariables):

View File

@@ -260,12 +260,18 @@ class InventoryTest(BaseTest):
invalid = dict(name='asdf0.example.com') invalid = dict(name='asdf0.example.com')
new_host_a = dict(name='asdf0.example.com:1022', inventory=inv.pk) new_host_a = dict(name='asdf0.example.com:1022', inventory=inv.pk)
new_host_b = dict(name='asdf1.example.com', inventory=inv.pk) new_host_b = dict(name='asdf1.example.com', inventory=inv.pk)
new_host_c = dict(name='127.1.2.3:2022', inventory=inv.pk) new_host_c = dict(name='127.1.2.3:2022', inventory=inv.pk,
variables=json.dumps({'who': 'what?'}))
new_host_d = dict(name='asdf3.example.com', inventory=inv.pk) new_host_d = dict(name='asdf3.example.com', inventory=inv.pk)
new_host_e = dict(name='asdf4.example.com', inventory=inv.pk) new_host_e = dict(name='asdf4.example.com', inventory=inv.pk)
host_data0 = self.post(hosts, data=invalid, expect=400, auth=self.get_super_credentials()) host_data0 = self.post(hosts, data=invalid, expect=400, auth=self.get_super_credentials())
host_data0 = self.post(hosts, data=new_host_a, expect=201, auth=self.get_super_credentials()) host_data0 = self.post(hosts, data=new_host_a, expect=201, auth=self.get_super_credentials())
# Port should be split out into host variables.
host_a = Host.objects.get(pk=host_data0['id'])
self.assertEqual(host_a.name, 'asdf0.example.com')
self.assertEqual(host_a.variables_dict, {'ansible_ssh_port': 1022})
# an org admin can add hosts # an org admin can add hosts
host_data1 = self.post(hosts, data=new_host_e, expect=201, auth=self.get_normal_credentials()) host_data1 = self.post(hosts, data=new_host_e, expect=201, auth=self.get_normal_credentials())
@@ -280,6 +286,11 @@ class InventoryTest(BaseTest):
) )
host_data3 = self.post(hosts, data=new_host_c, expect=201, auth=self.get_other_credentials()) host_data3 = self.post(hosts, data=new_host_c, expect=201, auth=self.get_other_credentials())
# Port should be split out into host variables, other variables kept intact.
host_c = Host.objects.get(pk=host_data3['id'])
self.assertEqual(host_c.name, '127.1.2.3')
self.assertEqual(host_c.variables_dict, {'ansible_ssh_port': 2022, 'who': 'what?'})
# hostnames must be unique inside an organization # hostnames must be unique inside an organization
host_data4 = self.post(hosts, data=new_host_c, expect=400, auth=self.get_other_credentials()) host_data4 = self.post(hosts, data=new_host_c, expect=400, auth=self.get_other_credentials())
@@ -680,22 +691,22 @@ class InventoryTest(BaseTest):
# Try with invalid hostnames and invalid IPs. # Try with invalid hostnames and invalid IPs.
hosts = reverse('main:host_list') hosts = reverse('main:host_list')
invalid_expect = 201 # hostname validation is disabled for now. invalid_expect = 400 # hostname validation is disabled for now.
data = dict(name='', inventory=inv.pk) data = dict(name='', inventory=inv.pk)
with self.current_user(self.super_django_user): with self.current_user(self.super_django_user):
response = self.post(hosts, data=data, expect=400) response = self.post(hosts, data=data, expect=400)
data = dict(name='not a valid host name', inventory=inv.pk) #data = dict(name='not a valid host name', inventory=inv.pk)
with self.current_user(self.super_django_user): #with self.current_user(self.super_django_user):
response = self.post(hosts, data=data, expect=invalid_expect) # response = self.post(hosts, data=data, expect=invalid_expect)
data = dict(name='validhost:99999', inventory=inv.pk) data = dict(name='validhost:99999', inventory=inv.pk)
with self.current_user(self.super_django_user): with self.current_user(self.super_django_user):
response = self.post(hosts, data=data, expect=invalid_expect) response = self.post(hosts, data=data, expect=invalid_expect)
data = dict(name='123.234.345.456', inventory=inv.pk) #data = dict(name='123.234.345.456', inventory=inv.pk)
with self.current_user(self.super_django_user): #with self.current_user(self.super_django_user):
response = self.post(hosts, data=data, expect=invalid_expect) # response = self.post(hosts, data=data, expect=invalid_expect)
data = dict(name='2001::1::3F', inventory=inv.pk) #data = dict(name='2001::1::3F', inventory=inv.pk)
with self.current_user(self.super_django_user): #with self.current_user(self.super_django_user):
response = self.post(hosts, data=data, expect=invalid_expect) # response = self.post(hosts, data=data, expect=invalid_expect)
######################################################### #########################################################
# FIXME: TAGS # FIXME: TAGS