From babd6f0975fc81c4524a0d6a35a334a75aa623bb Mon Sep 17 00:00:00 2001 From: Sarabraj Singh Date: Fri, 10 Jun 2022 14:48:36 -0400 Subject: [PATCH] bubble up an error code when something goes wrong with import/export --- awxkit/awxkit/api/pages/api.py | 13 ++++++++++++- awxkit/awxkit/cli/resource.py | 16 ++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/awxkit/awxkit/api/pages/api.py b/awxkit/awxkit/api/pages/api.py index 56b86b4a21..2c511433a8 100644 --- a/awxkit/awxkit/api/pages/api.py +++ b/awxkit/awxkit/api/pages/api.py @@ -90,6 +90,7 @@ class ApiV2(base.Base): return None if post_fields is None: # Deprecated endpoint or insufficient permissions log.error("Object export failed: %s", _page.endpoint) + self._has_error = True return None # Note: doing _page[key] automatically parses json blob strings, which can be a problem. @@ -110,6 +111,7 @@ class ApiV2(base.Base): pass if resource is None: log.error("Unable to infer endpoint for %r on %s.", key, _page.endpoint) + self._has_error = True continue related = self._filtered_list(resource, _page.json[key]).results[0] else: @@ -119,12 +121,14 @@ class ApiV2(base.Base): if rel_endpoint is None: # This foreign key is unreadable if post_fields[key].get('required'): log.error("Foreign key %r export failed for object %s.", key, _page.endpoint) + self._has_error = True return None log.warning("Foreign key %r export failed for object %s, setting to null", key, _page.endpoint) continue rel_natural_key = rel_endpoint.get_natural_key(self._cache) if rel_natural_key is None: log.error("Unable to construct a natural key for foreign key %r of object %s.", key, _page.endpoint) + self._has_error = True return None # This foreign key has unresolvable dependencies fields[key] = rel_natural_key @@ -169,6 +173,7 @@ class ApiV2(base.Base): natural_key = _page.get_natural_key(self._cache) if natural_key is None: log.error("Unable to construct a natural key for object %s.", _page.endpoint) + self._has_error = True return None fields['natural_key'] = natural_key @@ -260,6 +265,7 @@ class ApiV2(base.Base): except (exc.Common, AssertionError) as e: identifier = asset.get("name", None) or asset.get("username", None) or asset.get("hostname", None) log.error(f"{endpoint} \"{identifier}\": {e}.") + self._has_error = True log.debug("post_data: %r", post_data) continue @@ -294,6 +300,7 @@ class ApiV2(base.Base): pass except exc.Common as e: log.error("Role assignment failed: %s.", e) + self._has_error = True log.debug("post_data: %r", {'id': role_page['id']}) def _assign_membership(self): @@ -324,17 +331,21 @@ class ApiV2(base.Base): for item in related_set: rel_page = self._cache.get_by_natural_key(item) if rel_page is None: - continue # FIXME + log.error("Could not find matching object in Tower for imported relation, item: %r", item) + self._has_error = True + continue if rel_page['id'] in existing: continue try: post_data = {'id': rel_page['id']} endpoint.post(post_data) log.error("endpoint: %s, id: %s", endpoint.endpoint, rel_page['id']) + self._has_error = True except exc.NoContent: # desired exception on successful (dis)association pass except exc.Common as e: log.error("Object association failed: %s.", e) + self._has_error = True log.debug("post_data: %r", post_data) else: # It is a create set self._cache.get_page(endpoint) diff --git a/awxkit/awxkit/cli/resource.py b/awxkit/awxkit/cli/resource.py index ac23115f8d..27b6c623ee 100644 --- a/awxkit/awxkit/cli/resource.py +++ b/awxkit/awxkit/cli/resource.py @@ -144,6 +144,8 @@ class Import(CustomCommand): client.authenticate() client.v2.import_assets(data) + self._has_error = getattr(client.v2, '_has_error', False) + return {} @@ -174,7 +176,11 @@ class Export(CustomCommand): kwargs = {resource: getattr(parsed, resource, None) for resource in EXPORTABLE_RESOURCES} client.authenticate() - return client.v2.export_assets(**kwargs) + data = client.v2.export_assets(**kwargs) + + self._has_error = getattr(client.v2, '_has_error', False) + + return data def parse_resource(client, skip_deprecated=False): @@ -183,6 +189,8 @@ def parse_resource(client, skip_deprecated=False): metavar='resource', ) + _system_exit = 0 + # check if the user is running a custom command for command in CustomCommand.__subclasses__(): client.subparsers[command.name] = subparsers.add_parser(command.name, help=command.help_text) @@ -210,6 +218,10 @@ def parse_resource(client, skip_deprecated=False): parser = client.subparsers[resource] command = CustomCommand.registry[resource]() response = command.handle(client, parser) + + if getattr(command, '_has_error', False): + _system_exit = 1 + if response: _filter = client.get_config('filter') if resource == 'config' and client.get_config('format') == 'human': @@ -221,7 +233,7 @@ def parse_resource(client, skip_deprecated=False): connection = None formatted = format_response(Page.from_json(response, connection=connection), fmt=client.get_config('format'), filter=_filter) print(formatted) - raise SystemExit() + raise SystemExit(_system_exit) else: return resource