Add CLI formatted responses to Shipyard CLI
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
This commit is contained in:
parent
4d0b16e1c9
commit
b6d7af07fa
@ -13,11 +13,11 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
alembic==0.9.5
|
alembic==0.9.5
|
||||||
arrow==0.10.0
|
arrow==0.10.0 # API and Client
|
||||||
configparser==3.5.0
|
configparser==3.5.0
|
||||||
falcon==1.2.0
|
falcon==1.2.0
|
||||||
jsonschema==2.6.0
|
jsonschema==2.6.0
|
||||||
keystoneauth1==2.13.0
|
keystoneauth1==2.13.0 # API and Client
|
||||||
keystonemiddleware==4.17.0
|
keystonemiddleware==4.17.0
|
||||||
oslo.config==4.11.0
|
oslo.config==4.11.0
|
||||||
oslo.policy==1.25.1
|
oslo.policy==1.25.1
|
||||||
@ -26,11 +26,13 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
|||||||
psycopg2==2.7.3.1
|
psycopg2==2.7.3.1
|
||||||
python-dateutil==2.6.1
|
python-dateutil==2.6.1
|
||||||
python-memcached==1.58
|
python-memcached==1.58
|
||||||
python-openstackclient==3.11.0
|
requests==2.18.4 # API and Client
|
||||||
requests==2.18.4
|
|
||||||
SQLAlchemy==1.1.13
|
SQLAlchemy==1.1.13
|
||||||
ulid==1.1
|
ulid==1.1
|
||||||
uwsgi==2.0.15
|
uwsgi==2.0.15
|
||||||
|
|
||||||
|
# Client
|
||||||
click==6.7
|
click==6.7
|
||||||
click-default-group==1.2
|
click-default-group==1.2
|
||||||
|
PTable==0.9.2
|
||||||
pyyaml==3.12
|
pyyaml==3.12
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -11,17 +11,44 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import abc
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from keystoneauth1.exceptions.auth import AuthorizationFailure
|
||||||
|
from keystoneauth1.exceptions.catalog import EndpointNotFound
|
||||||
|
from keystoneauth1.identity import v3
|
||||||
|
from keystoneauth1 import session
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .client_error import ClientError
|
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
|
||||||
|
|
||||||
|
|
||||||
class BaseClient:
|
class BaseClient(metaclass=abc.ABCMeta):
|
||||||
|
"""Abstract base client class
|
||||||
|
|
||||||
|
Requrires the definition of service_type and interface by child classes
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def service_type(self):
|
||||||
|
"""Specify the name/type used to lookup the service"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def interface(self):
|
||||||
|
"""The interface to choose from during service lookup
|
||||||
|
|
||||||
|
Specify the interface to look up the service: public, internal admin
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.logger = logging.Logger('api_client')
|
self.logger = logging.Logger('api_client')
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.endpoint = None
|
||||||
|
|
||||||
def log_message(self, level, msg):
|
def log_message(self, level, msg):
|
||||||
""" Logs a message with context, and extra populated. """
|
""" Logs a message with context, and extra populated. """
|
||||||
@ -57,14 +84,21 @@ class BaseClient:
|
|||||||
headers = {
|
headers = {
|
||||||
'X-Context-Marker': self.context.context_marker,
|
'X-Context-Marker': self.context.context_marker,
|
||||||
'content-type': content_type,
|
'content-type': content_type,
|
||||||
'X-Auth-Token': self.context.get_token()
|
'X-Auth-Token': self.get_token()
|
||||||
}
|
}
|
||||||
self.debug('Post request url: ' + url)
|
self.debug('Post request url: ' + url)
|
||||||
self.debug('Query Params: ' + str(query_params))
|
self.debug('Query Params: ' + str(query_params))
|
||||||
# This could use keystoneauth1 session, but that library handles
|
# This could use keystoneauth1 session, but that library handles
|
||||||
# responses strangely (wraps all 400/500 in a keystone exception)
|
# responses strangely (wraps all 400/500 in a keystone exception)
|
||||||
return requests.post(
|
response = requests.post(
|
||||||
url, data=data, params=query_params, headers=headers)
|
url, data=data, params=query_params, headers=headers)
|
||||||
|
# handle some cases where the response code is sufficient to know
|
||||||
|
# what needs to be done
|
||||||
|
if response.status_code == 401:
|
||||||
|
raise UnauthenticatedClientError()
|
||||||
|
if response.status_code == 403:
|
||||||
|
raise UnauthorizedClientError()
|
||||||
|
return response
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
self.error(str(e))
|
self.error(str(e))
|
||||||
raise ClientError(str(e))
|
raise ClientError(str(e))
|
||||||
@ -76,11 +110,52 @@ class BaseClient:
|
|||||||
try:
|
try:
|
||||||
headers = {
|
headers = {
|
||||||
'X-Context-Marker': self.context.context_marker,
|
'X-Context-Marker': self.context.context_marker,
|
||||||
'X-Auth-Token': self.context.get_token()
|
'X-Auth-Token': self.get_token()
|
||||||
}
|
}
|
||||||
self.debug('url: ' + url)
|
self.debug('url: ' + url)
|
||||||
self.debug('Query Params: ' + str(query_params))
|
self.debug('Query Params: ' + str(query_params))
|
||||||
return requests.get(url, params=query_params, headers=headers)
|
response = requests.get(url, params=query_params, headers=headers)
|
||||||
|
# handle some cases where the response code is sufficient to know
|
||||||
|
# what needs to be done
|
||||||
|
if response.status_code == 401:
|
||||||
|
raise UnauthenticatedClientError()
|
||||||
|
if response.status_code == 403:
|
||||||
|
raise UnauthorizedClientError()
|
||||||
|
return response
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
self.error(str(e))
|
self.error(str(e))
|
||||||
raise ClientError(str(e))
|
raise ClientError(str(e))
|
||||||
|
|
||||||
|
def get_token(self):
|
||||||
|
"""
|
||||||
|
Returns the simple token string for a token acquired from keystone
|
||||||
|
"""
|
||||||
|
return self._get_ks_session().get_auth_headers().get('X-Auth-Token')
|
||||||
|
|
||||||
|
def _get_ks_session(self):
|
||||||
|
self.logger.debug('Accessing keystone for keystone session')
|
||||||
|
try:
|
||||||
|
auth = v3.Password(**self.context.keystone_auth)
|
||||||
|
return session.Session(auth=auth)
|
||||||
|
except AuthorizationFailure as e:
|
||||||
|
self.logger.error('Could not authorize against keystone: %s',
|
||||||
|
str(e))
|
||||||
|
raise ClientError(str(e))
|
||||||
|
|
||||||
|
def get_endpoint(self):
|
||||||
|
"""Lookup the endpoint for the client. Cache it.
|
||||||
|
|
||||||
|
Uses a keystone session to find an endpoint for the specified
|
||||||
|
service_type at the specified interface (public, internal, admin)
|
||||||
|
"""
|
||||||
|
if self.endpoint is None:
|
||||||
|
self.logger.debug('Accessing keystone for %s endpoint',
|
||||||
|
self.service_type)
|
||||||
|
try:
|
||||||
|
self.endpoint = self._get_ks_session().get_endpoint(
|
||||||
|
interface=self.interface, service_type=self.service_type)
|
||||||
|
except EndpointNotFound as e:
|
||||||
|
self.logger.error('Could not find %s interface for %s',
|
||||||
|
self.interface, self.service_type)
|
||||||
|
raise ClientError(str(e))
|
||||||
|
return self.endpoint
|
||||||
|
@ -15,3 +15,11 @@
|
|||||||
|
|
||||||
class ClientError(Exception):
|
class ClientError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnauthorizedClientError(ClientError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnauthenticatedClientError(ClientError):
|
||||||
|
pass
|
||||||
|
@ -42,10 +42,9 @@ class ShipyardClient(BaseClient):
|
|||||||
A client for shipyard API
|
A client for shipyard API
|
||||||
:param context: shipyardclient_context, context object
|
:param context: shipyardclient_context, context object
|
||||||
"""
|
"""
|
||||||
|
# Set up the values used to look up the service endpoint.
|
||||||
def __init__(self, context):
|
interface = 'public'
|
||||||
super().__init__(context)
|
service_type = 'shipyard'
|
||||||
self.shipyard_url = context.shipyard_endpoint
|
|
||||||
|
|
||||||
def post_configdocs(self,
|
def post_configdocs(self,
|
||||||
collection_id=None,
|
collection_id=None,
|
||||||
@ -60,8 +59,10 @@ class ShipyardClient(BaseClient):
|
|||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
query_params = {"buffermode": buffer_mode}
|
query_params = {"buffermode": buffer_mode}
|
||||||
url = ApiPaths.POST_GET_CONFIG.value.format(self.shipyard_url,
|
url = ApiPaths.POST_GET_CONFIG.value.format(
|
||||||
collection_id)
|
self.get_endpoint(),
|
||||||
|
collection_id
|
||||||
|
)
|
||||||
return self.post_resp(url, query_params, document_data)
|
return self.post_resp(url, query_params, document_data)
|
||||||
|
|
||||||
def get_configdocs(self, collection_id=None, version='buffer'):
|
def get_configdocs(self, collection_id=None, version='buffer'):
|
||||||
@ -73,8 +74,9 @@ class ShipyardClient(BaseClient):
|
|||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
query_params = {"version": version}
|
query_params = {"version": version}
|
||||||
url = ApiPaths.POST_GET_CONFIG.value.format(self.shipyard_url,
|
url = ApiPaths.POST_GET_CONFIG.value.format(
|
||||||
collection_id)
|
self.get_endpoint(),
|
||||||
|
collection_id)
|
||||||
return self.get_resp(url, query_params)
|
return self.get_resp(url, query_params)
|
||||||
|
|
||||||
def get_rendereddocs(self, version='buffer'):
|
def get_rendereddocs(self, version='buffer'):
|
||||||
@ -84,7 +86,9 @@ class ShipyardClient(BaseClient):
|
|||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
query_params = {"version": version}
|
query_params = {"version": version}
|
||||||
url = ApiPaths.GET_RENDERED.value.format(self.shipyard_url)
|
url = ApiPaths.GET_RENDERED.value.format(
|
||||||
|
self.get_endpoint()
|
||||||
|
)
|
||||||
return self.get_resp(url, query_params)
|
return self.get_resp(url, query_params)
|
||||||
|
|
||||||
def commit_configdocs(self, force=False):
|
def commit_configdocs(self, force=False):
|
||||||
@ -94,7 +98,7 @@ class ShipyardClient(BaseClient):
|
|||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
query_params = {"force": force}
|
query_params = {"force": force}
|
||||||
url = ApiPaths.COMMIT_CONFIG.value.format(self.shipyard_url)
|
url = ApiPaths.COMMIT_CONFIG.value.format(self.get_endpoint())
|
||||||
return self.post_resp(url, query_params)
|
return self.post_resp(url, query_params)
|
||||||
|
|
||||||
def get_actions(self):
|
def get_actions(self):
|
||||||
@ -103,7 +107,9 @@ class ShipyardClient(BaseClient):
|
|||||||
:returns: lists all actions
|
:returns: lists all actions
|
||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
url = ApiPaths.POST_GET_ACTIONS.value.format(self.shipyard_url)
|
url = ApiPaths.POST_GET_ACTIONS.value.format(
|
||||||
|
self.get_endpoint()
|
||||||
|
)
|
||||||
return self.get_resp(url)
|
return self.get_resp(url)
|
||||||
|
|
||||||
def post_actions(self, name=None, parameters=None):
|
def post_actions(self, name=None, parameters=None):
|
||||||
@ -115,7 +121,9 @@ class ShipyardClient(BaseClient):
|
|||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
action_data = {"name": name, "parameters": parameters}
|
action_data = {"name": name, "parameters": parameters}
|
||||||
url = ApiPaths.POST_GET_ACTIONS.value.format(self.shipyard_url)
|
url = ApiPaths.POST_GET_ACTIONS.value.format(
|
||||||
|
self.get_endpoint()
|
||||||
|
)
|
||||||
return self.post_resp(
|
return self.post_resp(
|
||||||
url, data=json.dumps(action_data), content_type='application/json')
|
url, data=json.dumps(action_data), content_type='application/json')
|
||||||
|
|
||||||
@ -126,8 +134,10 @@ class ShipyardClient(BaseClient):
|
|||||||
:returns: information describing the action
|
:returns: information describing the action
|
||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
url = ApiPaths.GET_ACTION_DETAIL.value.format(self.shipyard_url,
|
url = ApiPaths.GET_ACTION_DETAIL.value.format(
|
||||||
action_id)
|
self.get_endpoint(),
|
||||||
|
action_id
|
||||||
|
)
|
||||||
return self.get_resp(url)
|
return self.get_resp(url)
|
||||||
|
|
||||||
def get_validation_detail(self, action_id=None, validation_id=None):
|
def get_validation_detail(self, action_id=None, validation_id=None):
|
||||||
@ -139,7 +149,7 @@ class ShipyardClient(BaseClient):
|
|||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
url = ApiPaths.GET_VALIDATION_DETAIL.value.format(
|
url = ApiPaths.GET_VALIDATION_DETAIL.value.format(
|
||||||
self.shipyard_url, action_id, validation_id)
|
self.get_endpoint(), action_id, validation_id)
|
||||||
return self.get_resp(url)
|
return self.get_resp(url)
|
||||||
|
|
||||||
def get_step_detail(self, action_id=None, step_id=None):
|
def get_step_detail(self, action_id=None, step_id=None):
|
||||||
@ -150,8 +160,11 @@ class ShipyardClient(BaseClient):
|
|||||||
:returns: details for a step by id for the given action by Id
|
:returns: details for a step by id for the given action by Id
|
||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
url = ApiPaths.GET_STEP_DETAIL.value.format(self.shipyard_url,
|
url = ApiPaths.GET_STEP_DETAIL.value.format(
|
||||||
action_id, step_id)
|
self.get_endpoint(),
|
||||||
|
action_id,
|
||||||
|
step_id
|
||||||
|
)
|
||||||
return self.get_resp(url)
|
return self.get_resp(url)
|
||||||
|
|
||||||
def post_control_action(self, action_id=None, control_verb=None):
|
def post_control_action(self, action_id=None, control_verb=None):
|
||||||
@ -163,7 +176,7 @@ class ShipyardClient(BaseClient):
|
|||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
url = ApiPaths.POST_CONTROL_ACTION.value.format(
|
url = ApiPaths.POST_CONTROL_ACTION.value.format(
|
||||||
self.shipyard_url, action_id, control_verb)
|
self.get_endpoint(), action_id, control_verb)
|
||||||
return self.post_resp(url)
|
return self.post_resp(url)
|
||||||
|
|
||||||
def get_workflows(self, since=None):
|
def get_workflows(self, since=None):
|
||||||
@ -175,7 +188,7 @@ class ShipyardClient(BaseClient):
|
|||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
query_params = {'since': since}
|
query_params = {'since': since}
|
||||||
url = ApiPaths.GET_WORKFLOWS.value.format(self.shipyard_url)
|
url = ApiPaths.GET_WORKFLOWS.value.format(self.get_endpoint())
|
||||||
return self.get_resp(url, query_params)
|
return self.get_resp(url, query_params)
|
||||||
|
|
||||||
def get_dag_detail(self, workflow_id=None):
|
def get_dag_detail(self, workflow_id=None):
|
||||||
@ -185,6 +198,6 @@ class ShipyardClient(BaseClient):
|
|||||||
:returns: details of a DAGs output
|
:returns: details of a DAGs output
|
||||||
:rtype: Response object
|
:rtype: Response object
|
||||||
"""
|
"""
|
||||||
url = ApiPaths.GET_DAG_DETAIL.value.format(self.shipyard_url,
|
url = ApiPaths.GET_DAG_DETAIL.value.format(self.get_endpoint(),
|
||||||
workflow_id)
|
workflow_id)
|
||||||
return self.get_resp(url)
|
return self.get_resp(url)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -11,64 +11,25 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from keystoneauth1 import session
|
|
||||||
from keystoneauth1.identity import v3
|
|
||||||
from keystoneauth1.exceptions.auth import AuthorizationFailure
|
|
||||||
from keystoneauth1.exceptions.catalog import EndpointNotFound
|
|
||||||
from .client_error import ClientError
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ShipyardClientContext:
|
class ShipyardClientContext:
|
||||||
|
"""A context object for ShipyardClient instances."""
|
||||||
|
|
||||||
def __init__(self, keystone_auth, context_marker, debug=False):
|
def __init__(self, keystone_auth, context_marker, debug=False):
|
||||||
"""
|
"""Shipyard context object
|
||||||
shipyard context object
|
|
||||||
:param bool debug: true, or false
|
:param bool debug: true, or false
|
||||||
:param str context_marker:
|
:param str context_marker:
|
||||||
:param dict keystone_auth: auth_url, password, project_domain_name,
|
:param dict keystone_auth: auth_url, password, project_domain_name,
|
||||||
project_name, username, user_domain_name
|
project_name, username, user_domain_name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.keystone_auth = keystone_auth
|
|
||||||
# the service type will for now just be shipyard will change later
|
|
||||||
self.service_type = 'shipyard'
|
|
||||||
self.shipyard_endpoint = self.get_endpoint()
|
|
||||||
self.set_debug()
|
|
||||||
self.context_marker = context_marker
|
|
||||||
|
|
||||||
def set_debug(self):
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
LOG.setLevel(logging.DEBUG)
|
LOG.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
def get_token(self):
|
self.keystone_auth = keystone_auth
|
||||||
"""
|
self.context_marker = context_marker
|
||||||
Returns the simple token string for a token acquired from keystone
|
|
||||||
"""
|
|
||||||
return self._get_ks_session().get_auth_headers().get('X-Auth-Token')
|
|
||||||
|
|
||||||
def _get_ks_session(self):
|
|
||||||
LOG.debug('Accessing keystone for keystone session')
|
|
||||||
try:
|
|
||||||
auth = v3.Password(**self.keystone_auth)
|
|
||||||
return session.Session(auth=auth)
|
|
||||||
except AuthorizationFailure as e:
|
|
||||||
LOG.error('Could not authorize against keystone: %s', str(e))
|
|
||||||
raise ClientError(str(e))
|
|
||||||
|
|
||||||
def get_endpoint(self):
|
|
||||||
"""
|
|
||||||
Wraps calls to keystone for lookup with overrides from configuration
|
|
||||||
"""
|
|
||||||
LOG.debug('Accessing keystone for %s endpoint', self.service_type)
|
|
||||||
try:
|
|
||||||
return self._get_ks_session().get_endpoint(
|
|
||||||
interface='public', service_type=self.service_type)
|
|
||||||
except EndpointNotFound as e:
|
|
||||||
LOG.error('Could not find a public interface for %s',
|
|
||||||
self.service_type)
|
|
||||||
raise ClientError(str(e))
|
|
||||||
|
@ -13,60 +13,164 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# Base classes for cli actions intended to invoke the api
|
# Base classes for cli actions intended to invoke the api
|
||||||
|
import abc
|
||||||
import logging
|
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.shipyard_api_client import ShipyardClient
|
||||||
from shipyard_client.api_client.shipyardclient_context import \
|
from shipyard_client.api_client.shipyardclient_context import \
|
||||||
ShipyardClientContext
|
ShipyardClientContext
|
||||||
from shipyard_client.api_client.client_error import ClientError
|
from shipyard_client.cli import format_utils
|
||||||
from shipyard_client.cli.input_checks import validate_auth_vars
|
|
||||||
|
|
||||||
|
|
||||||
class CliAction(object):
|
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"""
|
"""Action base for CliActions"""
|
||||||
|
|
||||||
def __init__(self, ctx):
|
def __init__(self, ctx):
|
||||||
"""Sets api_client"""
|
"""Initialize CliAction"""
|
||||||
self.logger = logging.getLogger('shipyard_cli')
|
self.logger = logging.getLogger('shipyard_cli')
|
||||||
self.api_parameters = ctx.obj['API_PARAMETERS']
|
self.api_parameters = ctx.obj['API_PARAMETERS']
|
||||||
self.resp_txt = ""
|
self.resp_txt = ""
|
||||||
self.needs_credentials = False
|
self.needs_credentials = False
|
||||||
|
self.output_format = ctx.obj['FORMAT']
|
||||||
|
|
||||||
auth_vars = self.api_parameters['auth_vars']
|
self.auth_vars = self.api_parameters.get('auth_vars')
|
||||||
context_marker = self.api_parameters['context_marker']
|
self.context_marker = self.api_parameters.get('context_marker')
|
||||||
debug = self.api_parameters['debug']
|
self.debug = self.api_parameters.get('debug')
|
||||||
|
|
||||||
validate_auth_vars(ctx, self.api_parameters.get('auth_vars'))
|
self.client_context = ShipyardClientContext(
|
||||||
|
self.auth_vars, self.context_marker, self.debug)
|
||||||
|
|
||||||
self.logger.debug("Passing environment varibles to the API client")
|
def get_api_client(self):
|
||||||
try:
|
"""Returns the api client for this action"""
|
||||||
shipyard_client_context = ShipyardClientContext(
|
return ShipyardClient(self.client_context)
|
||||||
auth_vars, context_marker, debug)
|
|
||||||
self.api_client = ShipyardClient(shipyard_client_context)
|
|
||||||
except ClientError as e:
|
|
||||||
self.logger.debug("The shipyard Client Context could not be set.")
|
|
||||||
ctx.fail('Client Error: %s.' % str(e))
|
|
||||||
|
|
||||||
def invoke_and_return_resp(self):
|
def invoke_and_return_resp(self):
|
||||||
"""
|
"""Lifecycle method to invoke and return a response
|
||||||
calls the invoke method in the approiate actions.py and returns the
|
|
||||||
formatted response
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.logger.debug("Inoking action.")
|
Calls the invoke method in the child action and returns the formatted
|
||||||
env_vars = self.api_parameters['auth_vars']
|
response.
|
||||||
|
"""
|
||||||
|
self.logger.debug("Invoking: %s", self.__class__.__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.invoke()
|
self.validate_auth_vars()
|
||||||
except ClientError as e:
|
self.resp_txt = self.output_formatter(self.invoke())
|
||||||
self.resp_txt = "Client Error: %s." % str(e)
|
except AuthValuesError as ave:
|
||||||
except Exception as e:
|
self.resp_txt = "Error: {}".format(ave.diagnostic)
|
||||||
self.resp_txt = "Error: Unable to invoke action because %s." % str(
|
except UnauthenticatedClientError:
|
||||||
e)
|
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
|
return self.resp_txt
|
||||||
|
|
||||||
def invoke(self):
|
def output_formatter(self, response):
|
||||||
"""Default invoke"""
|
"""Formats response (Requests library) from api_client
|
||||||
self.resp_txt = "Error: Invoke method is not defined for this action."
|
|
||||||
|
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))
|
||||||
|
211
shipyard_client/cli/cli_format_common.py
Normal file
211
shipyard_client/cli/cli_format_common.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
# 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.
|
||||||
|
"""Reusable parts for outputting Shipyard results in CLI format"""
|
||||||
|
|
||||||
|
from shipyard_client.cli import format_utils
|
||||||
|
|
||||||
|
|
||||||
|
def gen_action_steps(step_list, action_id):
|
||||||
|
"""Generate a table from the list of steps.
|
||||||
|
|
||||||
|
Assumes that the input list contains dictionaries with 'id', 'index', and
|
||||||
|
'state' fields.
|
||||||
|
Returns a string representation of the table.
|
||||||
|
"""
|
||||||
|
# Generate the steps table.
|
||||||
|
steps = format_utils.table_factory(
|
||||||
|
field_names=['Steps', 'Index', 'State']
|
||||||
|
)
|
||||||
|
if step_list:
|
||||||
|
for step in step_list:
|
||||||
|
steps.add_row(
|
||||||
|
['step/{}/{}'.format(action_id, step.get('id')),
|
||||||
|
step.get('index'),
|
||||||
|
step.get('state')]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
steps.add_row(['None', '', ''])
|
||||||
|
|
||||||
|
return format_utils.table_get_string(steps)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_action_commands(command_list):
|
||||||
|
"""Generate a table from the list of commands
|
||||||
|
|
||||||
|
Assumes command_list is a list of dictionaries with 'command', 'user', and
|
||||||
|
'datetime'.
|
||||||
|
"""
|
||||||
|
cmds = format_utils.table_factory(
|
||||||
|
field_names=['Commands', 'User', 'Datetime']
|
||||||
|
)
|
||||||
|
if command_list:
|
||||||
|
for cmd in command_list:
|
||||||
|
cmds.add_row(
|
||||||
|
[cmd.get('command'), cmd.get('user'), cmd.get('datetime')]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cmds.add_row(['None', '', ''])
|
||||||
|
|
||||||
|
return format_utils.table_get_string(cmds)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_action_validations(validation_list):
|
||||||
|
"""Generates a CLI formatted listing of validations
|
||||||
|
|
||||||
|
Assumes validation_list is a list of dictionaries with 'validation_name',
|
||||||
|
'action_id', 'id', and 'details'.
|
||||||
|
"""
|
||||||
|
if validation_list:
|
||||||
|
validations = []
|
||||||
|
for val in validation_list:
|
||||||
|
validations.append('{} : validation/{}/{}\n'.format(
|
||||||
|
val.get('validation_name'),
|
||||||
|
val.get('action_id'),
|
||||||
|
val.get('id')
|
||||||
|
))
|
||||||
|
validations.append(val.get('details'))
|
||||||
|
validations.append('\n\n')
|
||||||
|
return 'Validations: {}'.format('\n'.join(validations))
|
||||||
|
else:
|
||||||
|
return 'Validations: {}'.format('None')
|
||||||
|
|
||||||
|
|
||||||
|
def gen_action_details(action_dict):
|
||||||
|
"""Generates the detailed information for an action
|
||||||
|
|
||||||
|
Assumes action_dict is a dictionary with 'name', 'id', 'action_lifecycle',
|
||||||
|
'parameters', 'datetime', 'dag_status', 'context_marker', and 'user'
|
||||||
|
"""
|
||||||
|
details = format_utils.table_factory()
|
||||||
|
details.add_row(['Name:', action_dict.get('name')])
|
||||||
|
details.add_row(['Action:', 'action/{}'.format(action_dict.get('id'))])
|
||||||
|
details.add_row(['Lifecycle:', action_dict.get('action_lifecycle')])
|
||||||
|
details.add_row(['Parameters:', str(action_dict.get('parameters'))])
|
||||||
|
details.add_row(['Datetime:', action_dict.get('datetime')])
|
||||||
|
details.add_row(['Dag Status:', action_dict.get('dag_status')])
|
||||||
|
details.add_row(['Context Marker:', action_dict.get('context_marker')])
|
||||||
|
details.add_row(['User:', action_dict.get('user')])
|
||||||
|
return format_utils.table_get_string(details)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_action_step_details(step_dict, action_id):
|
||||||
|
"""Generates the detailed information for an action step
|
||||||
|
|
||||||
|
Assumes action_dict is a dictionary with 'index', 'state', 'start_date',
|
||||||
|
'end_date', 'duration', 'try_number', and 'operator'
|
||||||
|
"""
|
||||||
|
details = format_utils.table_factory()
|
||||||
|
details.add_row(['Name:', step_dict.get('task_id')])
|
||||||
|
details.add_row(['Task ID:', 'step/{}/{}'.format(
|
||||||
|
action_id,
|
||||||
|
step_dict.get('task_id')
|
||||||
|
)])
|
||||||
|
details.add_row(['Index:', step_dict.get('index')])
|
||||||
|
details.add_row(['State:', step_dict.get('state')])
|
||||||
|
details.add_row(['Start Date:', step_dict.get('start_date')])
|
||||||
|
details.add_row(['End Date:', step_dict.get('end_date')])
|
||||||
|
details.add_row(['Duration:', step_dict.get('duration')])
|
||||||
|
details.add_row(['Try Number:', step_dict.get('try_number')])
|
||||||
|
details.add_row(['Operator:', step_dict.get('operator')])
|
||||||
|
return format_utils.table_get_string(details)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_action_table(action_list):
|
||||||
|
"""Generates a list of actions
|
||||||
|
|
||||||
|
Assumes action_list is a list of dictionaries with 'name', 'id', and
|
||||||
|
'action_lifecycle'
|
||||||
|
"""
|
||||||
|
actions = format_utils.table_factory(
|
||||||
|
field_names=['Name', 'Action', 'Lifecycle']
|
||||||
|
)
|
||||||
|
if action_list:
|
||||||
|
for action in action_list:
|
||||||
|
actions.add_row(
|
||||||
|
[action.get('name'),
|
||||||
|
'action/{}'.format(action.get('id')),
|
||||||
|
action.get('action_lifecycle')]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
actions.add_row(['None', '', ''])
|
||||||
|
|
||||||
|
return format_utils.table_get_string(actions)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_workflow_table(workflow_list):
|
||||||
|
"""Generates a list of workflows
|
||||||
|
|
||||||
|
Assumes workflow_list is a list of dictionaries with 'workflow_id' and
|
||||||
|
'state'
|
||||||
|
"""
|
||||||
|
workflows = format_utils.table_factory(
|
||||||
|
field_names=['Workflows', 'State']
|
||||||
|
)
|
||||||
|
if workflow_list:
|
||||||
|
for workflow in workflow_list:
|
||||||
|
workflows.add_row(
|
||||||
|
[workflow.get('workflow_id'), workflow.get('state')])
|
||||||
|
else:
|
||||||
|
workflows.add_row(['None', ''])
|
||||||
|
|
||||||
|
return format_utils.table_get_string(workflows)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_workflow_details(workflow_dict):
|
||||||
|
"""Generates a workflow detail
|
||||||
|
|
||||||
|
Assumes workflow_dict has 'execution_date', 'end_date', 'workflow_id',
|
||||||
|
'start_date', 'external_trigger', 'steps', 'dag_id', 'state', 'run_id',
|
||||||
|
and 'sub_dags'
|
||||||
|
"""
|
||||||
|
details = format_utils.table_factory()
|
||||||
|
details.add_row(['Workflow:', workflow_dict.get('workflow_id')])
|
||||||
|
|
||||||
|
details.add_row(['State:', workflow_dict.get('state')])
|
||||||
|
details.add_row(['Dag ID:', workflow_dict.get('dag_id')])
|
||||||
|
details.add_row(['Execution Date:', workflow_dict.get('execution_date')])
|
||||||
|
details.add_row(['Start Date:', workflow_dict.get('start_date')])
|
||||||
|
details.add_row(['End Date:', workflow_dict.get('end_date')])
|
||||||
|
details.add_row(['External Trigger:',
|
||||||
|
workflow_dict.get('external_trigger')])
|
||||||
|
return format_utils.table_get_string(details)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_workflow_steps(step_list):
|
||||||
|
"""Generates a table of steps for a workflow
|
||||||
|
|
||||||
|
Assumes step_list is a list of dictionaries with 'task_id' and 'state'
|
||||||
|
"""
|
||||||
|
steps = format_utils.table_factory(
|
||||||
|
field_names=['Steps', 'State']
|
||||||
|
)
|
||||||
|
if step_list:
|
||||||
|
for step in step_list:
|
||||||
|
steps.add_row([step.get('task_id'), step.get('state')])
|
||||||
|
else:
|
||||||
|
steps.add_row(['None', ''])
|
||||||
|
|
||||||
|
return format_utils.table_get_string(steps)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_sub_workflows(wf_list):
|
||||||
|
"""Generates the list of Sub Workflows
|
||||||
|
|
||||||
|
Assumes wf_list is a list of dictionaries with the same contents as a
|
||||||
|
standard workflow
|
||||||
|
"""
|
||||||
|
wfs = []
|
||||||
|
for wf in wf_list:
|
||||||
|
wfs.append(gen_workflow_details(wf))
|
||||||
|
return '\n\n'.join(wfs)
|
@ -13,23 +13,38 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from shipyard_client.cli.action import CliAction
|
from shipyard_client.cli.action import CliAction
|
||||||
from shipyard_client.cli.output_formatting import output_formatting
|
from shipyard_client.cli import format_utils
|
||||||
|
|
||||||
|
|
||||||
class CommitConfigdocs(CliAction):
|
class CommitConfigdocs(CliAction):
|
||||||
"""Actions to Commit Configdocs"""
|
"""Actions to Commit Configdocs"""
|
||||||
|
|
||||||
def __init__(self, ctx, force):
|
def __init__(self, ctx, force):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.force = force
|
self.force = force
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
self.logger.debug("CommitConfigdocs action initialized with force=%s",
|
self.logger.debug("CommitConfigdocs action initialized with force=%s",
|
||||||
force)
|
force)
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client commit_configdocs.")
|
self.logger.debug("Calling API Client commit_configdocs.")
|
||||||
self.resp_txt = output_formatting(
|
return self.get_api_client().commit_configdocs(force=self.force)
|
||||||
self.output_format,
|
|
||||||
self.api_client.commit_configdocs(force=self.force))
|
# Handle 400, 409 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [400, 409]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
outfmt_string = "Configuration documents committed.\n{}"
|
||||||
|
return outfmt_string.format(
|
||||||
|
format_utils.cli_format_status_handler(response)
|
||||||
|
)
|
||||||
|
@ -11,24 +11,38 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from shipyard_client.cli.action import CliAction
|
from shipyard_client.cli.action import CliAction
|
||||||
from shipyard_client.cli.output_formatting import output_formatting
|
|
||||||
|
|
||||||
|
|
||||||
class Control(CliAction):
|
class Control(CliAction):
|
||||||
"""Action to Pause Process"""
|
"""Action to Pause Process"""
|
||||||
|
|
||||||
def __init__(self, ctx, control_verb, action_id):
|
def __init__(self, ctx, control_verb, action_id):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.action_id = action_id
|
self.action_id = action_id
|
||||||
self.control_verb = control_verb
|
self.control_verb = control_verb
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
self.logger.debug("ControlPause action initialized")
|
self.logger.debug("ControlPause action initialized")
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client post_control_action.")
|
self.logger.debug("Calling API Client post_control_action.")
|
||||||
self.resp_txt = output_formatting(self.output_format,
|
return self.get_api_client().post_control_action(
|
||||||
self.api_client.post_control_action(
|
action_id=self.action_id,
|
||||||
action_id=self.action_id,
|
control_verb=self.control_verb
|
||||||
control_verb=self.control_verb))
|
)
|
||||||
|
|
||||||
|
# Handle 400, 409 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [400, 409]
|
||||||
|
|
||||||
|
# Handle 202 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [202]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 202 responses
|
||||||
|
"""
|
||||||
|
return "{} successfully submitted for action {}".format(
|
||||||
|
self.control_verb, self.action_id)
|
||||||
|
@ -9,37 +9,51 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from shipyard_client.cli.action import CliAction
|
from shipyard_client.cli.action import CliAction
|
||||||
from shipyard_client.cli.output_formatting import output_formatting
|
from shipyard_client.cli import cli_format_common
|
||||||
|
from shipyard_client.cli import format_utils
|
||||||
|
|
||||||
|
|
||||||
class CreateAction(CliAction):
|
class CreateAction(CliAction):
|
||||||
"""Action to Create Action"""
|
"""Action to Create Action"""
|
||||||
|
|
||||||
def __init__(self, ctx, action_name, param):
|
def __init__(self, ctx, action_name, param):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug("CreateAction action initialized with action command"
|
self.logger.debug("CreateAction action initialized with action command"
|
||||||
"%s and parameters %s", action_name, param)
|
"%s and parameters %s", action_name, param)
|
||||||
self.action_name = action_name
|
self.action_name = action_name
|
||||||
self.param = param
|
self.param = param
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Returns the response from API Client"""
|
||||||
self.logger.debug("Calling API Client post_actions.")
|
self.logger.debug("Calling API Client post_actions.")
|
||||||
self.resp_txt = output_formatting(self.output_format,
|
return self.get_api_client().post_actions(name=self.action_name,
|
||||||
self.api_client.post_actions(
|
parameters=self.param)
|
||||||
name=self.action_name,
|
|
||||||
parameters=self.param))
|
# Handle 400, 409 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [400, 409]
|
||||||
|
|
||||||
|
# Handle 201 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [201]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 201 responses
|
||||||
|
"""
|
||||||
|
resp_j = response.json()
|
||||||
|
action_list = [resp_j] if resp_j else []
|
||||||
|
return cli_format_common.gen_action_table(action_list)
|
||||||
|
|
||||||
|
|
||||||
class CreateConfigdocs(CliAction):
|
class CreateConfigdocs(CliAction):
|
||||||
"""Action to Create Configdocs"""
|
"""Action to Create Configdocs"""
|
||||||
|
|
||||||
def __init__(self, ctx, collection, buffer, data):
|
def __init__(self, ctx, collection, buffer, data):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug("CreateConfigdocs action initialized with" +
|
self.logger.debug("CreateConfigdocs action initialized with" +
|
||||||
" collection=%s,buffer=%s and data=%s", collection,
|
" collection=%s,buffer=%s and data=%s", collection,
|
||||||
@ -47,13 +61,30 @@ class CreateConfigdocs(CliAction):
|
|||||||
self.collection = collection
|
self.collection = collection
|
||||||
self.buffer = buffer
|
self.buffer = buffer
|
||||||
self.data = data
|
self.data = data
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client post_configdocs.")
|
self.logger.debug("Calling API Client post_configdocs.")
|
||||||
self.resp_txt = output_formatting(self.output_format,
|
return self.get_api_client().post_configdocs(
|
||||||
self.api_client.post_configdocs(
|
collection_id=self.collection,
|
||||||
collection_id=self.collection,
|
buffer_mode=self.buffer,
|
||||||
buffer_mode=self.buffer,
|
document_data=self.data
|
||||||
document_data=self.data))
|
)
|
||||||
|
|
||||||
|
# Handle 409 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [409]
|
||||||
|
|
||||||
|
# Handle 201 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [201]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 201 responses
|
||||||
|
"""
|
||||||
|
outfmt_string = "Configuration documents added.\n{}"
|
||||||
|
return outfmt_string.format(
|
||||||
|
format_utils.cli_format_status_handler(response)
|
||||||
|
)
|
||||||
|
@ -11,87 +11,158 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from shipyard_client.cli.action import CliAction
|
from shipyard_client.cli.action import CliAction
|
||||||
from shipyard_client.cli.output_formatting import output_formatting
|
from shipyard_client.cli import cli_format_common
|
||||||
|
|
||||||
|
|
||||||
class DescribeAction(CliAction):
|
class DescribeAction(CliAction):
|
||||||
"""Action to Describe Action"""
|
"""Action to Describe Action"""
|
||||||
|
|
||||||
def __init__(self, ctx, action_id):
|
def __init__(self, ctx, action_id):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"DescribeAction action initialized with action_id=%s", action_id)
|
"DescribeAction action initialized with action_id=%s", action_id)
|
||||||
self.action_id = action_id
|
self.action_id = action_id
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client get_action_detail.")
|
self.logger.debug("Calling API Client get_action_detail.")
|
||||||
self.resp_txt = output_formatting(
|
return self.get_api_client().get_action_detail(
|
||||||
self.output_format,
|
action_id=self.action_id)
|
||||||
self.api_client.get_action_detail(action_id=self.action_id))
|
|
||||||
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [404]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
resp_j = response.json()
|
||||||
|
# Assemble the sections of the action details
|
||||||
|
return '{}\n\n{}\n\n{}\n\n{}\n'.format(
|
||||||
|
cli_format_common.gen_action_details(resp_j),
|
||||||
|
cli_format_common.gen_action_steps(resp_j.get('steps'),
|
||||||
|
resp_j.get('id')),
|
||||||
|
cli_format_common.gen_action_commands(resp_j.get('commands')),
|
||||||
|
cli_format_common.gen_action_validations(
|
||||||
|
resp_j.get('validations')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DescribeStep(CliAction):
|
class DescribeStep(CliAction):
|
||||||
"""Action to Describe Step"""
|
"""Action to Describe Step"""
|
||||||
|
|
||||||
def __init__(self, ctx, action_id, step_id):
|
def __init__(self, ctx, action_id, step_id):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"DescribeStep action initialized with action_id=%s and step_id=%s",
|
"DescribeStep action initialized with action_id=%s and step_id=%s",
|
||||||
action_id, step_id)
|
action_id, step_id)
|
||||||
self.action_id = action_id
|
self.action_id = action_id
|
||||||
self.step_id = step_id
|
self.step_id = step_id
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client get_step_detail.")
|
self.logger.debug("Calling API Client get_step_detail.")
|
||||||
self.resp_txt = output_formatting(self.output_format,
|
return self.get_api_client().get_step_detail(action_id=self.action_id,
|
||||||
self.api_client.get_step_detail(
|
step_id=self.step_id)
|
||||||
action_id=self.action_id,
|
|
||||||
step_id=self.step_id))
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [404]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
resp_j = response.json()
|
||||||
|
return cli_format_common.gen_action_step_details(resp_j,
|
||||||
|
self.action_id)
|
||||||
|
|
||||||
|
|
||||||
class DescribeValidation(CliAction):
|
class DescribeValidation(CliAction):
|
||||||
"""Action to Describe Validation"""
|
"""Action to Describe Validation"""
|
||||||
|
|
||||||
def __init__(self, ctx, action_id, validation_id):
|
def __init__(self, ctx, action_id, validation_id):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
'DescribeValidation action initialized with action_id=%s'
|
'DescribeValidation action initialized with action_id=%s'
|
||||||
'and validation_id=%s', action_id, validation_id)
|
'and validation_id=%s', action_id, validation_id)
|
||||||
self.validation_id = validation_id
|
self.validation_id = validation_id
|
||||||
self.action_id = action_id
|
self.action_id = action_id
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client get_validation_detail.")
|
self.logger.debug("Calling API Client get_validation_detail.")
|
||||||
self.resp_txt = output_formatting(
|
return self.get_api_client().get_validation_detail(
|
||||||
self.output_format,
|
action_id=self.action_id, validation_id=self.validation_id)
|
||||||
self.api_client.get_validation_detail(
|
|
||||||
action_id=self.action_id, validation_id=self.validation_id))
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [404]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
resp_j = response.json()
|
||||||
|
val_list = [resp_j] if resp_j else []
|
||||||
|
return cli_format_common.gen_action_validations(val_list)
|
||||||
|
|
||||||
|
|
||||||
class DescribeWorkflow(CliAction):
|
class DescribeWorkflow(CliAction):
|
||||||
"""Action to describe a workflow"""
|
"""Action to describe a workflow"""
|
||||||
|
|
||||||
def __init__(self, ctx, workflow_id):
|
def __init__(self, ctx, workflow_id):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"DescribeWorkflow action initialized with workflow_id=%s",
|
"DescribeWorkflow action initialized with workflow_id=%s",
|
||||||
workflow_id)
|
workflow_id)
|
||||||
self.workflow_id = workflow_id
|
self.workflow_id = workflow_id
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client get_action_detail.")
|
self.logger.debug("Calling API Client get_action_detail.")
|
||||||
self.resp_txt = output_formatting(
|
return self.get_api_client().get_dag_detail(
|
||||||
self.output_format,
|
workflow_id=self.workflow_id)
|
||||||
self.api_client.get_dag_detail(workflow_id=self.workflow_id))
|
|
||||||
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [404]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
resp_j = response.json()
|
||||||
|
# Assemble the workflow details
|
||||||
|
|
||||||
|
return '{}\n\n{}\n\nSubworkflows:\n{}\n'.format(
|
||||||
|
cli_format_common.gen_workflow_details(resp_j),
|
||||||
|
cli_format_common.gen_workflow_steps(resp_j.get('steps', [])),
|
||||||
|
cli_format_common.gen_sub_workflows(resp_j.get('sub_dags', []))
|
||||||
|
)
|
||||||
|
139
shipyard_client/cli/format_utils.py
Normal file
139
shipyard_client/cli/format_utils.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# 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.
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from prettytable import PrettyTable
|
||||||
|
from prettytable.prettytable import PLAIN_COLUMNS
|
||||||
|
|
||||||
|
|
||||||
|
def cli_format_error_handler(response):
|
||||||
|
"""Generic handler for standard Shipyard error responses
|
||||||
|
|
||||||
|
Method is intended for use when there is no special handling needed
|
||||||
|
for the response.
|
||||||
|
:param client_response: a requests response object assuming the
|
||||||
|
standard error format
|
||||||
|
:returns: a generically formatted error response formulated from the
|
||||||
|
client_repsonse. The response will be in the format:
|
||||||
|
|
||||||
|
Error: {{message}}
|
||||||
|
Reason: {{Reason}}
|
||||||
|
Additional: {{details message list messages}}
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
return cli_format_status_handler(response, is_error=True)
|
||||||
|
|
||||||
|
|
||||||
|
def cli_format_status_handler(response, is_error=False):
|
||||||
|
"""Handler for standard Shipyard status and status-based error responses
|
||||||
|
|
||||||
|
Method is intended for use when there is no special handling needed
|
||||||
|
for the response. If the response is empty, returns empty string
|
||||||
|
:param client_response: a requests response object assuming the
|
||||||
|
standard error format
|
||||||
|
:is_error: toggles the use of status or error verbiage
|
||||||
|
:returns: a generically formatted error response formulated from the
|
||||||
|
client_repsonse. The response will be in the format:
|
||||||
|
|
||||||
|
[Error|Status]: {{message}}
|
||||||
|
Reason: {{Reason}}
|
||||||
|
Additional: {{details message list messages}}
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
formatted = "Error: {}\nReason: {}" if is_error \
|
||||||
|
else "Status: {}\nReason: {}"
|
||||||
|
try:
|
||||||
|
if response.text:
|
||||||
|
resp_j = response.json()
|
||||||
|
resp = formatted.format(resp_j.get('message', 'Not specified'),
|
||||||
|
resp_j.get('reason', 'Not specified'))
|
||||||
|
if resp_j.get('details'):
|
||||||
|
for message in resp_j.get('details').get('messageList', []):
|
||||||
|
if message.get('error', False):
|
||||||
|
resp = resp + '\n- Error: {}'.format(
|
||||||
|
message.get('message'))
|
||||||
|
else:
|
||||||
|
resp = resp + '\n- Info: {}'.format(
|
||||||
|
message.get('message'))
|
||||||
|
return resp
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
except ValueError:
|
||||||
|
return "Error: Unable to decode response. Value: {}".format(
|
||||||
|
response.text)
|
||||||
|
|
||||||
|
|
||||||
|
def raw_format_response_handler(response):
|
||||||
|
"""Basic format handler to return raw response text"""
|
||||||
|
return response.text
|
||||||
|
|
||||||
|
|
||||||
|
def formatted_response_handler(response):
|
||||||
|
"""Base format handler for either json or yaml depending on call"""
|
||||||
|
call = response.headers['Content-Type']
|
||||||
|
if 'json' in call:
|
||||||
|
try:
|
||||||
|
return json.dumps(response.json(), sort_keys=True, indent=4)
|
||||||
|
except ValueError:
|
||||||
|
return (
|
||||||
|
"This is not json and could not be printed as such. \n" +
|
||||||
|
response.text
|
||||||
|
)
|
||||||
|
|
||||||
|
else: # all others should be yaml
|
||||||
|
try:
|
||||||
|
return (yaml.dump_all(
|
||||||
|
yaml.safe_load_all(response.content),
|
||||||
|
width=79,
|
||||||
|
indent=4,
|
||||||
|
default_flow_style=False))
|
||||||
|
except ValueError:
|
||||||
|
return (
|
||||||
|
"This is not yaml and could not be printed as such.\n" +
|
||||||
|
response.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def table_factory(field_names=None, rows=None, style=None):
|
||||||
|
"""Generate a table using prettytable
|
||||||
|
|
||||||
|
Factory method for a prettytable using the PLAIN_COLUMN style unless
|
||||||
|
ovrridden by the style parameter.
|
||||||
|
If a field_names list of strings is passed in, the column names
|
||||||
|
will be initialized.
|
||||||
|
If rows are supplied (list of lists), the will be added as rows in
|
||||||
|
order.
|
||||||
|
"""
|
||||||
|
p = PrettyTable()
|
||||||
|
if style is None:
|
||||||
|
p.set_style(PLAIN_COLUMNS)
|
||||||
|
else:
|
||||||
|
p.set_style(style)
|
||||||
|
if field_names:
|
||||||
|
p.field_names = field_names
|
||||||
|
else:
|
||||||
|
p.header = False
|
||||||
|
if rows:
|
||||||
|
for row in rows:
|
||||||
|
p.add_row(row)
|
||||||
|
# This alignment only works if columns and rows are set up.
|
||||||
|
p.align = 'l'
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def table_get_string(table, align='l'):
|
||||||
|
"""Wrapper to return a prettytable string with the supplied alignment"""
|
||||||
|
table.align = 'l'
|
||||||
|
return table.get_string()
|
@ -11,78 +11,133 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from shipyard_client.cli.action import CliAction
|
from shipyard_client.cli.action import CliAction
|
||||||
from shipyard_client.cli.output_formatting import output_formatting
|
from shipyard_client.cli import cli_format_common
|
||||||
|
from shipyard_client.cli import format_utils
|
||||||
|
|
||||||
|
|
||||||
class GetActions(CliAction):
|
class GetActions(CliAction):
|
||||||
"""Action to Get Actions"""
|
"""Action to Get Actions"""
|
||||||
|
|
||||||
def __init__(self, ctx):
|
def __init__(self, ctx):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug("GetActions action initialized.")
|
self.logger.debug("GetActions action initialized.")
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client get_actions.")
|
self.logger.debug("Calling API Client get_actions.")
|
||||||
self.resp_txt = output_formatting(self.output_format,
|
return self.get_api_client().get_actions()
|
||||||
self.api_client.get_actions())
|
|
||||||
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = []
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
resp_j = response.json()
|
||||||
|
return cli_format_common.gen_action_table(resp_j)
|
||||||
|
|
||||||
|
|
||||||
class GetConfigdocs(CliAction):
|
class GetConfigdocs(CliAction):
|
||||||
"""Action to Get Configdocs"""
|
"""Action to Get Configdocs"""
|
||||||
|
|
||||||
def __init__(self, ctx, collection, version):
|
def __init__(self, ctx, collection, version):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"GetConfigdocs action initialized with collection=%s and "
|
"GetConfigdocs action initialized with collection=%s and "
|
||||||
"version=%s" % (collection, version))
|
"version=%s" % (collection, version))
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
self.version = version
|
self.version = version
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client get_configdocs.")
|
self.logger.debug("Calling API Client get_configdocs.")
|
||||||
self.resp_txt = output_formatting(self.output_format,
|
return self.get_api_client().get_configdocs(
|
||||||
self.api_client.get_configdocs(
|
collection_id=self.collection, version=self.version)
|
||||||
collection_id=self.collection,
|
|
||||||
version=self.version))
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [404]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
Effectively passes through the YAML received.
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a CLI appropriate response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
return format_utils.raw_format_response_handler(response)
|
||||||
|
|
||||||
|
|
||||||
class GetRenderedConfigdocs(CliAction):
|
class GetRenderedConfigdocs(CliAction):
|
||||||
"""Action to Get Rendered Configdocs"""
|
"""Action to Get Rendered Configdocs"""
|
||||||
|
|
||||||
def __init__(self, ctx, version):
|
def __init__(self, ctx, version):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug("GetRenderedConfigdocs action initialized")
|
self.logger.debug("GetRenderedConfigdocs action initialized")
|
||||||
self.version = version
|
self.version = version
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client get_rendereddocs.")
|
self.logger.debug("Calling API Client get_rendereddocs.")
|
||||||
self.resp_txt = output_formatting(
|
return self.get_api_client().get_rendereddocs(version=self.version)
|
||||||
self.output_format,
|
|
||||||
self.api_client.get_rendereddocs(version=self.version))
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [404]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
Effectively passes through the YAML received.
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a CLI appropriate response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
return format_utils.raw_format_response_handler(response)
|
||||||
|
|
||||||
|
|
||||||
class GetWorkflows(CliAction):
|
class GetWorkflows(CliAction):
|
||||||
"""Action to get workflows"""
|
"""Action to get workflows"""
|
||||||
|
|
||||||
def __init__(self, ctx, since=None):
|
def __init__(self, ctx, since=None):
|
||||||
"""Initializes api_client, sets parameters, and sets output_format"""
|
"""Sets parameters."""
|
||||||
super().__init__(ctx)
|
super().__init__(ctx)
|
||||||
self.logger.debug("GetWorkflows action initialized.")
|
self.logger.debug("GetWorkflows action initialized.")
|
||||||
self.since = since
|
self.since = since
|
||||||
self.output_format = ctx.obj['FORMAT']
|
|
||||||
|
|
||||||
def invoke(self):
|
def invoke(self):
|
||||||
"""Calls API Client and formats response from API Client"""
|
"""Calls API Client and formats response from API Client"""
|
||||||
self.logger.debug("Calling API Client get_actions.")
|
self.logger.debug("Calling API Client get_actions.")
|
||||||
self.resp_txt = output_formatting(
|
return self.get_api_client().get_workflows(self.since)
|
||||||
self.output_format,
|
|
||||||
self.api_client.get_workflows(self.since))
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [404]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
resp_j = response.json()
|
||||||
|
wf_list = resp_j if resp_j else []
|
||||||
|
return cli_format_common.gen_workflow_table(wf_list)
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
"""CLI value checks invoked from commands"""
|
||||||
import arrow
|
import arrow
|
||||||
from arrow.parser import ParserError
|
from arrow.parser import ParserError
|
||||||
|
|
||||||
@ -59,25 +60,6 @@ def check_workflow_id(ctx, workflow_id):
|
|||||||
'YYYY-MM-DDTHH:mm:ss.SSSSSS')
|
'YYYY-MM-DDTHH:mm:ss.SSSSSS')
|
||||||
|
|
||||||
|
|
||||||
def validate_auth_vars(ctx, auth_vars):
|
|
||||||
"""Checks that the required authurization varible have been entered"""
|
|
||||||
|
|
||||||
required_auth_vars = ['auth_url']
|
|
||||||
err_txt = ""
|
|
||||||
for var in required_auth_vars:
|
|
||||||
if auth_vars[var] is None:
|
|
||||||
err_txt += (
|
|
||||||
'Missing the required authorization variable: '
|
|
||||||
'--os_{}\n'.format(var))
|
|
||||||
if err_txt != "":
|
|
||||||
err_txt += ('\nMissing the following additional authorization '
|
|
||||||
'options: ')
|
|
||||||
for var in auth_vars:
|
|
||||||
if auth_vars[var] is None and var not in required_auth_vars:
|
|
||||||
err_txt += '\n--os_{}'.format(var)
|
|
||||||
ctx.fail(err_txt)
|
|
||||||
|
|
||||||
|
|
||||||
def check_reformat_parameter(ctx, param):
|
def check_reformat_parameter(ctx, param):
|
||||||
"""Checks for <name>=<value> format"""
|
"""Checks for <name>=<value> format"""
|
||||||
param_dictionary = {}
|
param_dictionary = {}
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
# 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-1.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.
|
|
||||||
|
|
||||||
import json
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
def output_formatting(output_format, response):
|
|
||||||
"""formats response from api_client"""
|
|
||||||
if output_format == 'raw':
|
|
||||||
return response.text
|
|
||||||
else: # assume formatted
|
|
||||||
return formatted(response)
|
|
||||||
|
|
||||||
|
|
||||||
def formatted(response):
|
|
||||||
"""Formats either json or yaml depending on call"""
|
|
||||||
call = response.headers['Content-Type']
|
|
||||||
if 'json' in call:
|
|
||||||
try:
|
|
||||||
input = response.json()
|
|
||||||
return (json.dumps(input, sort_keys=True, indent=4))
|
|
||||||
except ValueError:
|
|
||||||
return ("This is not json and could not be printed as such. \n " +
|
|
||||||
response.text)
|
|
||||||
|
|
||||||
else: # all others should be yaml
|
|
||||||
try:
|
|
||||||
return (yaml.dump_all(
|
|
||||||
yaml.safe_load_all(response.content),
|
|
||||||
width=79,
|
|
||||||
indent=4,
|
|
||||||
default_flow_style=False))
|
|
||||||
except ValueError:
|
|
||||||
return ("This is not yaml and could not be printed as such.\n " +
|
|
||||||
response.text)
|
|
@ -1,4 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -11,192 +11,204 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import mock
|
|
||||||
import json
|
import json
|
||||||
|
import mock
|
||||||
|
|
||||||
from shipyard_client.api_client.shipyard_api_client import ShipyardClient
|
|
||||||
from shipyard_client.api_client.base_client import BaseClient
|
from shipyard_client.api_client.base_client import BaseClient
|
||||||
|
from shipyard_client.api_client.shipyard_api_client import ShipyardClient
|
||||||
|
from shipyard_client.api_client.shipyardclient_context import \
|
||||||
|
ShipyardClientContext
|
||||||
|
|
||||||
|
|
||||||
class TemporaryContext:
|
def replace_get_endpoint(self):
|
||||||
def __init__(self):
|
"""Fake get endpoint method to isolate testing"""
|
||||||
self.debug = True
|
return 'http://shipyard/api/v1.0'
|
||||||
self.keystone_Auth = {}
|
|
||||||
self.token = 'abcdefgh'
|
|
||||||
self.service_type = 'http://shipyard'
|
|
||||||
self.shipyard_endpoint = 'http://shipyard/api/v1.0'
|
|
||||||
self.context_marker = '123456'
|
|
||||||
|
|
||||||
|
|
||||||
def replace_post_rep(self, url, query_params={}, data={}, content_type=''):
|
def replace_post_rep(self, url, query_params={}, data={}, content_type=''):
|
||||||
"""
|
"""Replaces call to shipyard client
|
||||||
replaces call to shipyard client
|
|
||||||
:returns: dict with url and parameters
|
:returns: dict with url and parameters
|
||||||
"""
|
"""
|
||||||
return {'url': url, 'params': query_params, 'data': data}
|
return {'url': url, 'params': query_params, 'data': data}
|
||||||
|
|
||||||
|
|
||||||
def replace_get_resp(self, url, query_params={}, json=False):
|
def replace_get_resp(self, url, query_params={}, json=False):
|
||||||
"""
|
"""Replaces call to shipyard client.
|
||||||
replaces call to shipyard client
|
|
||||||
:returns: dict with url and parameters
|
:returns: dict with url and parameters
|
||||||
"""
|
"""
|
||||||
return {'url': url, 'params': query_params}
|
return {'url': url, 'params': query_params}
|
||||||
|
|
||||||
|
|
||||||
def replace_base_constructor(self, context):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_api_client():
|
def get_api_client():
|
||||||
"""
|
"""
|
||||||
get a instance of shipyard client
|
get a instance of shipyard client
|
||||||
:returns: shipyard client with no context object
|
:returns: shipyard client with no context object
|
||||||
"""
|
"""
|
||||||
context = TemporaryContext()
|
keystone_auth = {
|
||||||
|
'project_domain_name': 'projDomainTest',
|
||||||
|
'user_domain_name': 'userDomainTest',
|
||||||
|
'project_name': 'projectTest',
|
||||||
|
'username': 'usernameTest',
|
||||||
|
'password': 'passwordTest',
|
||||||
|
'auth_url': 'urlTest'
|
||||||
|
},
|
||||||
|
|
||||||
|
context = ShipyardClientContext(
|
||||||
|
debug=True,
|
||||||
|
keystone_auth=keystone_auth,
|
||||||
|
context_marker='88888888-4444-4444-4444-121212121212'
|
||||||
|
)
|
||||||
return ShipyardClient(context)
|
return ShipyardClient(context)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_post_config_docs(*args):
|
def test_post_config_docs(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
buffermode = 'rejectoncontents'
|
buffermode = 'rejectoncontents'
|
||||||
result = shipyard_client.post_configdocs('ABC', buffer_mode=buffermode)
|
result = shipyard_client.post_configdocs('ABC', buffer_mode=buffermode)
|
||||||
params = result['params']
|
params = result['params']
|
||||||
assert result['url'] == '{}/configdocs/ABC'.format(
|
assert result['url'] == '{}/configdocs/ABC'.format(
|
||||||
shipyard_client.shipyard_url)
|
shipyard_client.get_endpoint())
|
||||||
assert params['buffermode'] == buffermode
|
assert params['buffermode'] == buffermode
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_get_config_docs(*args):
|
def test_get_config_docs(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
version = 'buffer'
|
version = 'buffer'
|
||||||
result = shipyard_client.get_configdocs('ABC', version=version)
|
result = shipyard_client.get_configdocs('ABC', version=version)
|
||||||
params = result['params']
|
params = result['params']
|
||||||
assert result['url'] == '{}/configdocs/ABC'.format(
|
assert result['url'] == '{}/configdocs/ABC'.format(
|
||||||
shipyard_client.shipyard_url)
|
shipyard_client.get_endpoint())
|
||||||
assert params['version'] == version
|
assert params['version'] == version
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_rendered_config_docs(*args):
|
def test_rendered_config_docs(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
version = 'buffer'
|
version = 'buffer'
|
||||||
result = shipyard_client.get_rendereddocs(version=version)
|
result = shipyard_client.get_rendereddocs(version=version)
|
||||||
params = result['params']
|
params = result['params']
|
||||||
assert result['url'] == '{}/renderedconfigdocs'.format(
|
assert result['url'] == '{}/renderedconfigdocs'.format(
|
||||||
shipyard_client.shipyard_url)
|
shipyard_client.get_endpoint())
|
||||||
assert params['version'] == version
|
assert params['version'] == version
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_commit_configs(*args):
|
def test_commit_configs(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
force_mode = True
|
force_mode = True
|
||||||
result = shipyard_client.commit_configdocs(force_mode)
|
result = shipyard_client.commit_configdocs(force_mode)
|
||||||
params = result['params']
|
params = result['params']
|
||||||
assert result['url'] == '{}/commitconfigdocs'.format(
|
assert result['url'] == '{}/commitconfigdocs'.format(
|
||||||
shipyard_client.shipyard_url)
|
shipyard_client.get_endpoint())
|
||||||
assert params['force'] == force_mode
|
assert params['force'] == force_mode
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_get_actions(*args):
|
def test_get_actions(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
result = shipyard_client.get_actions()
|
result = shipyard_client.get_actions()
|
||||||
assert result['url'] == '{}/actions'.format(shipyard_client.shipyard_url)
|
assert result['url'] == '{}/actions'.format(
|
||||||
|
shipyard_client.get_endpoint()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_post_actions(*args):
|
def test_post_actions(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
name = 'good action'
|
name = 'good action'
|
||||||
parameters = {'hello': 'world'}
|
parameters = {'hello': 'world'}
|
||||||
result = shipyard_client.post_actions(name, parameters)
|
result = shipyard_client.post_actions(name, parameters)
|
||||||
data = json.loads(result['data'])
|
data = json.loads(result['data'])
|
||||||
assert result['url'] == '{}/actions'.format(shipyard_client.shipyard_url)
|
assert result['url'] == '{}/actions'.format(
|
||||||
|
shipyard_client.get_endpoint()
|
||||||
|
)
|
||||||
assert data['name'] == name
|
assert data['name'] == name
|
||||||
assert data['parameters']['hello'] == 'world'
|
assert data['parameters']['hello'] == 'world'
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_action_details(*args):
|
def test_action_details(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
action_id = 'GoodAction'
|
action_id = 'GoodAction'
|
||||||
result = shipyard_client.get_action_detail(action_id)
|
result = shipyard_client.get_action_detail(action_id)
|
||||||
assert result['url'] == '{}/actions/{}'.format(
|
assert result['url'] == '{}/actions/{}'.format(
|
||||||
shipyard_client.shipyard_url, action_id)
|
shipyard_client.get_endpoint(), action_id)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_get_val_details(*args):
|
def test_get_val_details(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
action_id = 'GoodAction'
|
action_id = 'GoodAction'
|
||||||
validation_id = 'Validation'
|
validation_id = 'Validation'
|
||||||
result = shipyard_client.get_validation_detail(action_id, validation_id)
|
result = shipyard_client.get_validation_detail(action_id, validation_id)
|
||||||
assert result['url'] == '{}/actions/{}/validationdetails/{}'.format(
|
assert result['url'] == '{}/actions/{}/validationdetails/{}'.format(
|
||||||
shipyard_client.shipyard_url, action_id, validation_id)
|
shipyard_client.get_endpoint(), action_id, validation_id)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_get_step_details(*args):
|
def test_get_step_details(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
action_id = 'GoodAction'
|
action_id = 'GoodAction'
|
||||||
step_id = 'TestStep'
|
step_id = 'TestStep'
|
||||||
result = shipyard_client.get_step_detail(action_id, step_id)
|
result = shipyard_client.get_step_detail(action_id, step_id)
|
||||||
assert result['url'] == '{}/actions/{}/steps/{}'.format(
|
assert result['url'] == '{}/actions/{}/steps/{}'.format(
|
||||||
shipyard_client.shipyard_url, action_id, step_id)
|
shipyard_client.get_endpoint(), action_id, step_id)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_post_control(*args):
|
def test_post_control(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
action_id = 'GoodAction'
|
action_id = 'GoodAction'
|
||||||
control_verb = 'Control'
|
control_verb = 'Control'
|
||||||
result = shipyard_client.post_control_action(action_id, control_verb)
|
result = shipyard_client.post_control_action(action_id, control_verb)
|
||||||
assert result['url'] == '{}/actions/{}/control/{}'.format(
|
assert result['url'] == '{}/actions/{}/control/{}'.format(
|
||||||
shipyard_client.shipyard_url, action_id, control_verb)
|
shipyard_client.get_endpoint(), action_id, control_verb)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_get_workflows(*args):
|
def test_get_workflows(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
since_mode = 'TestSince'
|
since_mode = 'TestSince'
|
||||||
result = shipyard_client.get_workflows(since_mode)
|
result = shipyard_client.get_workflows(since_mode)
|
||||||
assert result['url'] == '{}/workflows'.format(shipyard_client.shipyard_url,
|
assert result['url'] == '{}/workflows'.format(
|
||||||
since_mode)
|
shipyard_client.get_endpoint())
|
||||||
|
|
||||||
|
params = result['params']
|
||||||
|
assert 'since' in params
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', replace_get_endpoint)
|
||||||
def test_get_dag_details(*args):
|
def test_get_dag_details(*args):
|
||||||
shipyard_client = get_api_client()
|
shipyard_client = get_api_client()
|
||||||
workflow_id = 'TestWorkflow'
|
workflow_id = 'TestWorkflow'
|
||||||
result = shipyard_client.get_dag_detail(workflow_id)
|
result = shipyard_client.get_dag_detail(workflow_id)
|
||||||
assert result['url'] == '{}/workflows/{}'.format(
|
assert result['url'] == '{}/workflows/{}'.format(
|
||||||
shipyard_client.shipyard_url, workflow_id)
|
shipyard_client.get_endpoint(), workflow_id)
|
||||||
|
@ -11,56 +11,65 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from shipyard_client.cli.commit.actions import CommitConfigdocs
|
import responses
|
||||||
|
|
||||||
from shipyard_client.api_client.base_client import BaseClient
|
from shipyard_client.api_client.base_client import BaseClient
|
||||||
from shipyard_client.tests.unit.cli.replace_api_client import \
|
from shipyard_client.cli.commit.actions import CommitConfigdocs
|
||||||
replace_base_constructor, replace_post_rep, replace_get_resp, \
|
from shipyard_client.tests.unit.cli import stubs
|
||||||
replace_output_formatting
|
|
||||||
from shipyard_client.tests.unit.cli.utils import temporary_context
|
|
||||||
from shipyard_client.api_client.shipyardclient_context import \
|
|
||||||
ShipyardClientContext
|
|
||||||
|
|
||||||
auth_vars = {
|
|
||||||
'project_domain_name': 'projDomainTest',
|
|
||||||
'user_domain_name': 'userDomainTest',
|
|
||||||
'project_name': 'projectTest',
|
|
||||||
'username': 'usernameTest',
|
|
||||||
'password': 'passwordTest',
|
|
||||||
'auth_url': 'urlTest'
|
|
||||||
}
|
|
||||||
|
|
||||||
api_parameters = {
|
|
||||||
'auth_vars': auth_vars,
|
|
||||||
'context_marker': 'UUID',
|
|
||||||
'debug': False
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MockCTX():
|
@responses.activate
|
||||||
pass
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_commit_configdocs(*args):
|
||||||
|
responses.add(responses.POST,
|
||||||
|
'http://shiptest/commitconfigdocs?force=false',
|
||||||
|
body=None,
|
||||||
|
status=200)
|
||||||
|
response = CommitConfigdocs(stubs.StubCliContext(),
|
||||||
|
False).invoke_and_return_resp()
|
||||||
|
assert response == 'Configuration documents committed.\n'
|
||||||
|
|
||||||
|
|
||||||
ctx = MockCTX()
|
@responses.activate
|
||||||
ctx.obj = {}
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
ctx.obj['API_PARAMETERS'] = api_parameters
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
ctx.obj['FORMAT'] = 'format'
|
def test_commit_configdocs_409(*args):
|
||||||
|
api_resp = stubs.gen_err_resp(message="Conflicts message",
|
||||||
|
sub_message='Another bucket message',
|
||||||
|
sub_error_count=1,
|
||||||
|
sub_info_count=0,
|
||||||
|
reason='Conflicts reason',
|
||||||
|
code=409)
|
||||||
|
responses.add(responses.POST,
|
||||||
|
'http://shiptest/commitconfigdocs?force=false',
|
||||||
|
body=api_resp,
|
||||||
|
status=409)
|
||||||
|
response = CommitConfigdocs(stubs.StubCliContext(),
|
||||||
|
False).invoke_and_return_resp()
|
||||||
|
assert 'Error: Conflicts message' in response
|
||||||
|
assert 'Configuration documents committed' not in response
|
||||||
|
assert 'Reason: Conflicts reason' in response
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
@responses.activate
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
def test_commit_configdocs_forced(*args):
|
||||||
@mock.patch(
|
api_resp = stubs.gen_err_resp(message="Conflicts message forced",
|
||||||
'shipyard_client.cli.commit.actions.output_formatting',
|
sub_message='Another bucket message',
|
||||||
side_effect=replace_output_formatting)
|
sub_error_count=1,
|
||||||
def test_CommitConfigdocs(*args):
|
sub_info_count=0,
|
||||||
response = CommitConfigdocs(ctx, True).invoke_and_return_resp()
|
reason='Conflicts reason',
|
||||||
# test correct function was called
|
code=200)
|
||||||
url = response.get('url')
|
responses.add(responses.POST,
|
||||||
assert 'commitconfigdocs' in url
|
'http://shiptest/commitconfigdocs?force=true',
|
||||||
# test function was called with correct parameters
|
body=api_resp,
|
||||||
params = response.get('params')
|
status=200)
|
||||||
assert params.get('force') is True
|
response = CommitConfigdocs(stubs.StubCliContext(),
|
||||||
|
True).invoke_and_return_resp()
|
||||||
|
assert 'Status: Conflicts message forced' in response
|
||||||
|
assert 'Configuration documents committed' in response
|
||||||
|
assert 'Reason: Conflicts reason' in response
|
||||||
|
@ -11,59 +11,109 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from shipyard_client.cli.control.actions import Control
|
import responses
|
||||||
|
|
||||||
from shipyard_client.api_client.base_client import BaseClient
|
from shipyard_client.api_client.base_client import BaseClient
|
||||||
from shipyard_client.tests.unit.cli.replace_api_client import \
|
from shipyard_client.cli.control.actions import Control
|
||||||
replace_base_constructor, replace_post_rep, replace_get_resp, \
|
from shipyard_client.tests.unit.cli import stubs
|
||||||
replace_output_formatting
|
|
||||||
from shipyard_client.tests.unit.cli.utils import temporary_context
|
|
||||||
from shipyard_client.api_client.shipyardclient_context import \
|
|
||||||
ShipyardClientContext
|
|
||||||
|
|
||||||
auth_vars = {
|
|
||||||
'project_domain_name': 'projDomainTest',
|
|
||||||
'user_domain_name': 'userDomainTest',
|
|
||||||
'project_name': 'projectTest',
|
|
||||||
'username': 'usernameTest',
|
|
||||||
'password': 'passwordTest',
|
|
||||||
'auth_url': 'urlTest'
|
|
||||||
}
|
|
||||||
|
|
||||||
api_parameters = {
|
|
||||||
'auth_vars': auth_vars,
|
|
||||||
'context_marker': 'UUID',
|
|
||||||
'debug': False
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MockCTX():
|
@responses.activate
|
||||||
pass
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
|
||||||
ctx = MockCTX()
|
|
||||||
ctx.obj = {}
|
|
||||||
ctx.obj['API_PARAMETERS'] = api_parameters
|
|
||||||
ctx.obj['FORMAT'] = 'format'
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
|
||||||
@mock.patch(
|
|
||||||
'shipyard_client.cli.control.actions.output_formatting',
|
|
||||||
side_effect=replace_output_formatting)
|
|
||||||
def test_Control(*args):
|
def test_Control(*args):
|
||||||
|
responses.add(
|
||||||
|
responses.POST,
|
||||||
|
'http://shiptest/actions/01BTG32JW87G0YKA1K29TKNAFX/control/pause',
|
||||||
|
body=None,
|
||||||
|
status=202
|
||||||
|
)
|
||||||
control_verb = 'pause'
|
control_verb = 'pause'
|
||||||
id = '01BTG32JW87G0YKA1K29TKNAFX'
|
id = '01BTG32JW87G0YKA1K29TKNAFX'
|
||||||
response = Control(ctx, control_verb, id).invoke_and_return_resp()
|
response = Control(stubs.StubCliContext(),
|
||||||
|
control_verb,
|
||||||
|
id).invoke_and_return_resp()
|
||||||
# test correct function was called
|
# test correct function was called
|
||||||
url = response.get('url')
|
assert response == ('pause successfully submitted for action'
|
||||||
assert 'control' in url
|
' 01BTG32JW87G0YKA1K29TKNAFX')
|
||||||
|
|
||||||
# test function was called with correct parameters
|
|
||||||
assert control_verb in url
|
@responses.activate
|
||||||
assert id in url
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_control_unpause(*args):
|
||||||
|
responses.add(
|
||||||
|
responses.POST,
|
||||||
|
'http://shiptest/actions/01BTG32JW87G0YKA1K29TKNAFX/control/unpause',
|
||||||
|
body=None,
|
||||||
|
status=202
|
||||||
|
)
|
||||||
|
control_verb = 'unpause'
|
||||||
|
id = '01BTG32JW87G0YKA1K29TKNAFX'
|
||||||
|
response = Control(stubs.StubCliContext(),
|
||||||
|
control_verb,
|
||||||
|
id).invoke_and_return_resp()
|
||||||
|
# test correct function was called
|
||||||
|
assert response == ('unpause successfully submitted for action'
|
||||||
|
' 01BTG32JW87G0YKA1K29TKNAFX')
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_control_stop(*args):
|
||||||
|
responses.add(
|
||||||
|
responses.POST,
|
||||||
|
'http://shiptest/actions/01BTG32JW87G0YKA1K29TKNAFX/control/stop',
|
||||||
|
body=None,
|
||||||
|
status=202
|
||||||
|
)
|
||||||
|
control_verb = 'stop'
|
||||||
|
id = '01BTG32JW87G0YKA1K29TKNAFX'
|
||||||
|
response = Control(stubs.StubCliContext(),
|
||||||
|
control_verb,
|
||||||
|
id).invoke_and_return_resp()
|
||||||
|
# test correct function was called
|
||||||
|
assert response == ('stop successfully submitted for action'
|
||||||
|
' 01BTG32JW87G0YKA1K29TKNAFX')
|
||||||
|
|
||||||
|
|
||||||
|
resp_body = """
|
||||||
|
{
|
||||||
|
"message": "Unable to pause action",
|
||||||
|
"details": {
|
||||||
|
"messageList": [
|
||||||
|
{
|
||||||
|
"message": "Conflicting things",
|
||||||
|
"error": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message": "Try soup",
|
||||||
|
"error": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"reason": "Conflicts"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_control_409(*args):
|
||||||
|
responses.add(
|
||||||
|
responses.POST,
|
||||||
|
'http://shiptest/actions/01BTG32JW87G0YKA1K29TKNAFX/control/pause',
|
||||||
|
body=resp_body,
|
||||||
|
status=409
|
||||||
|
)
|
||||||
|
control_verb = 'pause'
|
||||||
|
id = '01BTG32JW87G0YKA1K29TKNAFX'
|
||||||
|
response = Control(stubs.StubCliContext(),
|
||||||
|
control_verb,
|
||||||
|
id).invoke_and_return_resp()
|
||||||
|
# test correct function was called
|
||||||
|
assert 'Unable to pause action' in response
|
||||||
|
@ -15,81 +15,156 @@
|
|||||||
import mock
|
import mock
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from shipyard_client.cli.create.actions import CreateAction, CreateConfigdocs
|
import responses
|
||||||
|
|
||||||
from shipyard_client.api_client.base_client import BaseClient
|
from shipyard_client.api_client.base_client import BaseClient
|
||||||
from shipyard_client.tests.unit.cli.replace_api_client import \
|
from shipyard_client.cli.create.actions import CreateAction
|
||||||
replace_base_constructor, replace_post_rep, replace_get_resp, \
|
from shipyard_client.cli.create.actions import CreateConfigdocs
|
||||||
replace_output_formatting
|
from shipyard_client.tests.unit.cli import stubs
|
||||||
from shipyard_client.tests.unit.cli.utils import temporary_context
|
|
||||||
from shipyard_client.api_client.shipyardclient_context import \
|
|
||||||
ShipyardClientContext
|
|
||||||
|
|
||||||
auth_vars = {
|
resp_body = """
|
||||||
'project_domain_name': 'projDomainTest',
|
{
|
||||||
'user_domain_name': 'userDomainTest',
|
"dag_status": "SCHEDULED",
|
||||||
'project_name': 'projectTest',
|
"parameters": {},
|
||||||
'username': 'usernameTest',
|
"dag_execution_date": "2017-09-24T19:05:49",
|
||||||
'password': 'passwordTest',
|
"id": "01BTTMFVDKZFRJM80FGD7J1AKN",
|
||||||
'auth_url': 'urlTest'
|
"dag_id": "deploy_site",
|
||||||
}
|
"name": "deploy_site",
|
||||||
|
"user": "shipyard",
|
||||||
api_parameters = {
|
"context_marker": "629f2ea2-c59d-46b9-8641-7367a91a7016",
|
||||||
'auth_vars': auth_vars,
|
"timestamp": "2017-09-24 19:05:43.603591"
|
||||||
'context_marker': 'UUID',
|
|
||||||
'debug': False
|
|
||||||
}
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class MockCTX():
|
@responses.activate
|
||||||
pass
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_create_action(*args):
|
||||||
|
responses.add(responses.POST,
|
||||||
|
'http://shiptest/actions',
|
||||||
|
body=resp_body,
|
||||||
|
status=201)
|
||||||
|
response = CreateAction(stubs.StubCliContext(),
|
||||||
|
action_name='deploy_site',
|
||||||
|
param=None).invoke_and_return_resp()
|
||||||
|
assert 'Name' in response
|
||||||
|
assert 'Action' in response
|
||||||
|
assert 'Lifecycle' in response
|
||||||
|
assert 'action/01BTTMFVDKZFRJM80FGD7J1AKN' in response
|
||||||
|
assert 'Error:' not in response
|
||||||
|
|
||||||
|
|
||||||
ctx = MockCTX()
|
@responses.activate
|
||||||
ctx.obj = {}
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
ctx.obj['API_PARAMETERS'] = api_parameters
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
ctx.obj['FORMAT'] = 'format'
|
def test_create_action_400(*args):
|
||||||
|
responses.add(responses.POST,
|
||||||
|
'http://shiptest/actions',
|
||||||
|
body=stubs.gen_err_resp(message='Error_400',
|
||||||
|
reason='bad action'),
|
||||||
|
status=400)
|
||||||
|
response = CreateAction(stubs.StubCliContext(),
|
||||||
|
action_name='deploy_dogs',
|
||||||
|
param=None).invoke_and_return_resp()
|
||||||
|
assert 'Error_400' in response
|
||||||
|
assert 'bad action' in response
|
||||||
|
assert 'action/01BTTMFVDKZFRJM80FGD7J1AKN' not in response
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
@responses.activate
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
def test_create_action_409(*args):
|
||||||
@mock.patch(
|
responses.add(responses.POST,
|
||||||
'shipyard_client.cli.create.actions.output_formatting',
|
'http://shiptest/actions',
|
||||||
side_effect=replace_output_formatting)
|
body=stubs.gen_err_resp(message='Error_409',
|
||||||
def test_CreateAction(*args):
|
reason='bad validations'),
|
||||||
action_name = 'redeploy_server'
|
status=409)
|
||||||
param = {'server-name': 'mcp'}
|
response = CreateAction(stubs.StubCliContext(),
|
||||||
response = CreateAction(ctx, action_name, param).invoke_and_return_resp()
|
action_name='deploy_site',
|
||||||
# test correct function was called
|
param=None).invoke_and_return_resp()
|
||||||
url = response.get('url')
|
assert 'Error_409' in response
|
||||||
assert 'actions' in url
|
assert 'bad validations' in response
|
||||||
# test function was called with correct parameters
|
assert 'action/01BTTMFVDKZFRJM80FGD7J1AKN' not in response
|
||||||
data = response.get('data')
|
|
||||||
assert '"name": "redeploy_server"' in data
|
|
||||||
assert '"parameters": {"server-name": "mcp"}' in data
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
@responses.activate
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
def test_create_configdocs(*args):
|
||||||
@mock.patch(
|
succ_resp = stubs.gen_err_resp(message='Validations succeeded',
|
||||||
'shipyard_client.cli.create.actions.output_formatting',
|
sub_error_count=0,
|
||||||
side_effect=replace_output_formatting)
|
sub_info_count=0,
|
||||||
def test_CreateConfigdocs(*args):
|
reason='Validation',
|
||||||
collection = 'design'
|
code=200)
|
||||||
|
responses.add(responses.POST,
|
||||||
|
'http://shiptest/configdocs/design',
|
||||||
|
body=succ_resp,
|
||||||
|
status=201)
|
||||||
|
|
||||||
filename = 'shipyard_client/tests/unit/cli/create/sample_yaml/sample.yaml'
|
filename = 'shipyard_client/tests/unit/cli/create/sample_yaml/sample.yaml'
|
||||||
document_data = yaml.dump_all(filename)
|
document_data = yaml.dump_all(filename)
|
||||||
buffer = 'append'
|
|
||||||
response = CreateConfigdocs(ctx, collection, buffer,
|
response = CreateConfigdocs(stubs.StubCliContext(),
|
||||||
|
'design',
|
||||||
|
'append',
|
||||||
document_data).invoke_and_return_resp()
|
document_data).invoke_and_return_resp()
|
||||||
# test correct function was called
|
assert 'Configuration documents added.'
|
||||||
url = response.get('url')
|
assert 'Status: Validations succeeded' in response
|
||||||
assert 'configdocs' in url
|
assert 'Reason: Validation' in response
|
||||||
# test function was called with correct parameters
|
|
||||||
assert collection in url
|
|
||||||
data = response.get('data')
|
@responses.activate
|
||||||
assert document_data in data
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
params = response.get('params')
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
assert params.get('buffermode') == buffer
|
def test_create_configdocs_201_with_val_fails(*args):
|
||||||
|
succ_resp = stubs.gen_err_resp(message='Validations failed',
|
||||||
|
sub_message='Some reason',
|
||||||
|
sub_error_count=2,
|
||||||
|
sub_info_count=1,
|
||||||
|
reason='Validation',
|
||||||
|
code=400)
|
||||||
|
responses.add(responses.POST,
|
||||||
|
'http://shiptest/configdocs/design',
|
||||||
|
body=succ_resp,
|
||||||
|
status=201)
|
||||||
|
|
||||||
|
filename = 'shipyard_client/tests/unit/cli/create/sample_yaml/sample.yaml'
|
||||||
|
document_data = yaml.dump_all(filename)
|
||||||
|
|
||||||
|
response = CreateConfigdocs(stubs.StubCliContext(),
|
||||||
|
'design',
|
||||||
|
'append',
|
||||||
|
document_data).invoke_and_return_resp()
|
||||||
|
assert 'Configuration documents added.' in response
|
||||||
|
assert 'Status: Validations failed' in response
|
||||||
|
assert 'Reason: Validation' in response
|
||||||
|
assert 'Some reason-1' in response
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_create_configdocs_409(*args):
|
||||||
|
err_resp = stubs.gen_err_resp(message='Invalid collection',
|
||||||
|
sub_message='Buffer is either not...',
|
||||||
|
sub_error_count=1,
|
||||||
|
sub_info_count=0,
|
||||||
|
reason='Buffermode : append',
|
||||||
|
code=409)
|
||||||
|
responses.add(responses.POST,
|
||||||
|
'http://shiptest/configdocs/design',
|
||||||
|
body=err_resp,
|
||||||
|
status=409)
|
||||||
|
|
||||||
|
filename = 'shipyard_client/tests/unit/cli/create/sample_yaml/sample.yaml'
|
||||||
|
document_data = yaml.dump_all(filename)
|
||||||
|
|
||||||
|
response = CreateConfigdocs(stubs.StubCliContext(),
|
||||||
|
'design',
|
||||||
|
'append',
|
||||||
|
document_data).invoke_and_return_resp()
|
||||||
|
assert 'Error: Invalid collection' in response
|
||||||
|
assert 'Reason: Buffermode : append' in response
|
||||||
|
assert 'Buffer is either not...' in response
|
||||||
|
@ -11,106 +11,317 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from shipyard_client.cli.describe.actions import \
|
import responses
|
||||||
DescribeAction, DescribeStep, DescribeValidation, DescribeWorkflow
|
|
||||||
from shipyard_client.api_client.base_client import BaseClient
|
from shipyard_client.api_client.base_client import BaseClient
|
||||||
from shipyard_client.tests.unit.cli.replace_api_client import \
|
from shipyard_client.cli.describe.actions import DescribeAction
|
||||||
replace_base_constructor, replace_post_rep, replace_get_resp, \
|
from shipyard_client.cli.describe.actions import DescribeStep
|
||||||
replace_output_formatting
|
from shipyard_client.cli.describe.actions import DescribeValidation
|
||||||
from shipyard_client.tests.unit.cli.utils import temporary_context
|
from shipyard_client.cli.describe.actions import DescribeWorkflow
|
||||||
from shipyard_client.api_client.shipyardclient_context import \
|
from shipyard_client.tests.unit.cli import stubs
|
||||||
ShipyardClientContext
|
|
||||||
|
|
||||||
auth_vars = {
|
|
||||||
'project_domain_name': 'projDomainTest',
|
GET_ACTION_API_RESP = """
|
||||||
'user_domain_name': 'userDomainTest',
|
{
|
||||||
'project_name': 'projectTest',
|
"name": "deploy_site",
|
||||||
'username': 'usernameTest',
|
"dag_execution_date": "2017-09-24T19:05:49",
|
||||||
'password': 'passwordTest',
|
"validations": [],
|
||||||
'auth_url': 'urlTest'
|
"id": "01BTTMFVDKZFRJM80FGD7J1AKN",
|
||||||
}
|
"dag_id": "deploy_site",
|
||||||
|
"command_audit": [
|
||||||
api_parameters = {
|
{
|
||||||
'auth_vars': auth_vars,
|
"id": "01BTTMG16R9H3Z4JVQNBMRV1MZ",
|
||||||
'context_marker': 'UUID',
|
"action_id": "01BTTMFVDKZFRJM80FGD7J1AKN",
|
||||||
'debug': False
|
"datetime": "2017-09-24 19:05:49.530223+00:00",
|
||||||
|
"user": "shipyard",
|
||||||
|
"command": "invoke"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"user": "shipyard",
|
||||||
|
"context_marker": "629f2ea2-c59d-46b9-8641-7367a91a7016",
|
||||||
|
"datetime": "2017-09-24 19:05:43.603591+00:00",
|
||||||
|
"dag_status": "failed",
|
||||||
|
"parameters": {},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "action_xcom",
|
||||||
|
"url": "/actions/01BTTMFVDKZFRJM80FGD7J1AKN/steps/action_xcom",
|
||||||
|
"index": 1,
|
||||||
|
"state": "success"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"action_lifecycle": "Failed"
|
||||||
}
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class MockCTX():
|
@responses.activate
|
||||||
pass
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_describe_action(*args):
|
||||||
|
responses.add(responses.GET,
|
||||||
|
'http://shiptest/actions/01BTTMFVDKZFRJM80FGD7J1AKN',
|
||||||
|
body=GET_ACTION_API_RESP,
|
||||||
|
status=200)
|
||||||
|
|
||||||
|
|
||||||
ctx = MockCTX()
|
|
||||||
ctx.obj = {}
|
|
||||||
ctx.obj['API_PARAMETERS'] = api_parameters
|
|
||||||
ctx.obj['FORMAT'] = 'format'
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
|
||||||
@mock.patch(
|
|
||||||
'shipyard_client.cli.describe.actions.output_formatting',
|
|
||||||
side_effect=replace_output_formatting)
|
|
||||||
def test_DescribeAction(*args):
|
|
||||||
response = DescribeAction(
|
response = DescribeAction(
|
||||||
ctx, '01BTG32JW87G0YKA1K29TKNAFX').invoke_and_return_resp()
|
stubs.StubCliContext(),
|
||||||
# test correct function was called
|
'01BTTMFVDKZFRJM80FGD7J1AKN'
|
||||||
url = response.get('url')
|
|
||||||
assert 'actions/01BTG32JW87G0YKA1K29TKNAFX' in url
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
|
||||||
@mock.patch(
|
|
||||||
'shipyard_client.cli.describe.actions.output_formatting',
|
|
||||||
side_effect=replace_output_formatting)
|
|
||||||
def test_DescribeStep(*args):
|
|
||||||
response = DescribeStep(ctx, '01BTG32JW87G0YKA1K29TKNAFX',
|
|
||||||
'preflight').invoke_and_return_resp()
|
|
||||||
# test correct function was called
|
|
||||||
url = response.get('url')
|
|
||||||
assert 'actions/01BTG32JW87G0YKA1K29TKNAFX/steps/preflight' in url
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
|
||||||
@mock.patch(
|
|
||||||
'shipyard_client.cli.describe.actions.output_formatting',
|
|
||||||
side_effect=replace_output_formatting)
|
|
||||||
def test_DescribeValidation(*args):
|
|
||||||
response = DescribeValidation(
|
|
||||||
ctx, '01BTG32JW87G0YKA1K29TKNAFX',
|
|
||||||
'01BTG3PKBS15KCKFZ56XXXBGF2').invoke_and_return_resp()
|
|
||||||
# test correct function was called
|
|
||||||
url = response.get('url')
|
|
||||||
assert 'actions' in url
|
|
||||||
assert '01BTG32JW87G0YKA1K29TKNAFX' in url
|
|
||||||
assert 'validationdetails' in url
|
|
||||||
assert '01BTG3PKBS15KCKFZ56XXXBGF2' in url
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
|
||||||
@mock.patch(
|
|
||||||
'shipyard_client.cli.describe.actions.output_formatting',
|
|
||||||
side_effect=replace_output_formatting)
|
|
||||||
def test_DescribeWorkflow(*args):
|
|
||||||
response = DescribeWorkflow(
|
|
||||||
ctx, 'deploy_site__2017-01-01T12:34:56.123456'
|
|
||||||
).invoke_and_return_resp()
|
).invoke_and_return_resp()
|
||||||
# test correct function was called
|
assert 'action/01BTTMFVDKZFRJM80FGD7J1AKN' in response
|
||||||
url = response.get('url')
|
assert 'step/01BTTMFVDKZFRJM80FGD7J1AKN/action_xcom' in response
|
||||||
assert 'workflows' in url
|
assert 'Steps' in response
|
||||||
assert 'deploy_site__2017-01-01T12:34:56.123456' in url
|
assert 'Commands' in response
|
||||||
|
assert 'Validations:' in response
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_describe_action_not_found(*args):
|
||||||
|
api_resp = stubs.gen_err_resp(message='Not Found',
|
||||||
|
sub_error_count=0,
|
||||||
|
sub_info_count=0,
|
||||||
|
reason='It does not exist',
|
||||||
|
code=404)
|
||||||
|
responses.add(responses.GET,
|
||||||
|
'http://shiptest/actions/01BTTMFVDKZFRJM80FGD7J1AKN',
|
||||||
|
body=api_resp,
|
||||||
|
status=404)
|
||||||
|
|
||||||
|
response = DescribeAction(
|
||||||
|
stubs.StubCliContext(),
|
||||||
|
'01BTTMFVDKZFRJM80FGD7J1AKN'
|
||||||
|
).invoke_and_return_resp()
|
||||||
|
assert 'Error: Not Found' in response
|
||||||
|
assert 'Reason: It does not exist' in response
|
||||||
|
|
||||||
|
|
||||||
|
GET_STEP_API_RESP = """
|
||||||
|
{
|
||||||
|
"end_date": "2017-09-24 19:05:59.446213",
|
||||||
|
"duration": 0.165181,
|
||||||
|
"queued_dttm": "2017-09-24 19:05:52.993983",
|
||||||
|
"operator": "PythonOperator",
|
||||||
|
"try_number": 1,
|
||||||
|
"task_id": "preflight",
|
||||||
|
"state": "success",
|
||||||
|
"execution_date": "2017-09-24 19:05:49",
|
||||||
|
"dag_id": "deploy_site",
|
||||||
|
"index": 1,
|
||||||
|
"start_date": "2017-09-24 19:05:59.281032"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_describe_step(*args):
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'http://shiptest/actions/01BTTMFVDKZFRJM80FGD7J1AKN/steps/preflight',
|
||||||
|
body=GET_STEP_API_RESP,
|
||||||
|
status=200)
|
||||||
|
|
||||||
|
response = DescribeStep(stubs.StubCliContext(),
|
||||||
|
'01BTTMFVDKZFRJM80FGD7J1AKN',
|
||||||
|
'preflight').invoke_and_return_resp()
|
||||||
|
assert 'step/01BTTMFVDKZFRJM80FGD7J1AKN/preflight' in response
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_describe_step_not_found(*args):
|
||||||
|
api_resp = stubs.gen_err_resp(message='Not Found',
|
||||||
|
sub_error_count=0,
|
||||||
|
sub_info_count=0,
|
||||||
|
reason='It does not exist',
|
||||||
|
code=404)
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'http://shiptest/actions/01BTTMFVDKZFRJM80FGD7J1AKN/steps/preflight',
|
||||||
|
body=api_resp,
|
||||||
|
status=404)
|
||||||
|
|
||||||
|
response = DescribeStep(stubs.StubCliContext(),
|
||||||
|
'01BTTMFVDKZFRJM80FGD7J1AKN',
|
||||||
|
'preflight').invoke_and_return_resp()
|
||||||
|
assert 'Error: Not Found' in response
|
||||||
|
assert 'Reason: It does not exist' in response
|
||||||
|
|
||||||
|
|
||||||
|
GET_VALIDATION_API_RESP = """
|
||||||
|
{
|
||||||
|
"validation_name": "validation_1",
|
||||||
|
"action_id": "01BTTMFVDKZFRJM80FGD7J1AKN",
|
||||||
|
"id": "02AURNEWAAAESKN99EBF8J2BHD",
|
||||||
|
"details": "Validations failed for field 'abc'"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_describe_validation(*args):
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'http://shiptest/actions/01BTTMFVDKZFRJM80FGD7J1AKN/'
|
||||||
|
'validationdetails/02AURNEWAAAESKN99EBF8J2BHD',
|
||||||
|
body=GET_VALIDATION_API_RESP,
|
||||||
|
status=200)
|
||||||
|
|
||||||
|
response = DescribeValidation(
|
||||||
|
stubs.StubCliContext(),
|
||||||
|
action_id='01BTTMFVDKZFRJM80FGD7J1AKN',
|
||||||
|
validation_id='02AURNEWAAAESKN99EBF8J2BHD').invoke_and_return_resp()
|
||||||
|
|
||||||
|
v_str = "validation/01BTTMFVDKZFRJM80FGD7J1AKN/02AURNEWAAAESKN99EBF8J2BHD"
|
||||||
|
assert v_str in response
|
||||||
|
assert "Validations failed for field 'abc'" in response
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_describe_validation_not_found(*args):
|
||||||
|
api_resp = stubs.gen_err_resp(message='Not Found',
|
||||||
|
sub_error_count=0,
|
||||||
|
sub_info_count=0,
|
||||||
|
reason='It does not exist',
|
||||||
|
code=404)
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'http://shiptest/actions/01BTTMFVDKZFRJM80FGD7J1AKN/'
|
||||||
|
'validationdetails/02AURNEWAAAESKN99EBF8J2BHD',
|
||||||
|
body=api_resp,
|
||||||
|
status=404)
|
||||||
|
|
||||||
|
response = DescribeValidation(
|
||||||
|
stubs.StubCliContext(),
|
||||||
|
action_id='01BTTMFVDKZFRJM80FGD7J1AKN',
|
||||||
|
validation_id='02AURNEWAAAESKN99EBF8J2BHD').invoke_and_return_resp()
|
||||||
|
assert 'Error: Not Found' in response
|
||||||
|
assert 'Reason: It does not exist' in response
|
||||||
|
|
||||||
|
|
||||||
|
WF_API_RESP = """
|
||||||
|
{
|
||||||
|
"execution_date": "2017-10-09 21:19:03",
|
||||||
|
"end_date": null,
|
||||||
|
"workflow_id": "deploy_site__2017-10-09T21:19:03.000000",
|
||||||
|
"start_date": "2017-10-09 21:19:03.361522",
|
||||||
|
"external_trigger": true,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"end_date": "2017-10-09 21:19:14.916220",
|
||||||
|
"task_id": "action_xcom",
|
||||||
|
"start_date": "2017-10-09 21:19:14.798053",
|
||||||
|
"duration": 0.118167,
|
||||||
|
"queued_dttm": "2017-10-09 21:19:08.432582",
|
||||||
|
"try_number": 1,
|
||||||
|
"state": "success",
|
||||||
|
"operator": "PythonOperator",
|
||||||
|
"dag_id": "deploy_site",
|
||||||
|
"execution_date": "2017-10-09 21:19:03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end_date": "2017-10-09 21:19:25.283785",
|
||||||
|
"task_id": "dag_concurrency_check",
|
||||||
|
"start_date": "2017-10-09 21:19:25.181492",
|
||||||
|
"duration": 0.102293,
|
||||||
|
"queued_dttm": "2017-10-09 21:19:19.283132",
|
||||||
|
"try_number": 1,
|
||||||
|
"state": "success",
|
||||||
|
"operator": "ConcurrencyCheckOperator",
|
||||||
|
"dag_id": "deploy_site",
|
||||||
|
"execution_date": "2017-10-09 21:19:03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end_date": "2017-10-09 21:20:05.394677",
|
||||||
|
"task_id": "preflight",
|
||||||
|
"start_date": "2017-10-09 21:19:34.994775",
|
||||||
|
"duration": 30.399902,
|
||||||
|
"queued_dttm": "2017-10-09 21:19:28.449848",
|
||||||
|
"try_number": 1,
|
||||||
|
"state": "failed",
|
||||||
|
"operator": "SubDagOperator",
|
||||||
|
"dag_id": "deploy_site",
|
||||||
|
"execution_date": "2017-10-09 21:19:03"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dag_id": "deploy_site",
|
||||||
|
"state": "failed",
|
||||||
|
"run_id": "manual__2017-10-09T21:19:03",
|
||||||
|
"sub_dags": [
|
||||||
|
{
|
||||||
|
"execution_date": "2017-10-09 21:19:03",
|
||||||
|
"end_date": null,
|
||||||
|
"workflow_id": "deploy_site.preflight__2017-10-09T21:19:03.000000",
|
||||||
|
"start_date": "2017-10-09 21:19:35.082479",
|
||||||
|
"external_trigger": false,
|
||||||
|
"dag_id": "deploy_site.preflight",
|
||||||
|
"state": "failed",
|
||||||
|
"run_id": "backfill_2017-10-09T21:19:03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"execution_date": "2017-10-09 21:19:03",
|
||||||
|
"end_date": null,
|
||||||
|
"workflow_id": "deploy_site.postflight__2017-10-09T21:19:03.000000",
|
||||||
|
"start_date": "2017-10-09 21:19:35.082479",
|
||||||
|
"external_trigger": false,
|
||||||
|
"dag_id": "deploy_site.postflight",
|
||||||
|
"state": "failed",
|
||||||
|
"run_id": "backfill_2017-10-09T21:19:03"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_describe_workflow(*args):
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'http://shiptest/workflows/deploy_site__2017-10-09T21:19:03.000000',
|
||||||
|
body=WF_API_RESP,
|
||||||
|
status=200)
|
||||||
|
|
||||||
|
response = DescribeWorkflow(
|
||||||
|
stubs.StubCliContext(),
|
||||||
|
'deploy_site__2017-10-09T21:19:03.000000'
|
||||||
|
).invoke_and_return_resp()
|
||||||
|
assert 'deploy_site__2017-10-09T21:19:03.000000' in response
|
||||||
|
assert 'deploy_site.preflight__2017-10-09T21:19:03.000000' in response
|
||||||
|
assert 'deploy_site.postflight__2017-10-09T21:19:03.000000' in response
|
||||||
|
assert 'dag_concurrency_check' in response
|
||||||
|
assert 'Subworkflows:' in response
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_describe_workflow_not_found(*args):
|
||||||
|
api_resp = stubs.gen_err_resp(message='Not Found',
|
||||||
|
sub_error_count=0,
|
||||||
|
sub_info_count=0,
|
||||||
|
reason='It does not exist',
|
||||||
|
code=404)
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'http://shiptest/workflows/deploy_site__2017-10-09T21:19:03.000000',
|
||||||
|
body=api_resp,
|
||||||
|
status=404)
|
||||||
|
|
||||||
|
response = DescribeWorkflow(
|
||||||
|
stubs.StubCliContext(),
|
||||||
|
'deploy_site__2017-10-09T21:19:03.000000'
|
||||||
|
).invoke_and_return_resp()
|
||||||
|
assert 'Error: Not Found' in response
|
||||||
|
assert 'Reason: It does not exist' in response
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# Copyright 2017 AT&T Intellectual Property. replace_shipyard All other rights
|
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
||||||
# reserved.
|
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -12,113 +11,218 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from shipyard_client.cli.get.actions import GetActions, GetConfigdocs, \
|
import responses
|
||||||
GetRenderedConfigdocs, GetWorkflows
|
|
||||||
from shipyard_client.api_client.base_client import BaseClient
|
from shipyard_client.api_client.base_client import BaseClient
|
||||||
from shipyard_client.tests.unit.cli.replace_api_client import \
|
from shipyard_client.cli.get.actions import GetActions
|
||||||
replace_base_constructor, replace_post_rep, replace_get_resp, \
|
from shipyard_client.cli.get.actions import GetConfigdocs
|
||||||
replace_output_formatting
|
from shipyard_client.cli.get.actions import GetRenderedConfigdocs
|
||||||
from shipyard_client.tests.unit.cli.utils import temporary_context
|
from shipyard_client.cli.get.actions import GetWorkflows
|
||||||
from shipyard_client.api_client.shipyardclient_context import \
|
from shipyard_client.tests.unit.cli import stubs
|
||||||
ShipyardClientContext
|
|
||||||
|
|
||||||
auth_vars = {
|
GET_ACTIONS_API_RESP = """
|
||||||
'project_domain_name': 'projDomainTest',
|
[
|
||||||
'user_domain_name': 'userDomainTest',
|
{
|
||||||
'project_name': 'projectTest',
|
"dag_status": "failed",
|
||||||
'username': 'usernameTest',
|
"parameters": {},
|
||||||
'password': 'passwordTest',
|
"steps": [
|
||||||
'auth_url': 'urlTest'
|
{
|
||||||
}
|
"id": "action_xcom",
|
||||||
api_parameters = {
|
"url": "/actions/01BTP9T2WCE1PAJR2DWYXG805V/steps/action_xcom",
|
||||||
'auth_vars': auth_vars,
|
"index": 1,
|
||||||
'context_marker': '88888888-4444-4444-4444-121212121212',
|
"state": "success"
|
||||||
'debug': False
|
},
|
||||||
}
|
{
|
||||||
|
"id": "concurrency_check",
|
||||||
|
"url": "/actions/01BTP9T2WCE1PAJR2DWYXG805V/steps/concurrency_check",
|
||||||
|
"index": 2,
|
||||||
|
"state": "success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "preflight",
|
||||||
|
"url": "/actions/01BTP9T2WCE1PAJR2DWYXG805V/steps/preflight",
|
||||||
|
"index": 3,
|
||||||
|
"state": "failed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"action_lifecycle": "Failed",
|
||||||
|
"dag_execution_date": "2017-09-23T02:42:12",
|
||||||
|
"id": "01BTP9T2WCE1PAJR2DWYXG805V",
|
||||||
|
"dag_id": "deploy_site",
|
||||||
|
"datetime": "2017-09-23 02:42:06.860597+00:00",
|
||||||
|
"user": "shipyard",
|
||||||
|
"context_marker": "416dec4b-82f9-4339-8886-3a0c4982aec3",
|
||||||
|
"name": "deploy_site"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class MockCTX():
|
@responses.activate
|
||||||
pass
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_get_actions(*args):
|
||||||
|
responses.add(responses.GET,
|
||||||
|
'http://shiptest/actions',
|
||||||
|
body=GET_ACTIONS_API_RESP,
|
||||||
|
status=200)
|
||||||
|
response = GetActions(stubs.StubCliContext()).invoke_and_return_resp()
|
||||||
|
assert 'deploy_site' in response
|
||||||
|
assert 'action/01BTP9T2WCE1PAJR2DWYXG805V' in response
|
||||||
|
assert 'Lifecycle' in response
|
||||||
|
|
||||||
|
|
||||||
ctx = MockCTX()
|
@responses.activate
|
||||||
ctx.obj = {}
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
ctx.obj['API_PARAMETERS'] = api_parameters
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
ctx.obj['FORMAT'] = 'format'
|
def test_get_actions_empty(*args):
|
||||||
|
responses.add(responses.GET,
|
||||||
|
'http://shiptest/actions',
|
||||||
|
body="[]",
|
||||||
|
status=200)
|
||||||
|
response = GetActions(stubs.StubCliContext()).invoke_and_return_resp()
|
||||||
|
assert 'None' in response
|
||||||
|
assert 'Lifecycle' in response
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
GET_CONFIGDOCS_API_RESP = """
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
---
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
yaml: yaml
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
---
|
||||||
@mock.patch(
|
yaml2: yaml2
|
||||||
'shipyard_client.cli.get.actions.output_formatting',
|
...
|
||||||
side_effect=replace_output_formatting)
|
"""
|
||||||
def test_GetActions(*args):
|
|
||||||
response = GetActions(ctx).invoke_and_return_resp()
|
|
||||||
# test correct function was called
|
|
||||||
url = response.get('url')
|
|
||||||
assert 'actions' in url
|
|
||||||
assert response.get('params') == {}
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
@responses.activate
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
def test_get_configdocs(*args):
|
||||||
@mock.patch(
|
responses.add(responses.GET,
|
||||||
'shipyard_client.cli.get.actions.output_formatting',
|
'http://shiptest/configdocs/design?version=buffer',
|
||||||
side_effect=replace_output_formatting)
|
body=GET_CONFIGDOCS_API_RESP,
|
||||||
def test_GetConfigdocs(*args):
|
status=200)
|
||||||
response = GetConfigdocs(ctx, 'design', 'buffer').invoke_and_return_resp()
|
response = GetConfigdocs(stubs.StubCliContext(),
|
||||||
# test correct function was called
|
collection='design',
|
||||||
url = response.get('url')
|
version='buffer').invoke_and_return_resp()
|
||||||
assert 'configdocs/design' in url
|
assert response == GET_CONFIGDOCS_API_RESP
|
||||||
params = response.get('params')
|
|
||||||
assert params.get('version') == 'buffer'
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
@responses.activate
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
def test_get_configdocs_not_found(*args):
|
||||||
@mock.patch(
|
api_resp = stubs.gen_err_resp(message='Not Found',
|
||||||
'shipyard_client.cli.get.actions.output_formatting',
|
sub_error_count=0,
|
||||||
side_effect=replace_output_formatting)
|
sub_info_count=0,
|
||||||
def test_GetRenderedConfigdocs(*args):
|
reason='It does not exist',
|
||||||
response = GetRenderedConfigdocs(ctx, 'buffer').invoke_and_return_resp()
|
code=404)
|
||||||
# test correct function was called
|
|
||||||
url = response.get('url')
|
responses.add(responses.GET,
|
||||||
assert 'renderedconfigdocs' in url
|
'http://shiptest/configdocs/design?version=buffer',
|
||||||
params = response.get('params')
|
body=api_resp,
|
||||||
assert params.get('version') == 'buffer'
|
status=404)
|
||||||
|
response = GetConfigdocs(stubs.StubCliContext(),
|
||||||
|
collection='design',
|
||||||
|
version='buffer').invoke_and_return_resp()
|
||||||
|
assert 'Error: Not Found' in response
|
||||||
|
assert 'Reason: It does not exist' in response
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(BaseClient, '__init__', replace_base_constructor)
|
GET_RENDEREDCONFIGDOCS_API_RESP = """
|
||||||
@mock.patch.object(BaseClient, 'post_resp', replace_post_rep)
|
---
|
||||||
@mock.patch.object(BaseClient, 'get_resp', replace_get_resp)
|
yaml: yaml
|
||||||
@mock.patch.object(ShipyardClientContext, '__init__', temporary_context)
|
---
|
||||||
@mock.patch(
|
yaml2: yaml2
|
||||||
'shipyard_client.cli.get.actions.output_formatting',
|
...
|
||||||
side_effect=replace_output_formatting)
|
"""
|
||||||
def test_GetWorkflows(*args):
|
|
||||||
response = GetWorkflows(ctx, since=None).invoke_and_return_resp()
|
|
||||||
url = response.get('url')
|
|
||||||
assert 'workflows' in url
|
|
||||||
assert 'since' not in url
|
|
||||||
|
|
||||||
response = GetWorkflows(ctx).invoke_and_return_resp()
|
|
||||||
url = response.get('url')
|
|
||||||
assert 'workflows' in url
|
|
||||||
assert 'since' not in url
|
|
||||||
|
|
||||||
since_val = '2017-01-01T12:34:56Z'
|
@responses.activate
|
||||||
response = GetWorkflows(ctx,
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
since=since_val).invoke_and_return_resp()
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
url = response.get('url')
|
def test_get_renderedconfigdocs(*args):
|
||||||
assert 'workflows' in url
|
responses.add(responses.GET,
|
||||||
params = response.get('params')
|
'http://shiptest/renderedconfigdocs?version=buffer',
|
||||||
assert params.get('since') == since_val
|
body=GET_RENDEREDCONFIGDOCS_API_RESP,
|
||||||
|
status=200)
|
||||||
|
response = GetRenderedConfigdocs(
|
||||||
|
stubs.StubCliContext(),
|
||||||
|
version='buffer').invoke_and_return_resp()
|
||||||
|
assert response == GET_RENDEREDCONFIGDOCS_API_RESP
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_get_renderedconfigdocs_not_found(*args):
|
||||||
|
api_resp = stubs.gen_err_resp(message='Not Found',
|
||||||
|
sub_error_count=0,
|
||||||
|
sub_info_count=0,
|
||||||
|
reason='It does not exist',
|
||||||
|
code=404)
|
||||||
|
|
||||||
|
responses.add(responses.GET,
|
||||||
|
'http://shiptest/renderedconfigdocs?version=buffer',
|
||||||
|
body=api_resp,
|
||||||
|
status=404)
|
||||||
|
response = GetRenderedConfigdocs(stubs.StubCliContext(),
|
||||||
|
version='buffer').invoke_and_return_resp()
|
||||||
|
assert 'Error: Not Found' in response
|
||||||
|
assert 'Reason: It does not exist' in response
|
||||||
|
|
||||||
|
|
||||||
|
GET_WORKFLOWS_API_RESP = """
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"execution_date": "2017-10-09 21:18:56",
|
||||||
|
"end_date": null,
|
||||||
|
"workflow_id": "deploy_site__2017-10-09T21:18:56.000000",
|
||||||
|
"start_date": "2017-10-09 21:18:56.685999",
|
||||||
|
"external_trigger": true,
|
||||||
|
"dag_id": "deploy_site",
|
||||||
|
"state": "failed",
|
||||||
|
"run_id": "manual__2017-10-09T21:18:56"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"execution_date": "2017-10-09 21:19:03",
|
||||||
|
"end_date": null,
|
||||||
|
"workflow_id": "deploy_site__2017-10-09T21:19:03.000000",
|
||||||
|
"start_date": "2017-10-09 21:19:03.361522",
|
||||||
|
"external_trigger": true,
|
||||||
|
"dag_id": "deploy_site",
|
||||||
|
"state": "failed",
|
||||||
|
"run_id": "manual__2017-10-09T21:19:03"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_get_workflows(*args):
|
||||||
|
responses.add(responses.GET,
|
||||||
|
'http://shiptest/workflows',
|
||||||
|
body=GET_WORKFLOWS_API_RESP,
|
||||||
|
status=200)
|
||||||
|
response = GetWorkflows(stubs.StubCliContext()).invoke_and_return_resp()
|
||||||
|
assert 'deploy_site__2017-10-09T21:19:03.000000' in response
|
||||||
|
assert 'deploy_site__2017-10-09T21:18:56.000000' in response
|
||||||
|
assert 'State' in response
|
||||||
|
assert 'Workflow' in response
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
|
||||||
|
@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
|
||||||
|
def test_get_workflows_empty(*args):
|
||||||
|
responses.add(responses.GET,
|
||||||
|
'http://shiptest/workflows',
|
||||||
|
body="[]",
|
||||||
|
status=200)
|
||||||
|
response = GetWorkflows(stubs.StubCliContext()).invoke_and_return_resp()
|
||||||
|
assert 'None' in response
|
||||||
|
assert 'State' in response
|
||||||
|
@ -15,14 +15,9 @@
|
|||||||
# For testing purposes only
|
# For testing purposes only
|
||||||
|
|
||||||
|
|
||||||
class TemporaryContext(object):
|
def replace_get_endpoint():
|
||||||
def __init__(self):
|
"""Replaces the get endpoint call to isolate tests"""
|
||||||
self.debug = True
|
return 'http://shipyard-test'
|
||||||
self.keystone_Auth = {}
|
|
||||||
self.token = 'abcdefgh'
|
|
||||||
self.service_type = 'http://shipyard'
|
|
||||||
self.shipyard_endpoint = 'http://shipyard/api/v1.0'
|
|
||||||
self.context_marker = '123456'
|
|
||||||
|
|
||||||
|
|
||||||
def replace_post_rep(self, url, query_params={}, data={}, content_type=''):
|
def replace_post_rep(self, url, query_params={}, data={}, content_type=''):
|
||||||
@ -39,11 +34,3 @@ def replace_get_resp(self, url, query_params={}, json=False):
|
|||||||
:returns: dict with url and parameters
|
:returns: dict with url and parameters
|
||||||
"""
|
"""
|
||||||
return {'url': url, 'params': query_params}
|
return {'url': url, 'params': query_params}
|
||||||
|
|
||||||
|
|
||||||
def replace_base_constructor(self, context):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def replace_output_formatting(format, response):
|
|
||||||
return response
|
|
||||||
|
164
shipyard_client/tests/unit/cli/stubs.py
Normal file
164
shipyard_client/tests/unit/cli/stubs.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# 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.
|
||||||
|
import responses
|
||||||
|
|
||||||
|
from shipyard_client.cli.action import CliAction
|
||||||
|
from shipyard_client.cli import format_utils
|
||||||
|
|
||||||
|
DEFAULT_AUTH_VARS = {
|
||||||
|
'project_domain_name': 'projDomainTest',
|
||||||
|
'user_domain_name': 'userDomainTest',
|
||||||
|
'project_name': 'projectTest',
|
||||||
|
'username': 'usernameTest',
|
||||||
|
'password': 'passwordTest',
|
||||||
|
'auth_url': 'urlTest'
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_API_PARAMS = {
|
||||||
|
'auth_vars': DEFAULT_AUTH_VARS,
|
||||||
|
'context_marker': '88888888-4444-4444-4444-121212121212',
|
||||||
|
'debug': False
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_BODY = """
|
||||||
|
{
|
||||||
|
"message": "Sample status response",
|
||||||
|
"details": {
|
||||||
|
"messageList": [
|
||||||
|
{
|
||||||
|
"message": "Message1",
|
||||||
|
"error": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message": "Message2",
|
||||||
|
"error": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"reason": "Testing"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
STATUS_TEMPL = """
|
||||||
|
{{
|
||||||
|
"kind": "Status",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"metadata": {{}},
|
||||||
|
"status": "Valid",
|
||||||
|
"message": "{}",
|
||||||
|
"reason": "{}",
|
||||||
|
"details": {{
|
||||||
|
"errorCount": {},
|
||||||
|
"messageList": {}
|
||||||
|
}},
|
||||||
|
"code": {}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
STATUS_MSG_TEMPL = """
|
||||||
|
{{
|
||||||
|
"message": "{}-{}",
|
||||||
|
"error": {}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def gen_err_resp(message='Err Message',
|
||||||
|
sub_message='Submessage',
|
||||||
|
sub_error_count=1,
|
||||||
|
sub_info_count=0,
|
||||||
|
reason='Reason Text',
|
||||||
|
code=400):
|
||||||
|
"""Generates a fake status/error response for testing purposes"""
|
||||||
|
sub_messages = []
|
||||||
|
for i in range(0, sub_error_count):
|
||||||
|
sub_messages.append(STATUS_MSG_TEMPL.format(sub_message, i, 'true'))
|
||||||
|
for i in range(0, sub_info_count):
|
||||||
|
sub_messages.append(STATUS_MSG_TEMPL.format(sub_message, i, 'false'))
|
||||||
|
msg_list = '[{}]'.format(','.join(sub_messages))
|
||||||
|
resp_str = STATUS_TEMPL.format(message,
|
||||||
|
reason,
|
||||||
|
sub_error_count,
|
||||||
|
msg_list,
|
||||||
|
code)
|
||||||
|
return resp_str
|
||||||
|
|
||||||
|
|
||||||
|
def gen_api_param(auth_vars=None,
|
||||||
|
context_marker='88888888-4444-4444-4444-121212121212',
|
||||||
|
debug=False):
|
||||||
|
"""Generates an object that is useful as input to a StubCliContext"""
|
||||||
|
if auth_vars is None:
|
||||||
|
auth_vars = DEFAULT_AUTH_VARS
|
||||||
|
return {
|
||||||
|
'auth_vars': auth_vars,
|
||||||
|
'context_marker': context_marker,
|
||||||
|
'debug': debug
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StubCliContext():
|
||||||
|
"""A stub CLI context that can be configured for tests"""
|
||||||
|
def __init__(self,
|
||||||
|
fmt='cli',
|
||||||
|
api_parameters=None):
|
||||||
|
if api_parameters is None:
|
||||||
|
api_parameters = gen_api_param()
|
||||||
|
self.obj = {}
|
||||||
|
self.obj['API_PARAMETERS'] = api_parameters
|
||||||
|
self.obj['FORMAT'] = fmt
|
||||||
|
|
||||||
|
|
||||||
|
class StubAction(CliAction):
|
||||||
|
"""A modifiable action that can be used to drive specific behaviors"""
|
||||||
|
def __init__(self,
|
||||||
|
ctx,
|
||||||
|
body=DEFAULT_BODY,
|
||||||
|
status_code=200,
|
||||||
|
method='GET'):
|
||||||
|
super().__init__(ctx)
|
||||||
|
self.body = body
|
||||||
|
self.status_code = status_code
|
||||||
|
self.method = method
|
||||||
|
|
||||||
|
def invoke(self):
|
||||||
|
"""Uses responses package to return a fake response"""
|
||||||
|
return responses.Response(
|
||||||
|
method=self.method,
|
||||||
|
url='http://shiptest/stub',
|
||||||
|
body=self.body,
|
||||||
|
status=self.status_code
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle 404 with default error handler for cli.
|
||||||
|
cli_handled_err_resp_codes = [400]
|
||||||
|
|
||||||
|
# Handle 200 responses using the cli_format_response_handler
|
||||||
|
cli_handled_succ_resp_codes = [200]
|
||||||
|
|
||||||
|
def cli_format_response_handler(self, response):
|
||||||
|
"""CLI output handler
|
||||||
|
|
||||||
|
:param response: a requests response object
|
||||||
|
:returns: a string representing a formatted response
|
||||||
|
Handles 200 responses
|
||||||
|
"""
|
||||||
|
resp_j = response.json()
|
||||||
|
return format_utils.table_factory(
|
||||||
|
field_names=['Col1', 'Col2'],
|
||||||
|
rows=[
|
||||||
|
['message', resp_j.get('message')],
|
||||||
|
['reason', resp_j.get('reason')]
|
||||||
|
]
|
||||||
|
)
|
69
shipyard_client/tests/unit/cli/test_auth_validations.py
Normal file
69
shipyard_client/tests/unit/cli/test_auth_validations.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# 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.
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from shipyard_client.cli.action import AuthValuesError
|
||||||
|
from shipyard_client.tests.unit.cli import stubs
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_auth_vars_valid():
|
||||||
|
action = stubs.StubAction(stubs.StubCliContext())
|
||||||
|
try:
|
||||||
|
action.validate_auth_vars()
|
||||||
|
except AuthValuesError:
|
||||||
|
# Valid parameters should not raise an AuthValuesError
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_auth_vars_missing_required():
|
||||||
|
auth_vars = {
|
||||||
|
'project_domain_name': 'default',
|
||||||
|
'user_domain_name': 'default',
|
||||||
|
'project_name': 'service',
|
||||||
|
'username': 'shipyard',
|
||||||
|
'password': 'password',
|
||||||
|
'auth_url': None
|
||||||
|
}
|
||||||
|
|
||||||
|
param = stubs.gen_api_param(auth_vars=auth_vars)
|
||||||
|
action = stubs.StubAction(stubs.StubCliContext(api_parameters=param))
|
||||||
|
with pytest.raises(AuthValuesError):
|
||||||
|
try:
|
||||||
|
action.validate_auth_vars()
|
||||||
|
except AuthValuesError as ex:
|
||||||
|
assert 'os_auth_url' in ex.diagnostic
|
||||||
|
assert 'os_username' not in ex.diagnostic
|
||||||
|
assert 'os_password' not in ex.diagnostic
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_auth_vars_missing_required_and_others():
|
||||||
|
auth_vars = {
|
||||||
|
'project_domain_name': 'default',
|
||||||
|
'user_domain_name': 'default',
|
||||||
|
'project_name': 'service',
|
||||||
|
'username': None,
|
||||||
|
'password': 'password',
|
||||||
|
'auth_url': None
|
||||||
|
}
|
||||||
|
param = stubs.gen_api_param(auth_vars=auth_vars)
|
||||||
|
action = stubs.StubAction(stubs.StubCliContext(api_parameters=param))
|
||||||
|
with pytest.raises(AuthValuesError):
|
||||||
|
try:
|
||||||
|
action.validate_auth_vars()
|
||||||
|
except AuthValuesError as ex:
|
||||||
|
assert 'os_auth_url' in ex.diagnostic
|
||||||
|
assert 'os_username' in ex.diagnostic
|
||||||
|
assert 'os_password' not in ex.diagnostic
|
||||||
|
raise
|
152
shipyard_client/tests/unit/cli/test_format_utils.py
Normal file
152
shipyard_client/tests/unit/cli/test_format_utils.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# 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.
|
||||||
|
import json
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from prettytable.prettytable import DEFAULT
|
||||||
|
|
||||||
|
import shipyard_client.cli.format_utils as format_utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_format_error_handler_bogus_json():
|
||||||
|
"""Tests the generic handler for shipyard error response if passed
|
||||||
|
unrecognized json
|
||||||
|
"""
|
||||||
|
resp = MagicMock()
|
||||||
|
resp.json = MagicMock(return_value=json.loads('{"key": "value"}'))
|
||||||
|
output = format_utils.cli_format_error_handler(resp)
|
||||||
|
assert 'Error: Not specified' in output
|
||||||
|
assert 'Reason: Not specified' in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_format_error_handler_broken_json():
|
||||||
|
"""Tests the generic handler for shipyard error response if passed
|
||||||
|
unrecognized json
|
||||||
|
"""
|
||||||
|
resp = MagicMock()
|
||||||
|
resp.json.side_effect = ValueError("")
|
||||||
|
resp.text = "Not JSON"
|
||||||
|
output = format_utils.cli_format_error_handler(resp)
|
||||||
|
assert 'Error: Unable to decode response. Value: Not JSON' in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_format_error_handler_no_messages():
|
||||||
|
"""Tests the generic handler for shipyard error response if passed
|
||||||
|
json in the right format, but with no messages
|
||||||
|
"""
|
||||||
|
resp_val = """
|
||||||
|
{
|
||||||
|
"apiVersion": "v1.0",
|
||||||
|
"status": "Failure",
|
||||||
|
"metadata": {},
|
||||||
|
"message": "Unauthenticated",
|
||||||
|
"code": "401 Unauthorized",
|
||||||
|
"details": {},
|
||||||
|
"kind": "status",
|
||||||
|
"reason": "Credentials are not established"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
resp = MagicMock()
|
||||||
|
resp.json = MagicMock(return_value=json.loads(resp_val))
|
||||||
|
output = format_utils.cli_format_error_handler(resp)
|
||||||
|
print(output)
|
||||||
|
assert "Error: Unauthenticated" in output
|
||||||
|
assert "Reason: Credentials are not established" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_format_error_handler_messages():
|
||||||
|
"""Tests the generic handler for shipyard error response if passed
|
||||||
|
a response with messages in the detail
|
||||||
|
"""
|
||||||
|
resp_val = """
|
||||||
|
{
|
||||||
|
"apiVersion": "v1.0",
|
||||||
|
"status": "Failure",
|
||||||
|
"metadata": {},
|
||||||
|
"message": "Unauthenticated",
|
||||||
|
"code": "401 Unauthorized",
|
||||||
|
"details": {
|
||||||
|
"messageList": [
|
||||||
|
{ "message":"Hello1", "error": false },
|
||||||
|
{ "message":"Hello2", "error": false },
|
||||||
|
{ "message":"Hello3", "error": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"kind": "status",
|
||||||
|
"reason": "Credentials are not established"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
resp = MagicMock()
|
||||||
|
resp.json = MagicMock(return_value=json.loads(resp_val))
|
||||||
|
output = format_utils.cli_format_error_handler(resp)
|
||||||
|
assert "Error: Unauthenticated" in output
|
||||||
|
assert "Reason: Credentials are not established" in output
|
||||||
|
assert "- Error: Hello3" in output
|
||||||
|
assert "- Info: Hello2" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_format_error_handler_messages_broken():
|
||||||
|
"""Tests the generic handler for shipyard error response if passed
|
||||||
|
a response with messages in the detail, but missing error or message
|
||||||
|
elements
|
||||||
|
"""
|
||||||
|
resp_val = """
|
||||||
|
{
|
||||||
|
"apiVersion": "v1.0",
|
||||||
|
"status": "Failure",
|
||||||
|
"metadata": {},
|
||||||
|
"message": "Unauthenticated",
|
||||||
|
"code": "401 Unauthorized",
|
||||||
|
"details": {
|
||||||
|
"messageList": [
|
||||||
|
{ "message":"Hello1", "error": false },
|
||||||
|
{ "error": true },
|
||||||
|
{ "message":"Hello3" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"kind": "status",
|
||||||
|
"reason": "Credentials are not established"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
resp = MagicMock()
|
||||||
|
resp.json = MagicMock(return_value=json.loads(resp_val))
|
||||||
|
output = format_utils.cli_format_error_handler(resp)
|
||||||
|
assert "Error: Unauthenticated" in output
|
||||||
|
assert "Reason: Credentials are not established" in output
|
||||||
|
assert "- Error: None" in output
|
||||||
|
assert "- Info: Hello3" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_table_factory():
|
||||||
|
t = format_utils.table_factory()
|
||||||
|
assert t.get_string() == ''
|
||||||
|
|
||||||
|
|
||||||
|
def test_table_factory_fields():
|
||||||
|
t = format_utils.table_factory(field_names=['a', 'b', 'c'])
|
||||||
|
t.add_row(['1', '2', '3'])
|
||||||
|
assert 'a' in t.get_string()
|
||||||
|
assert 'b' in t.get_string()
|
||||||
|
assert 'c' in t.get_string()
|
||||||
|
|
||||||
|
|
||||||
|
def test_table_factory_fields_data():
|
||||||
|
t = format_utils.table_factory(style=DEFAULT,
|
||||||
|
field_names=['a', 'b', 'c'],
|
||||||
|
rows=[['1', '2', '3'], ['4', '5', '6']])
|
||||||
|
assert 'a' in t.get_string()
|
||||||
|
assert 'b' in t.get_string()
|
||||||
|
assert 'c' in t.get_string()
|
||||||
|
assert '1' in t.get_string()
|
||||||
|
assert '6' in t.get_string()
|
@ -200,62 +200,6 @@ def test_check_action_commands_none():
|
|||||||
assert 'call.fail(' in str(ctx.mock_calls[0])
|
assert 'call.fail(' in str(ctx.mock_calls[0])
|
||||||
|
|
||||||
|
|
||||||
def test_validate_auth_vars_valid():
|
|
||||||
ctx = Mock(side_effect=Exception("failed"))
|
|
||||||
auth_vars = {
|
|
||||||
'project_domain_name': 'default',
|
|
||||||
'user_domain_name': 'default',
|
|
||||||
'project_name': 'service',
|
|
||||||
'username': 'shipyard',
|
|
||||||
'password': 'password',
|
|
||||||
'auth_url': 'abcdefg'
|
|
||||||
}
|
|
||||||
input_checks.validate_auth_vars(ctx, auth_vars)
|
|
||||||
ctx.fail.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_auth_vars_missing_required():
|
|
||||||
ctx = Mock(side_effect=Exception("failed"))
|
|
||||||
auth_vars = {
|
|
||||||
'project_domain_name': 'default',
|
|
||||||
'user_domain_name': 'default',
|
|
||||||
'project_name': 'service',
|
|
||||||
'username': 'shipyard',
|
|
||||||
'password': 'password',
|
|
||||||
'auth_url': None
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
input_checks.validate_auth_vars(ctx, auth_vars)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
# py 3.6: ctx.fail.assert_called()
|
|
||||||
assert 'call.fail(' in str(ctx.mock_calls[0])
|
|
||||||
assert 'os_auth_url' in str(ctx.mock_calls[0])
|
|
||||||
assert 'os_username' not in str(ctx.mock_calls[0])
|
|
||||||
assert 'os_password' not in str(ctx.mock_calls[0])
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_auth_vars_missing_required_and_others():
|
|
||||||
ctx = Mock(side_effect=Exception("failed"))
|
|
||||||
auth_vars = {
|
|
||||||
'project_domain_name': 'default',
|
|
||||||
'user_domain_name': 'default',
|
|
||||||
'project_name': 'service',
|
|
||||||
'username': None,
|
|
||||||
'password': 'password',
|
|
||||||
'auth_url': None
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
input_checks.validate_auth_vars(ctx, auth_vars)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
# py 3.6: ctx.fail.assert_called()
|
|
||||||
assert 'call.fail(' in str(ctx.mock_calls[0])
|
|
||||||
assert 'os_auth_url' in str(ctx.mock_calls[0])
|
|
||||||
assert 'os_username' in str(ctx.mock_calls[0])
|
|
||||||
assert 'os_password' not in str(ctx.mock_calls[0])
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_reformat_parameter_valid():
|
def test_check_reformat_parameter_valid():
|
||||||
ctx = Mock(side_effect=Exception("failed"))
|
ctx = Mock(side_effect=Exception("failed"))
|
||||||
param = ['this=that']
|
param = ['this=that']
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import json
|
|
||||||
import yaml
|
|
||||||
from mock import patch, ANY
|
|
||||||
from requests.models import Response
|
|
||||||
|
|
||||||
from shipyard_client.cli.output_formatting import output_formatting
|
|
||||||
|
|
||||||
json_response = Response()
|
|
||||||
json_response._content = b'{ "key" : "a" }'
|
|
||||||
json_response.status_code = 200
|
|
||||||
json_response.headers['content-type'] = 'application/json'
|
|
||||||
|
|
||||||
yaml_response = Response()
|
|
||||||
yaml_response._content = b'''Projects:
|
|
||||||
C/C++ Libraries:
|
|
||||||
- libyaml # "C" Fast YAML 1.1
|
|
||||||
- Syck # (dated) "C" YAML 1.0
|
|
||||||
- yaml-cpp # C++ YAML 1.2 implementation
|
|
||||||
Ruby:
|
|
||||||
- psych # libyaml wrapper (in Ruby core for 1.9.2)
|
|
||||||
- RbYaml # YAML 1.1 (PyYAML Port)
|
|
||||||
- yaml4r # YAML 1.0, standard library syck binding
|
|
||||||
Python:
|
|
||||||
- PyYAML # YAML 1.1, pure python and libyaml binding
|
|
||||||
- ruamel.yaml # YAML 1.2, update of PyYAML with round-tripping of comments
|
|
||||||
- PySyck # YAML 1.0, syck binding'''
|
|
||||||
|
|
||||||
yaml_response.headers['content-type'] = 'application/yaml'
|
|
||||||
|
|
||||||
|
|
||||||
def test_output_formatting():
|
|
||||||
"""call output formatting and check correct one was given"""
|
|
||||||
|
|
||||||
with patch.object(json, 'dumps') as mock_method:
|
|
||||||
output_formatting('format', json_response)
|
|
||||||
mock_method.assert_called_once_with(
|
|
||||||
json_response.json(), sort_keys=True, indent=4)
|
|
||||||
|
|
||||||
with patch.object(yaml, 'dump_all') as mock_method:
|
|
||||||
output_formatting('format', yaml_response)
|
|
||||||
mock_method.assert_called_once_with(
|
|
||||||
ANY, width=79, indent=4, default_flow_style=False)
|
|
@ -2,6 +2,7 @@
|
|||||||
pytest==3.2.1
|
pytest==3.2.1
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
mock==2.0.0
|
mock==2.0.0
|
||||||
|
responses==0.8.1
|
||||||
testfixtures==5.1.1
|
testfixtures==5.1.1
|
||||||
apache-airflow[crypto,celery,postgres,hive,hdfs,jdbc]==1.8.1
|
apache-airflow[crypto,celery,postgres,hive,hdfs,jdbc]==1.8.1
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user