awx/awxkit/awxkit/api/pages/inventory.py
2021-06-29 11:32:59 -04:00

445 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.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 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='127.0.0.1', ansible_connection='local')
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)