mirror of
https://github.com/ansible/awx.git
synced 2026-03-04 10:11:05 -03:30
Finish implementation/tests of job template callback.
This commit is contained in:
@@ -123,7 +123,11 @@ class CustomRbac(permissions.BasePermission):
|
|||||||
return self.has_permission(request, view, obj)
|
return self.has_permission(request, view, obj)
|
||||||
|
|
||||||
class JobTemplateCallbackPermission(CustomRbac):
|
class JobTemplateCallbackPermission(CustomRbac):
|
||||||
|
'''
|
||||||
|
Permission check used by job template callback view for requests from
|
||||||
|
empheral hosts.
|
||||||
|
'''
|
||||||
|
|
||||||
def has_permission(self, request, view, obj=None):
|
def has_permission(self, request, view, obj=None):
|
||||||
# If another authentication method was used and it's not a POST, return
|
# If another authentication method was used and it's not a POST, return
|
||||||
# True to fall through to the next permission class.
|
# True to fall through to the next permission class.
|
||||||
@@ -135,17 +139,20 @@ class JobTemplateCallbackPermission(CustomRbac):
|
|||||||
# active in order to proceed.
|
# active in order to proceed.
|
||||||
host_config_key = request.DATA.get('host_config_key', '')
|
host_config_key = request.DATA.get('host_config_key', '')
|
||||||
if request.method.lower() != 'post':
|
if request.method.lower() != 'post':
|
||||||
return False
|
raise PermissionDenied()
|
||||||
elif not host_config_key:
|
elif not host_config_key:
|
||||||
return False
|
raise PermissionDenied()
|
||||||
elif obj and not obj.active:
|
elif obj and not obj.active:
|
||||||
return False
|
raise PermissionDenied()
|
||||||
elif obj and obj.host_config_key != host_config_key:
|
elif obj and obj.host_config_key != host_config_key:
|
||||||
return False
|
raise PermissionDenied()
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class JobTaskPermission(CustomRbac):
|
class JobTaskPermission(CustomRbac):
|
||||||
|
'''
|
||||||
|
Permission checks used for API callbacks from running a task.
|
||||||
|
'''
|
||||||
|
|
||||||
def has_permission(self, request, view, obj=None):
|
def has_permission(self, request, view, obj=None):
|
||||||
# If another authentication method was used other than the one for job
|
# If another authentication method was used other than the one for job
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class BaseTestMixin(object):
|
|||||||
client_kwargs = {}
|
client_kwargs = {}
|
||||||
if accept:
|
if accept:
|
||||||
client_kwargs['HTTP_ACCEPT'] = accept
|
client_kwargs['HTTP_ACCEPT'] = accept
|
||||||
if remote_addr:
|
if remote_addr is not None:
|
||||||
client_kwargs['REMOTE_ADDR'] = remote_addr
|
client_kwargs['REMOTE_ADDR'] = remote_addr
|
||||||
client = Client(**client_kwargs)
|
client = Client(**client_kwargs)
|
||||||
auth = auth or self._current_auth
|
auth = auth or self._current_auth
|
||||||
|
|||||||
@@ -360,11 +360,11 @@ class BaseJobTestMixin(BaseTestMixin):
|
|||||||
project=self.proj_test,
|
project=self.proj_test,
|
||||||
playbook=self.proj_test.playbooks[0],
|
playbook=self.proj_test.playbooks[0],
|
||||||
host_config_key=uuid.uuid4().hex,
|
host_config_key=uuid.uuid4().hex,
|
||||||
|
credential=self.cred_eve,
|
||||||
created_by=self.user_sue,
|
created_by=self.user_sue,
|
||||||
)
|
)
|
||||||
self.job_sup_run = self.jt_sup_run.create_job(
|
self.job_sup_run = self.jt_sup_run.create_job(
|
||||||
created_by=self.user_sue,
|
created_by=self.user_sue,
|
||||||
credential=self.cred_eve,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Operations has job templates to check/run the prod project onto
|
# Operations has job templates to check/run the prod project onto
|
||||||
@@ -1016,7 +1016,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
|||||||
'''Return test IP address(es) for given test hostname.'''
|
'''Return test IP address(es) for given test hostname.'''
|
||||||
ips = []
|
ips = []
|
||||||
try:
|
try:
|
||||||
h = Host.objects.get(name=host)
|
h = Host.objects.exclude(name__endswith='-alias').get(name=host)
|
||||||
# Primary IP for host (both forward/reverse lookups work).
|
# Primary IP for host (both forward/reverse lookups work).
|
||||||
val = self.atoh('127.10.0.0') + h.pk
|
val = self.atoh('127.10.0.0') + h.pk
|
||||||
ips.append(self.htoa(val))
|
ips.append(self.htoa(val))
|
||||||
@@ -1028,6 +1028,10 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
|||||||
if h.pk % 3 == 0:
|
if h.pk % 3 == 0:
|
||||||
val = self.atoh('127.30.0.0') + h.pk
|
val = self.atoh('127.30.0.0') + h.pk
|
||||||
ips.append(self.htoa(val))
|
ips.append(self.htoa(val))
|
||||||
|
# Additional IP for host (neither forward/reverse lookups work).
|
||||||
|
if h.pk % 3 == 1:
|
||||||
|
val = self.atoh('127.40.0.0') + h.pk
|
||||||
|
ips.append(self.htoa(val))
|
||||||
except Host.DoesNotExist:
|
except Host.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
return ips
|
return ips
|
||||||
@@ -1046,15 +1050,13 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
|||||||
all_ips = set()
|
all_ips = set()
|
||||||
for host in Host.objects.all():
|
for host in Host.objects.all():
|
||||||
ips = self.get_test_ips_for_host(host.name)
|
ips = self.get_test_ips_for_host(host.name)
|
||||||
#print host, ips
|
|
||||||
self.assertTrue(ips)
|
self.assertTrue(ips)
|
||||||
all_ips.update(ips)
|
all_ips.update(ips)
|
||||||
ips = self.get_test_ips_for_host('invalid_host_name')
|
ips = self.get_test_ips_for_host('invalid_host_name')
|
||||||
self.assertFalse(ips)
|
self.assertFalse(ips)
|
||||||
for ip in all_ips:
|
for ip in all_ips:
|
||||||
host = self.get_test_host_for_ip(ip)
|
host = self.get_test_host_for_ip(ip)
|
||||||
#print ip, host
|
if ip.startswith('127.30.') or ip.startswith('127.40.'):
|
||||||
if ip.startswith('127.30.'):
|
|
||||||
continue
|
continue
|
||||||
self.assertTrue(host)
|
self.assertTrue(host)
|
||||||
ips = self.get_test_ips_for_host(host)
|
ips = self.get_test_ips_for_host(host)
|
||||||
@@ -1063,7 +1065,6 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
|||||||
self.assertFalse(host)
|
self.assertFalse(host)
|
||||||
|
|
||||||
def gethostbyaddr(self, ip):
|
def gethostbyaddr(self, ip):
|
||||||
#print 'gethostbyaddr', ip
|
|
||||||
if not ip.startswith('127.'):
|
if not ip.startswith('127.'):
|
||||||
return self._original_gethostbyaddr(ip)
|
return self._original_gethostbyaddr(ip)
|
||||||
host = self.get_test_host_for_ip(ip)
|
host = self.get_test_host_for_ip(ip)
|
||||||
@@ -1073,7 +1074,6 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
|||||||
return (host, [raddr], [ip])
|
return (host, [raddr], [ip])
|
||||||
|
|
||||||
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
|
def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
|
||||||
#print 'getaddrinfo', host, port, family, socktype, proto, flags
|
|
||||||
if family or socktype or proto or flags:
|
if family or socktype or proto or flags:
|
||||||
return self._original_getaddrinfo(host, port, family, socktype,
|
return self._original_getaddrinfo(host, port, family, socktype,
|
||||||
proto, flags)
|
proto, flags)
|
||||||
@@ -1083,6 +1083,7 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
|||||||
addrs = [host]
|
addrs = [host]
|
||||||
except socket.error:
|
except socket.error:
|
||||||
addrs = self.get_test_ips_for_host(host)
|
addrs = self.get_test_ips_for_host(host)
|
||||||
|
addrs = [x for x in addrs if not x.startswith('127.40.')]
|
||||||
if not addrs:
|
if not addrs:
|
||||||
raise socket.gaierror('test host not found')
|
raise socket.gaierror('test host not found')
|
||||||
results = []
|
results = []
|
||||||
@@ -1094,6 +1095,17 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def test_job_template_callback(self):
|
def test_job_template_callback(self):
|
||||||
|
# Set ansible_ssh_host for certain hosts, update name to be an alias.
|
||||||
|
for host in Host.objects.all():
|
||||||
|
ips = self.get_test_ips_for_host(host.name)
|
||||||
|
for ip in ips:
|
||||||
|
if ip.startswith('127.40.'):
|
||||||
|
host.name = '%s-alias' % host.name
|
||||||
|
host_vars = host.variables_dict
|
||||||
|
host_vars['ansible_ssh_host'] = ip
|
||||||
|
host.variables = json.dumps(host_vars)
|
||||||
|
host.save()
|
||||||
|
|
||||||
# Find a valid job template to use to test the callback.
|
# Find a valid job template to use to test the callback.
|
||||||
job_template = None
|
job_template = None
|
||||||
qs = JobTemplate.objects.filter(job_type='run',
|
qs = JobTemplate.objects.filter(job_type='run',
|
||||||
@@ -1106,18 +1118,197 @@ class JobTemplateCallbackTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
|||||||
break
|
break
|
||||||
self.assertTrue(job_template)
|
self.assertTrue(job_template)
|
||||||
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
||||||
|
data = dict(host_config_key=job_template.host_config_key)
|
||||||
|
|
||||||
# Test a POST to start a new job.
|
# Test a POST to start a new job.
|
||||||
with self.current_user(None):
|
host_qs = job_template.inventory.hosts.order_by('pk')
|
||||||
data = dict(host_config_key=job_template.host_config_key)
|
host_qs = host_qs.exclude(variables__icontains='ansible_ssh_host')
|
||||||
host = job_template.inventory.hosts.order_by('-pk')[0]
|
host = host_qs[0]
|
||||||
ip = self.get_test_ips_for_host(host.name)[0]
|
host_ip = self.get_test_ips_for_host(host.name)[0]
|
||||||
jobs_qs = job_template.jobs.filter(launch_type='callback')
|
jobs_qs = job_template.jobs.filter(launch_type='callback').order_by('-pk')
|
||||||
self.assertEqual(jobs_qs.count(), 0)
|
self.assertEqual(jobs_qs.count(), 0)
|
||||||
self.post(url, data, expect=202, remote_addr=ip)
|
self.post(url, data, expect=202, remote_addr=host_ip)
|
||||||
self.assertEqual(jobs_qs.count(), 1)
|
self.assertEqual(jobs_qs.count(), 1)
|
||||||
job = jobs_qs[0]
|
job = jobs_qs[0]
|
||||||
self.assertEqual(job.launch_type, 'callback')
|
self.assertEqual(job.launch_type, 'callback')
|
||||||
self.assertEqual(job.limit, host.name)
|
self.assertEqual(job.limit, host.name)
|
||||||
self.assertEqual(job.hosts.count(), 1)
|
self.assertEqual(job.hosts.count(), 1)
|
||||||
self.assertEqual(job.hosts.all()[0], host)
|
self.assertEqual(job.hosts.all()[0], host)
|
||||||
|
|
||||||
|
# GET as unauthenticated user will prompt for authentication.
|
||||||
|
self.get(url, expect=401, remote_addr=host_ip)
|
||||||
|
|
||||||
|
# Test GET (as super user) to validate host.
|
||||||
|
with self.current_user(self.user_sue):
|
||||||
|
response = self.get(url, expect=200, remote_addr=host_ip)
|
||||||
|
self.assertEqual(response['host_config_key'],
|
||||||
|
job_template.host_config_key)
|
||||||
|
self.assertEqual(response['matching_hosts'], [host.name])
|
||||||
|
|
||||||
|
# POST but leave out the host_config_key.
|
||||||
|
self.post(url, {}, expect=403, remote_addr=host_ip)
|
||||||
|
|
||||||
|
# Try with REMOTE_ADDR empty.
|
||||||
|
self.post(url, data, expect=400, remote_addr='')
|
||||||
|
|
||||||
|
# Try with REMOTE_ADDR set to an unknown address.
|
||||||
|
self.post(url, data, expect=400, remote_addr='127.127.0.1')
|
||||||
|
|
||||||
|
# Try using an alternate IP for the host (but one that also resolves
|
||||||
|
# via reverse lookup).
|
||||||
|
host = None
|
||||||
|
host_ip = None
|
||||||
|
host_qs = job_template.inventory.hosts.order_by('pk')
|
||||||
|
host_qs = host_qs.exclude(variables__icontains='ansible_ssh_host')
|
||||||
|
for h in host_qs:
|
||||||
|
ips = self.get_test_ips_for_host(h.name)
|
||||||
|
for ip in ips:
|
||||||
|
if ip.startswith('127.20.'):
|
||||||
|
host = h
|
||||||
|
host_ip = ip
|
||||||
|
break
|
||||||
|
if host_ip:
|
||||||
|
break
|
||||||
|
self.assertTrue(host)
|
||||||
|
self.assertEqual(jobs_qs.count(), 1)
|
||||||
|
self.post(url, data, expect=202, remote_addr=host_ip)
|
||||||
|
self.assertEqual(jobs_qs.count(), 2)
|
||||||
|
job = jobs_qs[0]
|
||||||
|
self.assertEqual(job.launch_type, 'callback')
|
||||||
|
self.assertEqual(job.limit, host.name)
|
||||||
|
self.assertEqual(job.hosts.count(), 1)
|
||||||
|
self.assertEqual(job.hosts.all()[0], host)
|
||||||
|
|
||||||
|
# Try using an IP for the host that doesn't resolve via reverse lookup,
|
||||||
|
# but can be found by doing a forward lookup on the host name.
|
||||||
|
host = None
|
||||||
|
host_ip = None
|
||||||
|
host_qs = job_template.inventory.hosts.order_by('pk')
|
||||||
|
host_qs = host_qs.exclude(variables__icontains='ansible_ssh_host')
|
||||||
|
for h in host_qs:
|
||||||
|
ips = self.get_test_ips_for_host(h.name)
|
||||||
|
for ip in ips:
|
||||||
|
if ip.startswith('127.30.'):
|
||||||
|
host = h
|
||||||
|
host_ip = ip
|
||||||
|
break
|
||||||
|
if host_ip:
|
||||||
|
break
|
||||||
|
self.assertTrue(host)
|
||||||
|
self.assertEqual(jobs_qs.count(), 2)
|
||||||
|
self.post(url, data, expect=202, remote_addr=host_ip)
|
||||||
|
self.assertEqual(jobs_qs.count(), 3)
|
||||||
|
job = jobs_qs[0]
|
||||||
|
self.assertEqual(job.launch_type, 'callback')
|
||||||
|
self.assertEqual(job.limit, host.name)
|
||||||
|
self.assertEqual(job.hosts.count(), 1)
|
||||||
|
self.assertEqual(job.hosts.all()[0], host)
|
||||||
|
|
||||||
|
# Try using address only specified via ansible_ssh_host.
|
||||||
|
host_qs = job_template.inventory.hosts.order_by('pk')
|
||||||
|
host_qs = host_qs.filter(variables__icontains='ansible_ssh_host')
|
||||||
|
host = host_qs[0]
|
||||||
|
host_ip = host.variables_dict['ansible_ssh_host']
|
||||||
|
self.assertEqual(jobs_qs.count(), 3)
|
||||||
|
self.post(url, data, expect=202, remote_addr=host_ip)
|
||||||
|
self.assertEqual(jobs_qs.count(), 4)
|
||||||
|
job = jobs_qs[0]
|
||||||
|
self.assertEqual(job.launch_type, 'callback')
|
||||||
|
self.assertEqual(job.limit, host.name)
|
||||||
|
self.assertEqual(job.hosts.count(), 1)
|
||||||
|
self.assertEqual(job.hosts.all()[0], host)
|
||||||
|
|
||||||
|
# Try when hostname is also an IP address, even if a different one is
|
||||||
|
# specified via ansible_ssh_host.
|
||||||
|
host_qs = job_template.inventory.hosts.order_by('pk')
|
||||||
|
host_qs = host_qs.exclude(variables__icontains='ansible_ssh_host')
|
||||||
|
host = None
|
||||||
|
host_ip = None
|
||||||
|
for h in host_qs:
|
||||||
|
ips = self.get_test_ips_for_host(h.name)
|
||||||
|
if len(ips) > 1:
|
||||||
|
host = h
|
||||||
|
host.name = list(ips)[0]
|
||||||
|
host_vars = host.variables_dict
|
||||||
|
host_vars['ansible_ssh_host'] = list(ips)[1]
|
||||||
|
host.variables = json.dumps(host_vars)
|
||||||
|
host.save()
|
||||||
|
host_ip = list(ips)[0]
|
||||||
|
break
|
||||||
|
self.assertTrue(host)
|
||||||
|
self.assertEqual(jobs_qs.count(), 4)
|
||||||
|
self.post(url, data, expect=202, remote_addr=host_ip)
|
||||||
|
self.assertEqual(jobs_qs.count(), 5)
|
||||||
|
job = jobs_qs[0]
|
||||||
|
self.assertEqual(job.launch_type, 'callback')
|
||||||
|
self.assertEqual(job.limit, host.name)
|
||||||
|
self.assertEqual(job.hosts.count(), 1)
|
||||||
|
self.assertEqual(job.hosts.all()[0], host)
|
||||||
|
|
||||||
|
# Find a new job template to use.
|
||||||
|
job_template = None
|
||||||
|
qs = JobTemplate.objects.filter(job_type='check',
|
||||||
|
credential__isnull=False)
|
||||||
|
qs = qs.exclude(host_config_key='')
|
||||||
|
for jt in qs:
|
||||||
|
if not jt.can_start_without_user_input():
|
||||||
|
continue
|
||||||
|
job_template = jt
|
||||||
|
break
|
||||||
|
self.assertTrue(job_template)
|
||||||
|
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
||||||
|
data = dict(host_config_key=job_template.host_config_key)
|
||||||
|
|
||||||
|
# Should get an error when multiple hosts match to the same IP.
|
||||||
|
host_qs = job_template.inventory.hosts.order_by('pk')
|
||||||
|
host_qs = host_qs.exclude(name__endswith='-alias')
|
||||||
|
for host in host_qs:
|
||||||
|
host_vars = host.variables_dict
|
||||||
|
host_vars['ansible_ssh_host'] = '127.50.0.1'
|
||||||
|
host.variables = json.dumps(host_vars)
|
||||||
|
host.save()
|
||||||
|
host = host_qs[0]
|
||||||
|
host_ip = host.variables_dict['ansible_ssh_host']
|
||||||
|
self.post(url, data, expect=400, remote_addr=host_ip)
|
||||||
|
|
||||||
|
# Find a job template to run that doesn't have a credential.
|
||||||
|
job_template = None
|
||||||
|
qs = JobTemplate.objects.filter(job_type='run',
|
||||||
|
credential__isnull=True)
|
||||||
|
qs = qs.exclude(host_config_key='')
|
||||||
|
for jt in qs:
|
||||||
|
job_template = jt
|
||||||
|
break
|
||||||
|
self.assertTrue(job_template)
|
||||||
|
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
||||||
|
data = dict(host_config_key=job_template.host_config_key)
|
||||||
|
|
||||||
|
# Test POST to start a new job when the template has no credential.
|
||||||
|
host_qs = job_template.inventory.hosts.order_by('pk')
|
||||||
|
host_qs = host_qs.exclude(variables__icontains='ansible_ssh_host')
|
||||||
|
host = host_qs[0]
|
||||||
|
host_ip = self.get_test_ips_for_host(host.name)[0]
|
||||||
|
self.post(url, data, expect=400, remote_addr=host_ip)
|
||||||
|
|
||||||
|
# Find a job template to run that has a credential but would require
|
||||||
|
# user input.
|
||||||
|
job_template = None
|
||||||
|
qs = JobTemplate.objects.filter(job_type='run',
|
||||||
|
credential__isnull=False)
|
||||||
|
qs = qs.exclude(host_config_key='')
|
||||||
|
for jt in qs:
|
||||||
|
if jt.can_start_without_user_input():
|
||||||
|
continue
|
||||||
|
job_template = jt
|
||||||
|
break
|
||||||
|
self.assertTrue(job_template)
|
||||||
|
url = reverse('main:job_template_callback', args=(job_template.pk,))
|
||||||
|
data = dict(host_config_key=job_template.host_config_key)
|
||||||
|
|
||||||
|
# Test POST to start a new job when the credential would require user
|
||||||
|
# input.
|
||||||
|
host_qs = job_template.inventory.hosts.order_by('pk')
|
||||||
|
host_qs = host_qs.exclude(variables__icontains='ansible_ssh_host')
|
||||||
|
host = host_qs[0]
|
||||||
|
host_ip = self.get_test_ips_for_host(host.name)[0]
|
||||||
|
self.post(url, data, expect=400, remote_addr=host_ip)
|
||||||
|
|||||||
@@ -1069,15 +1069,48 @@ class JobTemplateDetail(BaseDetail):
|
|||||||
|
|
||||||
class JobTemplateCallback(generics.RetrieveAPIView):
|
class JobTemplateCallback(generics.RetrieveAPIView):
|
||||||
'''
|
'''
|
||||||
Configure a host to POST to this resource using the `host_config_key`.
|
The job template callback allows for empheral hosts to launch a new job.
|
||||||
|
|
||||||
|
Configure a host to POST to this resource, passing the `host_config_key`
|
||||||
|
parameter, to start a new job limited to only the requesting host. In the
|
||||||
|
examples below, replace the `N` parameter with the `id` of the job template
|
||||||
|
and the `HOST_CONFIG_KEY` with the `host_config_key` associated with the
|
||||||
|
job template.
|
||||||
|
|
||||||
|
For example, using curl:
|
||||||
|
|
||||||
|
curl --data-urlencode host_config_key=HOST_CONFIG_KEY http://server/api/v1/job_templates/N/callback/
|
||||||
|
|
||||||
|
Or using wget:
|
||||||
|
|
||||||
|
wget -O /dev/null --post-data="host_config_key=HOST_CONFIG_KEY" http://server/api/v1/job_templates/N/callback/
|
||||||
|
|
||||||
|
The response will return status 202 if the request is valid, 403 for an
|
||||||
|
invalid host config key, or 400 if the host cannot be determined from the
|
||||||
|
address making the request.
|
||||||
|
|
||||||
|
A GET request may be used to verify that the correct host will be selected.
|
||||||
|
This request must authenticate as a valid user with permission to edit the
|
||||||
|
job template. For example:
|
||||||
|
|
||||||
|
curl http://user:password@server/api/v1/job_templates/N/callback/
|
||||||
|
|
||||||
|
The response will include the host config key as well as the host name(s)
|
||||||
|
that would match the request:
|
||||||
|
|
||||||
|
{
|
||||||
|
"host_config_key": "HOST_CONFIG_KEY",
|
||||||
|
"matching_hosts": ["hostname"]
|
||||||
|
}
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
model = JobTemplate
|
model = JobTemplate
|
||||||
permission_classes = (JobTemplateCallbackPermission,)
|
permission_classes = (JobTemplateCallbackPermission,)
|
||||||
|
|
||||||
def find_host(self):
|
def find_matching_hosts(self):
|
||||||
'''
|
'''
|
||||||
Find the host in the job template's inventory that matches the remote
|
Find the host(s) in the job template's inventory that match the remote
|
||||||
host for the current request.
|
host for the current request.
|
||||||
'''
|
'''
|
||||||
# Find the list of remote host names/IPs to check.
|
# Find the list of remote host names/IPs to check.
|
||||||
@@ -1099,30 +1132,27 @@ class JobTemplateCallback(generics.RetrieveAPIView):
|
|||||||
if rh.endswith('.arpa'):
|
if rh.endswith('.arpa'):
|
||||||
remote_hosts.remove(rh)
|
remote_hosts.remove(rh)
|
||||||
if not remote_hosts:
|
if not remote_hosts:
|
||||||
return
|
return set()
|
||||||
# Find the host objects to search for a match.
|
# Find the host objects to search for a match.
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
qs = obj.inventory.hosts.filter(active=True)
|
qs = obj.inventory.hosts.filter(active=True)
|
||||||
# First try for an exact match on the name.
|
# First try for an exact match on the name.
|
||||||
try:
|
try:
|
||||||
return qs.get(name__in=remote_hosts)
|
return set([qs.get(name__in=remote_hosts)])
|
||||||
except (Host.DoesNotExist, Host.MultipleObjectsReturned):
|
except (Host.DoesNotExist, Host.MultipleObjectsReturned):
|
||||||
pass
|
pass
|
||||||
# Next, try matching based on name or ansible_ssh_host variable.
|
# Next, try matching based on name or ansible_ssh_host variable.
|
||||||
matches = dict()
|
matches = set()
|
||||||
for host in qs:
|
for host in qs:
|
||||||
ansible_ssh_host = host.variables_dict.get('ansible_ssh_host', '')
|
ansible_ssh_host = host.variables_dict.get('ansible_ssh_host', '')
|
||||||
if ansible_ssh_host in remote_hosts:
|
if ansible_ssh_host in remote_hosts:
|
||||||
if host not in matches:
|
matches.add(host)
|
||||||
matches[host] = 0
|
# FIXME: Not entirely sure if this statement will ever be needed?
|
||||||
matches[host] += 2
|
|
||||||
if host.name != ansible_ssh_host and host.name in remote_hosts:
|
if host.name != ansible_ssh_host and host.name in remote_hosts:
|
||||||
if host not in matches:
|
matches.add(host)
|
||||||
matches[host] = 0
|
|
||||||
matches[host] += 1
|
|
||||||
if len(matches) == 1:
|
if len(matches) == 1:
|
||||||
return matches.keys()[0]
|
return matches
|
||||||
# Try to resolve forward addresses for each host to find a match.
|
# Try to resolve forward addresses for each host to find matches.
|
||||||
for host in qs:
|
for host in qs:
|
||||||
hostnames = set([host.name])
|
hostnames = set([host.name])
|
||||||
ansible_ssh_host = host.variables_dict.get('ansible_ssh_host', '')
|
ansible_ssh_host = host.variables_dict.get('ansible_ssh_host', '')
|
||||||
@@ -1134,22 +1164,18 @@ class JobTemplateCallback(generics.RetrieveAPIView):
|
|||||||
possible_ips = set(x[4][0] for x in result)
|
possible_ips = set(x[4][0] for x in result)
|
||||||
possible_ips.discard(hostname)
|
possible_ips.discard(hostname)
|
||||||
if possible_ips and possible_ips & remote_hosts:
|
if possible_ips and possible_ips & remote_hosts:
|
||||||
if host in matches:
|
matches.add(host)
|
||||||
matches[host] += 1
|
|
||||||
else:
|
|
||||||
matches[host] = 1
|
|
||||||
except socket.gaierror:
|
except socket.gaierror:
|
||||||
pass
|
pass
|
||||||
# Return the host with the highest match weight (in case of multiple
|
# Return all matches found.
|
||||||
# matches).
|
return matches
|
||||||
if matches:
|
|
||||||
return sorted(matches.items(), key=lambda x: x[1])[-1][0]
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
job_template = self.get_object()
|
job_template = self.get_object()
|
||||||
|
matching_hosts = self.find_matching_hosts()
|
||||||
data = dict(
|
data = dict(
|
||||||
host_config_key=job_template.host_config_key,
|
host_config_key=job_template.host_config_key,
|
||||||
matched_host=getattr(self.find_host(), 'name', None),
|
matching_hosts=[x.name for x in matching_hosts],
|
||||||
)
|
)
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
d = dict([(k,v) for k,v in request.META.items()
|
d = dict([(k,v) for k,v in request.META.items()
|
||||||
@@ -1160,12 +1186,20 @@ class JobTemplateCallback(generics.RetrieveAPIView):
|
|||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
job_template = self.get_object()
|
job_template = self.get_object()
|
||||||
# Permission class should have already validated host_config_key.
|
# Permission class should have already validated host_config_key.
|
||||||
host = self.find_host()
|
matching_hosts = self.find_matching_hosts()
|
||||||
if not host:
|
if not matching_hosts:
|
||||||
data = dict(msg='No matching host could be found!')
|
data = dict(msg='No matching host could be found!')
|
||||||
|
# FIXME: Log!
|
||||||
return Response(data, status=400)
|
return Response(data, status=400)
|
||||||
|
elif len(matching_hosts) > 1:
|
||||||
|
data = dict(msg='Multiple hosts matched the request!')
|
||||||
|
# FIXME: Log!
|
||||||
|
return Response(data, status=400)
|
||||||
|
else:
|
||||||
|
host = list(matching_hosts)[0]
|
||||||
if not job_template.can_start_without_user_input():
|
if not job_template.can_start_without_user_input():
|
||||||
data = dict(msg='Cannot start automatically, user input required!')
|
data = dict(msg='Cannot start automatically, user input required!')
|
||||||
|
# FIXME: Log!
|
||||||
return Response(data, status=400)
|
return Response(data, status=400)
|
||||||
limit = ':'.join(filter(None, [job_template.limit, host.name]))
|
limit = ':'.join(filter(None, [job_template.limit, host.name]))
|
||||||
job = job_template.create_job(limit=limit, launch_type='callback')
|
job = job_template.create_job(limit=limit, launch_type='callback')
|
||||||
|
|||||||
Reference in New Issue
Block a user