
Rather than always returning JSON or YAML, add functionality to return table responses when applicable for ease of reading. Adds some marker exceptions to the Shipyard API Client to handle cases where the user of the client would generally want to take a specific and repeatable course of action instead of handling the response in a case-by-case basis. Moved cli action testing to use the responses library instead of as many internal mocks. Added test response generators for standard api responses. Change-Id: I3a593fb29b6e76d971adc7f3bb3a4b7f378ed091
177 lines
6.6 KiB
Python
177 lines
6.6 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# Base classes for cli actions intended to invoke the api
|
|
import abc
|
|
import logging
|
|
|
|
from shipyard_client.api_client.client_error import ClientError
|
|
from shipyard_client.api_client.client_error import UnauthenticatedClientError
|
|
from shipyard_client.api_client.client_error import UnauthorizedClientError
|
|
from shipyard_client.api_client.shipyard_api_client import ShipyardClient
|
|
from shipyard_client.api_client.shipyardclient_context import \
|
|
ShipyardClientContext
|
|
from shipyard_client.cli import format_utils
|
|
|
|
|
|
class AuthValuesError(Exception):
|
|
"""Signals a failure in the authentication values provided to an action
|
|
|
|
Daignostic parameter is forced since newlines in exception text apparently
|
|
do not print with the exception.
|
|
"""
|
|
def __init__(self, *, diagnostic):
|
|
self.diagnostic = diagnostic
|
|
|
|
|
|
class AbstractCliAction(metaclass=abc.ABCMeta):
|
|
"""Abstract base class for CLI actions
|
|
|
|
Base class to encapsulate the items that must be implemented by
|
|
concrete actions
|
|
"""
|
|
|
|
@abc.abstractmethod
|
|
def invoke(self):
|
|
"""Default invoke for CLI actions
|
|
|
|
Descendent classes must override this method to perform the actual
|
|
needed invocation. The expected response from this method is a response
|
|
object or raise an exception.
|
|
"""
|
|
pass
|
|
|
|
@property
|
|
@abc.abstractmethod
|
|
def cli_handled_err_resp_codes(self):
|
|
"""Error response codes
|
|
|
|
Descendent classes shadow this for those response codes from invocation
|
|
that should be handled using the format_utils.cli_format_error_handler
|
|
Note that 401, 403 responses are handled prior to this via exception,
|
|
and should not be represented here. e.g.: [400, 409].
|
|
"""
|
|
pass
|
|
|
|
@property
|
|
@abc.abstractmethod
|
|
def cli_handled_succ_resp_codes(self):
|
|
"""Success response codes
|
|
|
|
Concrete actions must implement cli_handled_succ_resp_codes to indicate
|
|
the response code that should utilize the overridden
|
|
cli_format_response_handler of the sepecific action
|
|
"""
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def cli_format_response_handler(self, response):
|
|
"""Abstract format handler for cli output "good" responses
|
|
|
|
Overridden by descendent classes to indicate the specific output format
|
|
when the ation is invoked with a output format of "cli".
|
|
|
|
Expected to return the string of the output.
|
|
|
|
For those actions that do not have a valid "cli" output, the following
|
|
would be generally appropriate for an implementation of this method to
|
|
return the api client's response:
|
|
|
|
return format_utils.formatted_response_handler(response)
|
|
"""
|
|
pass
|
|
|
|
|
|
class CliAction(AbstractCliAction):
|
|
"""Action base for CliActions"""
|
|
|
|
def __init__(self, ctx):
|
|
"""Initialize CliAction"""
|
|
self.logger = logging.getLogger('shipyard_cli')
|
|
self.api_parameters = ctx.obj['API_PARAMETERS']
|
|
self.resp_txt = ""
|
|
self.needs_credentials = False
|
|
self.output_format = ctx.obj['FORMAT']
|
|
|
|
self.auth_vars = self.api_parameters.get('auth_vars')
|
|
self.context_marker = self.api_parameters.get('context_marker')
|
|
self.debug = self.api_parameters.get('debug')
|
|
|
|
self.client_context = ShipyardClientContext(
|
|
self.auth_vars, self.context_marker, self.debug)
|
|
|
|
def get_api_client(self):
|
|
"""Returns the api client for this action"""
|
|
return ShipyardClient(self.client_context)
|
|
|
|
def invoke_and_return_resp(self):
|
|
"""Lifecycle method to invoke and return a response
|
|
|
|
Calls the invoke method in the child action and returns the formatted
|
|
response.
|
|
"""
|
|
self.logger.debug("Invoking: %s", self.__class__.__name__)
|
|
|
|
try:
|
|
self.validate_auth_vars()
|
|
self.resp_txt = self.output_formatter(self.invoke())
|
|
except AuthValuesError as ave:
|
|
self.resp_txt = "Error: {}".format(ave.diagnostic)
|
|
except UnauthenticatedClientError:
|
|
self.resp_txt = ("Error: Command requires authentication. "
|
|
"Check credential values")
|
|
except UnauthorizedClientError:
|
|
self.resp_txt = "Error: Unauthorized to perform this action."
|
|
except ClientError as ex:
|
|
self.resp_txt = "Error: Client Error: {}".format(str(ex))
|
|
except Exception as ex:
|
|
self.resp_txt = (
|
|
"Error: Unable to invoke action due to: {}".format(str(ex)))
|
|
|
|
return self.resp_txt
|
|
|
|
def output_formatter(self, response):
|
|
"""Formats response (Requests library) from api_client
|
|
|
|
Dispatches to the appropriate response format handler.
|
|
"""
|
|
if self.output_format == 'raw':
|
|
return format_utils.raw_format_response_handler(response)
|
|
elif self.output_format == 'cli':
|
|
if response.status_code in self.cli_handled_err_resp_codes:
|
|
return format_utils.cli_format_error_handler(response)
|
|
elif response.status_code in self.cli_handled_succ_resp_codes:
|
|
return self.cli_format_response_handler(response)
|
|
else:
|
|
self.logger.debug("Unexpected response received")
|
|
return format_utils.cli_format_error_handler(response)
|
|
else: # assume formatted
|
|
return format_utils.formatted_response_handler(response)
|
|
|
|
def validate_auth_vars(self):
|
|
"""Checks that the required authorization varible have been entered"""
|
|
required_auth_vars = ['auth_url']
|
|
err_txt = []
|
|
for var in required_auth_vars:
|
|
if self.auth_vars[var] is None:
|
|
err_txt.append(
|
|
'Missing the required authorization variable: '
|
|
'--os_{}'.format(var))
|
|
if err_txt:
|
|
for var in self.auth_vars:
|
|
if (self.auth_vars.get(var) is None and
|
|
var not in required_auth_vars):
|
|
err_txt.append('- Also not set: --os_{}'.format(var))
|
|
raise AuthValuesError(diagnostic='\n'.join(err_txt))
|