mirror of
https://github.com/ansible/awx.git
synced 2026-01-28 00:51:27 -03:30
445 lines
15 KiB
Python
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)
|