Files
awx/awxkit/awxkit/api/pages/inventory.py
Elijah DeLee 054569da70 emulate workaround present in demo inventory
see 9d000a76de

This change works around the fact that the presumed correct python3 for rhel8 (which the EE is based on)
is not the python3 that ansible-playbook is using, and is not where the python dependencies are installed.
2021-09-03 15:21:34 -04:00

460 lines
15 KiB
Python

from contextlib import suppress
import logging
import json
from awxkit.api.pages import Credential, Organization, Project, UnifiedJob, UnifiedJobTemplate
from awxkit.utils import filter_by_class, random_title, update_payload, not_provided, PseudoNamespace, poll_until
from awxkit.api.mixins import DSAdapter, HasCreate, HasInstanceGroups, HasNotifications, HasVariables, HasCopy
from awxkit.config import config
from awxkit.api.resources import resources
import awxkit.exceptions as exc
from . import base
from . import page
log = logging.getLogger(__name__)
class Inventory(HasCopy, HasCreate, HasInstanceGroups, HasVariables, base.Base):
dependencies = [Organization]
NATURAL_KEY = ('organization', 'name')
def print_ini(self):
"""Print an ini version of the inventory"""
output = list()
inv_dict = self.related.script.get(hostvars=1).json
for group in inv_dict.keys():
if group == '_meta':
continue
# output host groups
output.append('[%s]' % group)
for host in inv_dict[group].get('hosts', []):
# FIXME ... include hostvars
output.append(host)
output.append('') # newline
# output child groups
if inv_dict[group].get('children', []):
output.append('[%s:children]' % group)
for child in inv_dict[group].get('children', []):
output.append(child)
output.append('') # newline
# output group vars
if inv_dict[group].get('vars', {}).items():
output.append('[%s:vars]' % group)
for k, v in inv_dict[group].get('vars', {}).items():
output.append('%s=%s' % (k, v))
output.append('') # newline
print('\n'.join(output))
def payload(self, organization, **kwargs):
payload = PseudoNamespace(
name=kwargs.get('name') or 'Inventory - {}'.format(random_title()),
description=kwargs.get('description') or random_title(10),
organization=organization.id,
)
optional_fields = ('host_filter', 'kind', 'variables')
update_payload(payload, optional_fields, kwargs)
if 'variables' in payload and isinstance(payload.variables, dict):
payload.variables = json.dumps(payload.variables)
return payload
def create_payload(self, name='', description='', organization=Organization, **kwargs):
self.create_and_update_dependencies(organization)
payload = self.payload(name=name, description=description, organization=self.ds.organization, **kwargs)
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
return payload
def create(self, name='', description='', organization=Organization, **kwargs):
payload = self.create_payload(name=name, description=description, organization=organization, **kwargs)
return self.update_identity(Inventories(self.connection).post(payload))
def add_host(self, host=None):
if host is None:
return self.related.hosts.create(inventory=self)
if isinstance(host, base.Base):
host = host.json
with suppress(exc.NoContent):
self.related.hosts.post(host)
return host
def wait_until_deleted(self):
def _wait():
try:
self.get()
except exc.NotFound:
return True
poll_until(_wait, interval=1, timeout=60)
def silent_delete(self):
try:
if not config.prevent_teardown:
r = self.delete()
self.wait_until_deleted()
return r
except (exc.NoContent, exc.NotFound, exc.Forbidden):
pass
except (exc.BadRequest, exc.Conflict) as e:
if 'Resource is being used' in e.msg:
pass
else:
raise e
def update_inventory_sources(self, wait=False):
response = self.related.update_inventory_sources.post()
source_ids = [entry['inventory_source'] for entry in response if entry['status'] == 'started']
inv_updates = []
for source_id in source_ids:
inv_source = self.related.inventory_sources.get(id=source_id).results.pop()
inv_updates.append(inv_source.related.current_job.get())
if wait:
for update in inv_updates:
update.wait_until_completed()
return inv_updates
page.register_page([resources.inventory, (resources.inventories, 'post'), (resources.inventory_copy, 'post')], Inventory)
class Inventories(page.PageList, Inventory):
pass
page.register_page([resources.inventories, resources.related_inventories], Inventories)
class Group(HasCreate, HasVariables, base.Base):
dependencies = [Inventory]
optional_dependencies = [Credential]
NATURAL_KEY = ('name', 'inventory')
@property
def is_root_group(self):
"""Returns whether the current group is a top-level root group in the inventory"""
return self.related.inventory.get().related.root_groups.get(id=self.id).count == 1
def get_parents(self):
"""Inspects the API and returns all groups that include the current group as a child."""
return Groups(self.connection).get(children=self.id).results
def payload(self, inventory, credential=None, **kwargs):
payload = PseudoNamespace(
name=kwargs.get('name') or 'Group{}'.format(random_title(non_ascii=False)),
description=kwargs.get('description') or random_title(10),
inventory=inventory.id,
)
if credential:
payload.credential = credential.id
update_payload(payload, ('variables',), kwargs)
if 'variables' in payload and isinstance(payload.variables, dict):
payload.variables = json.dumps(payload.variables)
return payload
def create_payload(self, name='', description='', inventory=Inventory, credential=None, **kwargs):
self.create_and_update_dependencies(inventory, credential)
credential = self.ds.credential if credential else None
payload = self.payload(inventory=self.ds.inventory, credential=credential, name=name, description=description, **kwargs)
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
return payload
def create(self, name='', description='', inventory=Inventory, **kwargs):
payload = self.create_payload(name=name, description=description, inventory=inventory, **kwargs)
parent = kwargs.get('parent', None) # parent must be a Group instance
resource = parent.related.children if parent else Groups(self.connection)
return self.update_identity(resource.post(payload))
def add_host(self, host=None):
if host is None:
host = self.related.hosts.create(inventory=self.ds.inventory)
with suppress(exc.NoContent):
host.related.groups.post(dict(id=self.id))
return host
if isinstance(host, base.Base):
host = host.json
with suppress(exc.NoContent):
self.related.hosts.post(host)
return host
def add_group(self, group):
if isinstance(group, page.Page):
group = group.json
with suppress(exc.NoContent):
self.related.children.post(group)
def remove_group(self, group):
if isinstance(group, page.Page):
group = group.json
with suppress(exc.NoContent):
self.related.children.post(dict(id=group.id, disassociate=True))
page.register_page([resources.group, (resources.groups, 'post')], Group)
class Groups(page.PageList, Group):
pass
page.register_page(
[
resources.groups,
resources.host_groups,
resources.inventory_related_groups,
resources.inventory_related_root_groups,
resources.group_children,
resources.group_potential_children,
],
Groups,
)
class Host(HasCreate, HasVariables, base.Base):
dependencies = [Inventory]
NATURAL_KEY = ('name', 'inventory')
def payload(self, inventory, **kwargs):
payload = PseudoNamespace(
name=kwargs.get('name') or 'Host{}'.format(random_title(non_ascii=False)),
description=kwargs.get('description') or random_title(10),
inventory=inventory.id,
)
optional_fields = ('enabled', 'instance_id')
update_payload(payload, optional_fields, kwargs)
variables = kwargs.get('variables', not_provided)
if variables is None:
variables = dict(ansible_host='localhost', ansible_connection='local', ansible_python_interpreter='{{ ansible_playbook_python }}')
if variables != not_provided:
if isinstance(variables, dict):
variables = json.dumps(variables)
payload.variables = variables
return payload
def create_payload(self, name='', description='', variables=None, inventory=Inventory, **kwargs):
self.create_and_update_dependencies(*filter_by_class((inventory, Inventory)))
payload = self.payload(inventory=self.ds.inventory, name=name, description=description, variables=variables, **kwargs)
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
return payload
def create(self, name='', description='', variables=None, inventory=Inventory, **kwargs):
payload = self.create_payload(name=name, description=description, variables=variables, inventory=inventory, **kwargs)
return self.update_identity(Hosts(self.connection).post(payload))
page.register_page([resources.host, (resources.hosts, 'post')], Host)
class Hosts(page.PageList, Host):
pass
page.register_page([resources.hosts, resources.group_related_hosts, resources.inventory_related_hosts, resources.inventory_sources_related_hosts], Hosts)
class FactVersion(base.Base):
pass
page.register_page(resources.host_related_fact_version, FactVersion)
class FactVersions(page.PageList, FactVersion):
@property
def count(self):
return len(self.results)
page.register_page(resources.host_related_fact_versions, FactVersions)
class FactView(base.Base):
pass
page.register_page(resources.fact_view, FactView)
class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate):
optional_schedule_fields = tuple()
dependencies = [Inventory]
optional_dependencies = [Credential, Project]
NATURAL_KEY = ('organization', 'name', 'inventory')
def payload(self, inventory, source='scm', credential=None, project=None, **kwargs):
payload = PseudoNamespace(
name=kwargs.get('name') or 'InventorySource - {}'.format(random_title()),
description=kwargs.get('description') or random_title(10),
inventory=inventory.id,
source=source,
)
if credential:
payload.credential = credential.id
if project:
payload.source_project = project.id
optional_fields = (
'source_path',
'source_vars',
'timeout',
'overwrite',
'overwrite_vars',
'update_cache_timeout',
'update_on_launch',
'update_on_project_update',
'verbosity',
)
update_payload(payload, optional_fields, kwargs)
return payload
def create_payload(self, name='', description='', source='scm', inventory=Inventory, credential=None, project=None, **kwargs):
if source == 'scm':
kwargs.setdefault('overwrite_vars', True)
kwargs.setdefault('source_path', 'inventories/script_migrations/script_source.py')
if project is None:
project = Project
inventory, credential, project = filter_by_class((inventory, Inventory), (credential, Credential), (project, Project))
self.create_and_update_dependencies(inventory, credential, project)
if credential:
credential = self.ds.credential
if project:
project = self.ds.project
payload = self.payload(inventory=self.ds.inventory, source=source, credential=credential, project=project, name=name, description=description, **kwargs)
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
return payload
def create(self, name='', description='', source='scm', inventory=Inventory, credential=None, project=None, **kwargs):
payload = self.create_payload(name=name, description=description, source=source, inventory=inventory, credential=credential, project=project, **kwargs)
return self.update_identity(InventorySources(self.connection).post(payload))
def update(self):
"""Update the inventory_source using related->update endpoint"""
# get related->launch
update_pg = self.get_related('update')
# assert can_update == True
assert update_pg.can_update, "The specified inventory_source (id:%s) is not able to update (can_update:%s)" % (self.id, update_pg.can_update)
# start the inventory_update
result = update_pg.post()
# assert JSON response
assert 'inventory_update' in result.json, "Unexpected JSON response when starting an inventory_update.\n%s" % json.dumps(result.json, indent=2)
# locate and return the inventory_update
jobs_pg = self.related.inventory_updates.get(id=result.json['inventory_update'])
assert jobs_pg.count == 1, "An inventory_update started (id:%s) but job not found in response at %s/inventory_updates/" % (
result.json['inventory_update'],
self.url,
)
return jobs_pg.results[0]
@property
def is_successful(self):
"""An inventory_source is considered successful when source != "" and super().is_successful ."""
return self.source != "" and super(InventorySource, self).is_successful
def add_credential(self, credential):
with suppress(exc.NoContent):
self.related.credentials.post(dict(id=credential.id, associate=True))
def remove_credential(self, credential):
with suppress(exc.NoContent):
self.related.credentials.post(dict(id=credential.id, disassociate=True))
page.register_page([resources.inventory_source, (resources.inventory_sources, 'post')], InventorySource)
class InventorySources(page.PageList, InventorySource):
pass
page.register_page([resources.inventory_sources, resources.related_inventory_sources], InventorySources)
class InventorySourceGroups(page.PageList, Group):
pass
page.register_page(resources.inventory_sources_related_groups, InventorySourceGroups)
class InventorySourceUpdate(base.Base):
pass
page.register_page([resources.inventory_sources_related_update, resources.inventory_related_update_inventory_sources], InventorySourceUpdate)
class InventoryUpdate(UnifiedJob):
pass
page.register_page(resources.inventory_update, InventoryUpdate)
class InventoryUpdates(page.PageList, InventoryUpdate):
pass
page.register_page([resources.inventory_updates, resources.inventory_source_updates, resources.project_update_scm_inventory_updates], InventoryUpdates)
class InventoryUpdateCancel(base.Base):
pass
page.register_page(resources.inventory_update_cancel, InventoryUpdateCancel)
class InventoryCopy(base.Base):
pass
page.register_page(resources.inventory_copy, InventoryCopy)