shipyard/src/bin/shipyard_client/shipyard_client/cli/action.py

185 lines
7.1 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.client_error import ShipyardBufferError
from shipyard_client.api_client.client_error import InvalidCollectionError
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,
self.api_parameters.get('verbosity'))
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 ShipyardBufferError as ex:
self.resp_txt = format_utils.cli_format_exception_handler(ex)
except InvalidCollectionError as ex:
self.resp_txt = format_utils.cli_format_exception_handler(ex)
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.replace('_', '-')))
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.replace('_', '-')))
raise AuthValuesError(diagnostic='\n'.join(err_txt))