From e4146e9bc72beec6e4147c1590b9bfe983aeb895 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Fri, 20 Mar 2020 14:02:12 -0400 Subject: [PATCH] Move the export logic onto methods on the ApiV2 class making it easier to invoke programmatically. --- awxkit/awxkit/api/pages/api.py | 116 ++++++++++++++++++++++++++++++++- awxkit/awxkit/cli/resource.py | 116 ++------------------------------- 2 files changed, 119 insertions(+), 113 deletions(-) diff --git a/awxkit/awxkit/api/pages/api.py b/awxkit/awxkit/api/pages/api.py index 40348ec5d7..323afbd44a 100644 --- a/awxkit/awxkit/api/pages/api.py +++ b/awxkit/awxkit/api/pages/api.py @@ -3,6 +3,60 @@ from . import base from . import page +EXPORTABLE_RESOURCES = [ + 'users', + 'organizations', + 'teams', + 'credential_types', + 'credentials', + 'notification_templates', + # 'projects', + # 'inventory', + # 'job_templates', + # 'workflow_job_templates', +] + + +NATURAL_KEYS = { + 'user': ('username',), + 'organization': ('name',), + 'team': ('organization', 'name'), + 'credential_type': ('name', 'kind'), + 'credential': ('organization', 'name', 'credential_type'), + 'notification_template': ('organization', 'name'), + 'project': ('organization', 'name'), + 'inventory': ('organization', 'name'), + 'job_template': ('organization', 'name'), + 'workflow_job_template': ('organization', 'name'), + + # related resources + 'role': ('name', ':content_object'), +} + + +def get_natural_key(page): + natural_key = {'type': page['type']} + lookup = NATURAL_KEYS.get(page['type'], ()) + + for key in lookup or (): + if key.startswith(':'): + # treat it like a special-case related object + related_objs = [ + related for name, related in page.related.items() + if name not in ('users', 'teams') + ] + if related_objs: + natural_key[key[1:]] = get_natural_key(related_objs[0].get()) + elif key in page.related: + natural_key[key] = get_natural_key(page.related[key].get()) + elif key in page: + natural_key[key] = page[key] + + if not natural_key: + return None + return natural_key + + class Api(base.Base): pass @@ -13,7 +67,67 @@ page.register_page(resources.api, Api) class ApiV2(base.Base): - pass + def _get_options(self, endpoint): + return endpoint.options().json['actions']['POST'] + + def _serialize_asset(self, asset, options): + # Drop any (credential_type) assets that are being managed by the Tower instance. + if asset.json.get('managed_by_tower'): + return None + + fields = { + key: asset[key] for key in options + if key in asset.json and key not in asset.related + } + fields['natural_key'] = get_natural_key(asset) + + fk_fields = { + key: get_natural_key(asset.related[key].get()) for key in options + if key in asset.related + } + + related = {} + for k, related_endpoint in asset.related.items(): + if k != 'roles': + continue + data = related_endpoint.get(all_pages=True) + if 'results' in data: + related[k] = [get_natural_key(x) for x in data.results] + + related_fields = {'related': related} if related else {} + + fields.update(fk_fields) + fields.update(related_fields) + return fields + + def _get_assets(self, resource, value): + endpoint = getattr(self, resource) + if value: + from awxkit.cli.options import pk_or_name + + pk = pk_or_name(self, resource, value) + results = endpoint.get(id=pk).results + else: + results = endpoint.get(all_pages=True).results + + options = self._get_options(endpoint) + assets = (self._serialize_asset(asset, options) for asset in results) + return [asset for asset in assets if asset is not None] + + def export_assets(self, **kwargs): + # If no resource kwargs are explicitly used, export everything. + all_resources = all(kwargs.get(resource) is None for resource in EXPORTABLE_RESOURCES) + + data = {} + for resource in EXPORTABLE_RESOURCES: + value = kwargs.get(resource) + if all_resources or value is not None: + data[resource] = self._get_assets(resource, value) + + return data + + def import_assets(self): + pass page.register_page(resources.v2, ApiV2) diff --git a/awxkit/awxkit/cli/resource.py b/awxkit/awxkit/cli/resource.py index d7ebe698d0..88a3118718 100644 --- a/awxkit/awxkit/cli/resource.py +++ b/awxkit/awxkit/cli/resource.py @@ -8,6 +8,7 @@ import awxkit.exceptions as exc from awxkit.utils import to_str from awxkit.api.mixins import has_create from awxkit.api.pages import Page +from awxkit.api.pages.api import EXPORTABLE_RESOURCES, get_natural_key from awxkit.cli.format import FORMATTERS, format_response, add_authentication_arguments from awxkit.cli.utils import CustomRegistryMeta, cprint @@ -47,58 +48,6 @@ DEPRECATED_RESOURCES_REVERSE = dict( (v, k) for k, v in DEPRECATED_RESOURCES.items() ) -EXPORTABLE_RESOURCES = [ - 'users', - 'organizations', - 'teams', - 'credential_types', - 'credentials', - 'notification_templates', - # 'projects', - # 'inventory', - # 'job_templates', - # 'workflow_job_templates', -] - -NATURAL_KEYS = { - 'user': ('username',), - 'organization': ('name',), - 'team': ('organization', 'name'), - 'credential_type': ('name', 'kind'), - 'credential': ('organization', 'name', 'credential_type'), - 'notification_template': ('organization', 'name'), - 'project': ('organization', 'name'), - 'inventory': ('organization', 'name'), - 'job_template': ('organization', 'name'), - 'workflow_job_template': ('organization', 'name'), - - # related resources - 'role': ('name', ':content_object'), -} - - -def get_natural_key(page): - natural_key = {'type': page['type']} - lookup = NATURAL_KEYS.get(page['type'], ()) - - for key in lookup or (): - if key.startswith(':'): - # treat it like a special-case related object - related_objs = [ - related for name, related in page.related.items() - if name not in ('users', 'teams') - ] - if related_objs: - natural_key[key[1:]] = get_natural_key(related_objs[0].get()) - elif key in page.related: - natural_key[key] = get_natural_key(page.related[key].get()) - elif key in page: - natural_key[key] = page[key] - - if not natural_key: - return None - return natural_key - class CustomCommand(metaclass=CustomRegistryMeta): """Base class for implementing custom commands. @@ -317,53 +266,6 @@ class Export(CustomCommand): # 3) the resource flag is used with an argument, and the attr will be that argument's value resources.add_argument('--{}'.format(resource), nargs='?', const='') - def get_options(self, endpoint): - return endpoint.options().json['actions']['POST'] - - def get_assets(self, resource, value): - endpoint = getattr(self.v2, resource) - if value: - from .options import pk_or_name - - pk = pk_or_name(self.v2, resource, value) - results = endpoint.get(id=pk).results - else: - results = endpoint.get(all_pages=True).results - - options = self.get_options(endpoint) - assets = (self.serialize_asset(asset, options) for asset in results) - return [asset for asset in assets if asset is not None] - - def serialize_asset(self, asset, options): - # Drop any (credential_type) assets that are being managed by the Tower instance. - if asset.json.get('managed_by_tower'): - return None - - fields = { - key: asset[key] for key in options - if key in asset.json and key not in asset.related - } - fields['natural_key'] = get_natural_key(asset) - - fk_fields = { - key: get_natural_key(asset.related[key].get()) for key in options - if key in asset.related - } - - related = {} - for k, related_endpoint in asset.related.items(): - if k != 'roles': - continue - data = related_endpoint.get(all_pages=True) - if 'results' in data: - related[k] = [get_natural_key(x) for x in data.results] - - related_fields = {'related': related} if related else {} - - fields.update(fk_fields) - fields.update(related_fields) - return fields - def handle(self, client, parser): self.extend_parser(parser) @@ -371,21 +273,11 @@ class Export(CustomCommand): parser.print_help() raise SystemExit() - client.authenticate() parsed = parser.parse_known_args()[0] + kwargs = {resource: getattr(parsed, resource, None) for resource in EXPORTABLE_RESOURCES} - # If no resource flags are explicitly used, export everything. - all_resources = all(getattr(parsed, resource, None) is None for resource in EXPORTABLE_RESOURCES) - - self.v2 = client.v2 - - data = {} - for resource in EXPORTABLE_RESOURCES: - value = getattr(parsed, resource, None) - if all_resources or value is not None: - data[resource] = self.get_assets(resource, value) - - return data + client.authenticate() + return client.v2.export_assets(**kwargs) def parse_resource(client, skip_deprecated=False):