1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-01 08:21:15 +03:00

Add refresh_inventory flag for job_template callback to refresh inventory before trying to find a matching host.

This commit is contained in:
Chris Church 2014-07-03 15:59:45 -04:00
parent 90423db9b7
commit 6813a082f0
4 changed files with 60 additions and 9 deletions

View File

@ -1,4 +1,4 @@
The job template callback allows for empheral hosts to launch a new job.
The job template callback allows for ephemeral 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
@ -18,6 +18,17 @@ 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.
_(New in Ansible Tower 2.0.0)_ By default, the host must already be present in
inventory for the callback to succeed. The `refresh_inventory` parameter can
be passed to the callback to trigger an inventory sync prior to searching for
the host and running the job. The associated inventory must have the
`update_on_launch` flag set and will only refresh if the `update_cache_timeout`
has expired.
For example, using curl:
curl --data-urlencode "host_config_key=HOST_CONFIG_KEY&refresh_inventory=1" http://server/api/v1/job_templates/N/callback/
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:

View File

@ -14,8 +14,7 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db.models import Q, Count, Sum
from django.db import IntegrityError
from django.db import IntegrityError, transaction
from django.shortcuts import get_object_or_404
from django.utils.datastructures import SortedDict
from django.utils.timezone import now
@ -1268,15 +1267,18 @@ class JobTemplateCallback(GenericAPIView):
# Find the list of remote host names/IPs to check.
remote_hosts = set()
for header in settings.REMOTE_HOST_HEADERS:
value = self.request.META.get(header, '').strip()
if value:
remote_hosts.add(value)
for value in self.request.META.get(header, '').split(','):
value = value.strip()
if value:
remote_hosts.add(value)
# Add the reverse lookup of IP addresses.
for rh in list(remote_hosts):
try:
result = socket.gethostbyaddr(rh)
except socket.herror:
continue
except socket.gaierror:
continue
remote_hosts.add(result[0])
remote_hosts.update(result[1])
# Filter out any .arpa results.
@ -1336,9 +1338,35 @@ class JobTemplateCallback(GenericAPIView):
return Response(data)
def post(self, request, *args, **kwargs):
job_template = self.get_object()
# Permission class should have already validated host_config_key.
job_template = self.get_object()
# Attempt to find matching hosts based on remote address.
matching_hosts = self.find_matching_hosts()
# If refresh_inventory flag is provided and the host is not found,
# update the inventory before trying to match the host.
refresh_inventory = request.DATA.get('refresh_inventory', '')
refresh_inventory = bool(refresh_inventory and refresh_inventory[0].lower() in ('t', 'y', '1'))
inventory_sources_already_updated = []
if refresh_inventory and len(matching_hosts) != 1:
inventory_sources = job_template.inventory.inventory_sources.filter(active=True, update_on_launch=True)
inventory_update_pks = set()
for inventory_source in inventory_sources:
if inventory_source.needs_update_on_launch:
# FIXME: Doesn't check for any existing updates.
inventory_update = inventory_source.create_inventory_update(launch_type='callback')
inventory_update.signal_start()
inventory_update_pks.add(inventory_update.pk)
inventory_update_qs = InventoryUpdate.objects.filter(pk__in=inventory_update_pks, status__in=('pending', 'waiting', 'running'))
# Poll for the inventory updates we've started to complete.
while inventory_update_qs.count():
time.sleep(1.0)
transaction.commit()
# Ignore failed inventory updates here, only add successful ones
# to the list to be excluded when running the job.
for inventory_update in InventoryUpdate.objects.filter(pk__in=inventory_update_pks, status='successful'):
inventory_sources_already_updated.append(inventory_update.inventory_source_id)
matching_hosts = self.find_matching_hosts()
# Check matching hosts.
if not matching_hosts:
data = dict(msg='No matching host could be found!')
# FIXME: Log!
@ -1355,7 +1383,7 @@ class JobTemplateCallback(GenericAPIView):
return Response(data, status=status.HTTP_400_BAD_REQUEST)
limit = ':&'.join(filter(None, [job_template.limit, host.name]))
job = job_template.create_job(limit=limit, launch_type='callback')
result = job.signal_start()
result = job.signal_start(inventory_sources_already_updated=inventory_sources_already_updated)
if not result:
data = dict(msg='Error starting job!')
return Response(data, status=status.HTTP_400_BAD_REQUEST)

View File

@ -323,6 +323,18 @@ class Job(UnifiedJob, JobOptions):
if type(obj) == InventoryUpdate:
if obj.inventory_source in inventory_sources:
inventory_sources_found.append(obj.inventory_source)
# Skip updating any inventory sources that were already updated before
# running this job (via callback inventory refresh).
try:
start_args = json.loads(decrypt_field(self, 'start_args'))
except Exception, e:
start_args = None
start_args = start_args or {}
inventory_sources_already_updated = start_args.get('inventory_sources_already_updated', [])
if inventory_sources_already_updated:
for source in inventory_sources.filter(pk__in=inventory_sources_already_updated):
if source not in inventory_sources_found:
inventory_sources_found.append(source)
if not project_found and self.project.needs_update_on_launch:
dependencies.append(self.project.create_project_update(launch_type='dependency'))
if inventory_sources.count(): # and not has_setup_failures? Probably handled as an error scenario in the task runner

View File

@ -25,7 +25,7 @@ angular.module('JobTemplatesHelper', ['Utilities'])
scope.setCallbackHelp = function() {
scope.callback_help = "<p>With a provisioning callback URL and a host config key a host can contact Tower and request a configuration update using this job " +
"template. The request from the host must be a POST. Here is an example using curl:</p>\n" +
"<pre>curl --data \"host_config_key=\"" + scope.example_config_key + "\" " +
"<pre>curl --data \"host_config_key=" + scope.example_config_key + "\" " +
scope.callback_server_path + GetBasePath('job_templates') + scope.example_template_id + "/callback/</pre>\n" +
"<p>Note the requesting host must be defined in the inventory associated with the job template. If Tower fails to " +
"locate the host, the request will be denied.</p>" +