diff --git a/.gitignore b/.gitignore index 95c071f..589ed55 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,10 @@ doc/source/api doc/build *.egg .eggs/* -stacktaskclient/versioninfo +adjutantclient/versioninfo *.egg-info *.log .testrepository .pypirc env/ +*~ diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index c8af2c3..0000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./stacktaskclient/tests/unit} $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/AUTHORS~ b/AUTHORS~ deleted file mode 100644 index c2a7e17..0000000 --- a/AUTHORS~ +++ /dev/null @@ -1,2 +0,0 @@ -Dale Smith -adriant diff --git a/MANIFEST.in b/MANIFEST.in index 16438ae..1eec6d9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include AUTHORS include LICENSE -include README.md +include README.rst include ChangeLog diff --git a/README.md b/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/README.rst b/README.rst index b674aca..755eb9f 100644 --- a/README.rst +++ b/README.rst @@ -1,17 +1,17 @@ -StackTaskClient is a command-line and python client for StackTask. +AdjutantClient is a command-line and python client for Adjutant. Getting Started =============== -StackTask Client can be installed from PyPI using pip: +Adjutant Client can be installed from PyPI using pip: :: - pip install python-openstackclient python-stacktaskclient + pip install python-openstackclient python-adjutantclient The command line client is installed as a plugin for the OpenStack client, and -an older deprecated version is available under the entry point 'stacktask'. +an older deprecated version is available under the entry point 'adjutant'. Python API ========== @@ -19,13 +19,13 @@ Python API In order to use the python api directly, you must first obtain an auth token and identify which endpoint you wish to speak to:: - >>> stacktask_url = 'http://stacktask.example.org:8004/v1/' + >>> adjutant_url = 'http://adjutant.example.org:8004/v1/' >>> auth_token = '3bcc3d3a03f44e3d8377f9247b0ad155' Once you have done so, you can use the API like so:: - >>> from stacktaskclient.client import Client - >>> stacktask = Client('1', endpoint=stacktask_url, token=auth_token) + >>> from adjutantclient.client import Client + >>> adjutant = Client('1', endpoint=adjutant_url, token=auth_token) An auth token isn't needed for accessing signup, user.password_forgot(), token.submit() or token.get(). diff --git a/stacktaskclient/__init__.py b/adjutantclient/__init__.py similarity index 87% rename from stacktaskclient/__init__.py rename to adjutantclient/__init__.py index b126fc8..f641663 100644 --- a/stacktaskclient/__init__.py +++ b/adjutantclient/__init__.py @@ -13,4 +13,4 @@ import pbr.version -__version__ = pbr.version.VersionInfo('python-stacktaskclient').version_string() +__version__ = pbr.version.VersionInfo('python-adjutantclient').version_string() diff --git a/adjutantclient/_i18n.py b/adjutantclient/_i18n.py new file mode 100644 index 0000000..d05e2a4 --- /dev/null +++ b/adjutantclient/_i18n.py @@ -0,0 +1,37 @@ +# 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. + +"""oslo.i18n integration module. + +See http://docs.openstack.org/developer/oslo.i18n/usage.html + +""" + +import oslo_i18n + +# NOTE(dhellmann): This reference to o-s-l-o will be replaced by the +# application name when this module is synced into the separate +# repository. It is OK to have more than one translation function +# using the same domain, since there will still only be one message +# catalog. +_translators = oslo_i18n.TranslatorFactory(domain='adjutantclient') + +# The primary translation function using the well-known name "_" +_ = _translators.primary + + +def get_available_languages(): + return oslo_i18n.get_available_languages('adjutantclient') + + +def enable_lazy(): + return oslo_i18n.enable_lazy() diff --git a/stacktaskclient/client.py b/adjutantclient/client.py similarity index 84% rename from stacktaskclient/client.py rename to adjutantclient/client.py index 417813b..2b1cf85 100644 --- a/stacktaskclient/client.py +++ b/adjutantclient/client.py @@ -10,10 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. -from stacktaskclient.common import utils +from oslo_utils import importutils def Client(version, *args, **kwargs): - module = utils.import_versioned_module(version, 'client') + module = importutils.import_versioned_module( + 'adjutantclient', version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **kwargs) diff --git a/stacktaskclient/common/__init__.py b/adjutantclient/common/__init__.py similarity index 100% rename from stacktaskclient/common/__init__.py rename to adjutantclient/common/__init__.py diff --git a/stacktaskclient/openstack/common/apiclient/base.py b/adjutantclient/common/base.py similarity index 94% rename from stacktaskclient/openstack/common/apiclient/base.py rename to adjutantclient/common/base.py index 84a38e3..b91ecaa 100644 --- a/stacktaskclient/openstack/common/apiclient/base.py +++ b/adjutantclient/common/base.py @@ -19,33 +19,16 @@ """ Base utilities to build API operation managers and objects on top of. """ - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - - -# E1102: %s is not callable -# pylint: disable=E1102 - import abc import copy +from oslo_utils import reflection from oslo_utils import strutils import six from six.moves.urllib import parse -from stacktaskclient.openstack.common._i18n import _ -from stacktaskclient.openstack.common.apiclient import exceptions +from adjutantclient._i18n import _ +from adjutantclient import exc as exceptions def getid(obj): @@ -216,14 +199,12 @@ class BaseManager(HookableMixin): else: return self.resource_class(self, body) - def _delete(self, url, json=None, response_key=None): + def _delete(self, url): """Delete an object. :param url: a partial URL, e.g., '/servers/my-server' - :param json: data that will be encoded as JSON and passed in DELETE - request """ - return self.client.delete(url, json=json) + return self.client.delete(url) @six.add_metaclass(abc.ABCMeta) @@ -277,14 +258,12 @@ class CrudManager(BaseManager): """Base manager class for manipulating entities. Children of this class are expected to define a `collection_key` and `key`. - - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. - """ collection_key = None key = None @@ -294,16 +273,11 @@ class CrudManager(BaseManager): Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. - By default, the URL will represent a collection of entities, e.g.:: - /entities - If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: - /entities/{entity_id} - :param base_url: if provided, the generated URL will be appended to it """ url = base_url if base_url is not None else '' @@ -444,7 +418,6 @@ class Resource(object): This is pretty much just a bag for attributes. """ - HUMAN_ID = False NAME_ATTR = 'name' @@ -465,12 +438,12 @@ class Resource(object): for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) - return "<%s %s>" % (self.__class__.__name__, info) + class_name = reflection.get_class_name(self, fully_qualified=False) + return "<%s %s>" % (class_name, info) @property def human_id(self): - """Human-readable ID which can be used for bash completion. - """ + """Human-readable ID which can be used for bash completion. """ if self.HUMAN_ID: name = getattr(self, self.NAME_ATTR, None) if name is not None: @@ -520,10 +493,18 @@ class Resource(object): # two resources of different types are not equal if not isinstance(other, self.__class__): return False - if hasattr(self, 'id') and hasattr(other, 'id'): - return self.id == other.id return self._info == other._info + def __ne__(self, other): + return not self.__eq__(other) + + def is_same_obj(self, other): + """Identify the two objects are same one with same id.""" + if isinstance(other, self.__class__): + if hasattr(self, 'id') and hasattr(other, 'id'): + return self.id == other.id + return False + def is_loaded(self): return self._loaded diff --git a/stacktaskclient/common/http.py b/adjutantclient/common/http.py similarity index 83% rename from stacktaskclient/common/http.py rename to adjutantclient/common/http.py index 7f74e5b..488d7d0 100644 --- a/stacktaskclient/common/http.py +++ b/adjutantclient/common/http.py @@ -19,6 +19,7 @@ import logging import os import socket +from keystoneauth1 import adapter from oslo_serialization import jsonutils from oslo_utils import encodeutils from oslo_utils import importutils @@ -26,19 +27,28 @@ import requests import six from six.moves.urllib import parse -from stacktaskclient.common import utils -from stacktaskclient import exc -from stacktaskclient.openstack.common._i18n import _ -from stacktaskclient.openstack.common._i18n import _LW -from keystoneauth1 import adapter +from adjutantclient._i18n import _ +from adjutantclient import exc LOG = logging.getLogger(__name__) -USER_AGENT = 'python-stacktaskclient' +USER_AGENT = 'python-adjutantclient' CHUNKSIZE = 1024 * 64 # 64kB SENSITIVE_HEADERS = ('X-Auth-Token',) osprofiler_web = importutils.try_import("osprofiler.web") +def get_response_body(resp): + body = resp.content + if 'application/json' in resp.headers.get('content-type', ''): + try: + body = resp.json() + except ValueError: + LOG.error('Could not decode response body as JSON') + else: + body = None + return body + + def get_system_ca_file(): """Return path to system default CA file.""" # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, @@ -54,7 +64,7 @@ def get_system_ca_file(): if os.path.exists(ca): LOG.debug("Using ca file %s", ca) return ca - LOG.warn(_LW("System ca file could not be found.")) + LOG.warning("System ca file could not be found.") class HTTPClient(object): @@ -158,7 +168,7 @@ class HTTPClient(object): kwargs['headers'].setdefault('X-Auth-Url', self.auth_url) if self.region_name: kwargs['headers'].setdefault('X-Region-Name', self.region_name) - if self.include_pass and not 'X-Auth-Key' in kwargs['headers']: + if self.include_pass and 'X-Auth-Key' not in kwargs['headers']: kwargs['headers'].update(self.credentials_headers()) if osprofiler_web: kwargs['headers'].update(osprofiler_web.get_trace_id_headers()) @@ -188,10 +198,14 @@ class HTTPClient(object): # See issue: https://github.com/kennethreitz/requests/issues/1704 allow_redirects = False + # Use fully qualified URL from response header for redirects + if not parse.urlparse(url).netloc: + url = self.endpoint_url + url + try: resp = requests.request( method, - self.endpoint_url + url, + url, allow_redirects=allow_redirects, **kwargs) except socket.gaierror as e: @@ -206,17 +220,11 @@ class HTTPClient(object): self.log_http_response(resp) - if not 'X-Auth-Key' in kwargs['headers'] and \ - (resp.status_code == 401 or - (resp.status_code == 500 and "(HTTP 401)" in resp.content)): - raise exc.HTTPUnauthorized(_("Authentication failed. Please try" - " again with option %(option)s or " - "export %(var)s\n%(content)s") % - { - 'option': '--include-password', - 'var': 'HEAT_INCLUDE_PASSWORD=1', - 'content': resp.content - }) + if not ('X-Auth-Key' in kwargs['headers']) and ( + resp.status_code == 401 or + (resp.status_code == 500 and "(HTTP 401)" in resp.content)): + raise exc.HTTPUnauthorized(_("Authentication failed: %s") + % resp.content) elif 400 <= resp.status_code < 600: raise exc.from_response(resp) elif resp.status_code in (301, 302, 305): @@ -224,31 +232,17 @@ class HTTPClient(object): # unless caller specified redirect=False if redirect: location = resp.headers.get('location') - path = self.strip_endpoint(location) - resp = self._http_request(path, method, **kwargs) + if not location: + message = _("Location not returned with redirect") + raise exc.InvalidEndpoint(message=message) + resp = self._http_request(location, method, **kwargs) elif resp.status_code == 300: raise exc.from_response(resp) return resp - def strip_endpoint(self, location): - if location is None: - message = _("Location not returned with 302") - raise exc.InvalidEndpoint(message=message) - elif location.lower().startswith(self.endpoint.lower()): - return location[len(self.endpoint):] - else: - message = _("Prohibited endpoint redirect %s") % location - raise exc.InvalidEndpoint(message=message) - def credentials_headers(self): creds = {} - # NOTE(dhu): (shardy) When deferred_auth_method=password, Heat - # encrypts and stores username/password. For Keystone v3, the - # intent is to use trusts since SHARDY is working towards - # deferred_auth_method=trusts as the default. - # TODO(dhu): Make Keystone v3 work in Heat standalone mode. Maye - # require X-Auth-User-Domain. if self.username: creds['X-Auth-User'] = self.username if self.password: @@ -264,7 +258,7 @@ class HTTPClient(object): kwargs['data'] = jsonutils.dumps(kwargs['data']) resp = self._http_request(url, method, **kwargs) - body = utils.get_response_body(resp) + body = get_response_body(resp) return resp, body def raw_request(self, method, url, **kwargs): @@ -303,10 +297,8 @@ class SessionClient(adapter.LegacyJsonAdapter): redirect = kwargs.get('redirect') kwargs.setdefault('user_agent', USER_AGENT) - try: - kwargs.setdefault('json', kwargs.pop('data')) - except KeyError: - pass + if 'data' in kwargs: + kwargs['data'] = jsonutils.dumps(kwargs['data']) resp, body = super(SessionClient, self).request( url, method, @@ -346,7 +338,15 @@ def _construct_http_client(endpoint=None, username=None, password=None, auth = kwargs.pop('auth', None) if session: - kwargs['endpoint_override'] = endpoint + if 'endpoint_override' not in kwargs and endpoint: + kwargs['endpoint_override'] = endpoint + + if 'service_type' not in kwargs: + kwargs['service_type'] = 'registration' + + if 'interface' not in kwargs and endpoint_type: + kwargs['interface'] = endpoint_type + return SessionClient(session, auth=auth, **kwargs) else: return HTTPClient(endpoint=endpoint, username=username, diff --git a/stacktaskclient/exc.py b/adjutantclient/exc.py similarity index 96% rename from stacktaskclient/exc.py rename to adjutantclient/exc.py index 9717019..d46b9fc 100644 --- a/stacktaskclient/exc.py +++ b/adjutantclient/exc.py @@ -14,7 +14,7 @@ import sys from oslo_serialization import jsonutils -from stacktaskclient.openstack.common._i18n import _ +from adjutantclient._i18n import _ verbose = 0 @@ -48,7 +48,7 @@ class HTTPException(BaseException): super(HTTPException, self).__init__(message) try: self.error = jsonutils.loads(message) - # Stacktask client: mangle the 'errors' return list into + # Adjutant client: mangle the 'errors' return list into # standard 'error' format if 'errors' in self.error: self.error['error'] = { @@ -81,7 +81,7 @@ class HTTPMultipleChoices(HTTPException): code = 300 def __str__(self): - self.details = _("Requested version of Stacktask API is not" + self.details = _("Requested version of Adjutant API is not" "available.") return (_("%(name)s (HTTP %(code)s) %(details)s") % { diff --git a/stacktaskclient/openstack/__init__.py b/adjutantclient/osc/__init__.py similarity index 100% rename from stacktaskclient/openstack/__init__.py rename to adjutantclient/osc/__init__.py diff --git a/stacktaskclient/osc/plugin.py b/adjutantclient/osc/plugin.py similarity index 89% rename from stacktaskclient/osc/plugin.py rename to adjutantclient/osc/plugin.py index ded490a..00f16ad 100644 --- a/stacktaskclient/osc/plugin.py +++ b/adjutantclient/osc/plugin.py @@ -23,12 +23,12 @@ DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_registration_version' API_NAME = "registration" API_VERSIONS = { - "1": "stacktaskclient.v1.client.Client", + "1": "adjutantclient.v1.client.Client", } def make_client(instance): - """Returns an stacktask service client.""" + """Returns an adjutant service client.""" version = instance._api_version[API_NAME] try: version = int(version) @@ -37,12 +37,12 @@ def make_client(instance): version = 1 - stacktask_client = utils.get_client_class( + adjutant_client = utils.get_client_class( API_NAME, version, API_VERSIONS) - LOG.debug('Instantiating stacktask client: %s', stacktask_client) + LOG.debug('Instantiating adjutant client: %s', adjutant_client) kwargs = {'region_name': instance.region_name} @@ -61,7 +61,7 @@ def make_client(instance): 'username': instance.auth_ref.username, 'token': instance.auth_ref.auth_token}) - client = stacktask_client(**kwargs) + client = adjutant_client(**kwargs) return client diff --git a/stacktaskclient/openstack/common/__init__.py b/adjutantclient/osc/v1/__init__.py similarity index 100% rename from stacktaskclient/openstack/common/__init__.py rename to adjutantclient/osc/v1/__init__.py diff --git a/stacktaskclient/osc/v1/notifications.py b/adjutantclient/osc/v1/notifications.py similarity index 98% rename from stacktaskclient/osc/v1/notifications.py rename to adjutantclient/osc/v1/notifications.py index df2c9da..cc74703 100644 --- a/stacktaskclient/osc/v1/notifications.py +++ b/adjutantclient/osc/v1/notifications.py @@ -32,7 +32,7 @@ def _show_notification(notification_id, client, formatter): class NotificationList(command.Lister): - """Lists stacktask notifications. """ + """Lists adjutant notifications. """ def get_parser(self, prog_name): parser = super(NotificationList, self).get_parser(prog_name) diff --git a/stacktaskclient/osc/v1/signup.py b/adjutantclient/osc/v1/signup.py similarity index 95% rename from stacktaskclient/osc/v1/signup.py rename to adjutantclient/osc/v1/signup.py index 1372736..707d72f 100644 --- a/stacktaskclient/osc/v1/signup.py +++ b/adjutantclient/osc/v1/signup.py @@ -18,7 +18,7 @@ import json from osc_lib.command import command from osc_lib.i18n import _ -from stacktaskclient import client as stacktask_client +from adjutantclient import client as adjutant_client LOG = logging.getLogger(__name__) @@ -60,7 +60,7 @@ class Signup(command.Command): self.app.client_manager.setup_auth() client = self.app.client_manager.registration else: - client = stacktask_client.Client(1, parsed_args.bypass_url) + client = adjutant_client.Client(1, parsed_args.bypass_url) signup = client.signup.post(parsed_args.user, parsed_args.email, parsed_args.project_name, diff --git a/stacktaskclient/osc/v1/status.py b/adjutantclient/osc/v1/status.py similarity index 96% rename from stacktaskclient/osc/v1/status.py rename to adjutantclient/osc/v1/status.py index 646ef8b..41a92d3 100644 --- a/stacktaskclient/osc/v1/status.py +++ b/adjutantclient/osc/v1/status.py @@ -22,7 +22,7 @@ LOG = logging.getLogger(__name__) class Status(command.Command): - """Lists stacktask tasks. """ + """Lists adjutant tasks. """ def take_action(self, parsed_args): client = self.app.client_manager.registration diff --git a/stacktaskclient/osc/v1/tasks.py b/adjutantclient/osc/v1/tasks.py similarity index 99% rename from stacktaskclient/osc/v1/tasks.py rename to adjutantclient/osc/v1/tasks.py index 74b1dbc..e23e550 100644 --- a/stacktaskclient/osc/v1/tasks.py +++ b/adjutantclient/osc/v1/tasks.py @@ -37,7 +37,7 @@ def _show_task(task_id, client, formatter): class TaskList(command.Lister): - """Lists stacktask tasks. """ + """Lists adjutant tasks. """ def get_parser(self, prog_name): parser = super(TaskList, self).get_parser(prog_name) diff --git a/stacktaskclient/osc/v1/tokens.py b/adjutantclient/osc/v1/tokens.py similarity index 95% rename from stacktaskclient/osc/v1/tokens.py rename to adjutantclient/osc/v1/tokens.py index ebe5e53..38d6d83 100644 --- a/stacktaskclient/osc/v1/tokens.py +++ b/adjutantclient/osc/v1/tokens.py @@ -19,7 +19,7 @@ import six from osc_lib.command import command from osc_lib.i18n import _ -from stacktaskclient import client as stacktask_client +from adjutantclient import client as adjutant_client LOG = logging.getLogger(__name__) @@ -35,7 +35,7 @@ def _list_tokens(client, filters={}): class TokenList(command.Lister): - """Lists stacktask tokens. """ + """Lists adjutant tokens. """ def get_parser(self, prog_name): parser = super(TokenList, self).get_parser(prog_name) @@ -73,7 +73,7 @@ class TokenShow(command.ShowOne): self.app.client_manager.setup_auth() client = self.app.client_manager.registration else: - client = stacktask_client.Client("1", parsed_args.bypass_url) + client = adjutant_client.Client("1", parsed_args.bypass_url) token = client.tokens.get(parsed_args.token) return zip(*six.iteritems(token.to_dict())) diff --git a/stacktaskclient/osc/v1/users.py b/adjutantclient/osc/v1/users.py similarity index 98% rename from stacktaskclient/osc/v1/users.py rename to adjutantclient/osc/v1/users.py index 24b5bce..15e8cf7 100644 --- a/stacktaskclient/osc/v1/users.py +++ b/adjutantclient/osc/v1/users.py @@ -19,7 +19,7 @@ from osc_lib import utils from osc_lib.command import command from osc_lib.i18n import _ -from stacktaskclient import client as stacktask_client +from adjutantclient import client as adjutant_client LOG = logging.getLogger(__name__) @@ -238,7 +238,7 @@ class PasswordForgot(command.Command): self.app.client_manager.setup_auth() client = self.app.client_manager.registration else: - client = stacktask_client.Client(1, parsed_args.bypass_url) + client = adjutant_client.Client(1, parsed_args.bypass_url) client.users.password_forgot(parsed_args.email, parsed_args.username) print("Task has been sucessfully submitted.") diff --git a/stacktaskclient/v1/__init__.py b/adjutantclient/v1/__init__.py similarity index 92% rename from stacktaskclient/v1/__init__.py rename to adjutantclient/v1/__init__.py index 507adf6..817886e 100644 --- a/stacktaskclient/v1/__init__.py +++ b/adjutantclient/v1/__init__.py @@ -15,4 +15,4 @@ __all__ = ['Client'] -from stacktaskclient.v1.client import Client # noqa +from adjutantclient.v1.client import Client # noqa diff --git a/stacktaskclient/v1/client.py b/adjutantclient/v1/client.py similarity index 78% rename from stacktaskclient/v1/client.py rename to adjutantclient/v1/client.py index c409c64..c0d848d 100644 --- a/stacktaskclient/v1/client.py +++ b/adjutantclient/v1/client.py @@ -12,20 +12,20 @@ # License for the specific language governing permissions and limitations # under the License. -from stacktaskclient.common import http -from stacktaskclient.v1 import notifications -from stacktaskclient.v1 import roles -from stacktaskclient.v1 import signup -from stacktaskclient.v1 import status -from stacktaskclient.v1 import tasks -from stacktaskclient.v1 import tokens -from stacktaskclient.v1 import users +from adjutantclient.common import http +from adjutantclient.v1 import notifications +from adjutantclient.v1 import roles +from adjutantclient.v1 import signup +from adjutantclient.v1 import status +from adjutantclient.v1 import tasks +from adjutantclient.v1 import tokens +from adjutantclient.v1 import users class Client(object): - """Client for the Stacktask v1 API. + """Client for the Adjutant v1 API. - :param string endpoint: A user-supplied endpoint URL for the stacktask + :param string endpoint: A user-supplied endpoint URL for the adjutant service. :param string token: Token for authentication. :param integer timeout: Allows customization of the timeout for client @@ -33,7 +33,7 @@ class Client(object): """ def __init__(self, *args, **kwargs): - """Initialize a new client for the Stacktask v1 API.""" + """Initialize a new client for the Adjutant v1 API.""" self.http_client = http._construct_http_client(*args, **kwargs) self.users = users.UserManager(self.http_client) diff --git a/stacktaskclient/v1/notifications.py b/adjutantclient/v1/notifications.py similarity index 96% rename from stacktaskclient/v1/notifications.py rename to adjutantclient/v1/notifications.py index b055f93..3419cd6 100644 --- a/stacktaskclient/v1/notifications.py +++ b/adjutantclient/v1/notifications.py @@ -14,7 +14,7 @@ from six.moves.urllib import parse -from stacktaskclient.openstack.common.apiclient import base +from adjutantclient.common import base class Notification(base.Resource): diff --git a/stacktaskclient/v1/roles.py b/adjutantclient/v1/roles.py similarity index 96% rename from stacktaskclient/v1/roles.py rename to adjutantclient/v1/roles.py index 9eeba7b..0dacf39 100644 --- a/stacktaskclient/v1/roles.py +++ b/adjutantclient/v1/roles.py @@ -14,8 +14,8 @@ from six.moves.urllib import parse -from stacktaskclient.openstack.common.apiclient import base -from stacktaskclient import exc +from adjutantclient.common import base +from adjutantclient import exc class Role(base.Resource): @@ -50,7 +50,7 @@ class ManagedRoleManager(base.ManagerWithFind): for role in roles: if role.id == role_id: return role - raise exc.NotFound() + raise exc.HTTPNotFound() class UserRoleManager(base.BaseManager): diff --git a/stacktaskclient/v1/signup.py b/adjutantclient/v1/signup.py similarity index 94% rename from stacktaskclient/v1/signup.py rename to adjutantclient/v1/signup.py index e0eddcc..510ae45 100644 --- a/stacktaskclient/v1/signup.py +++ b/adjutantclient/v1/signup.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from stacktaskclient.openstack.common.apiclient import base +from adjutantclient.common import base class SignupManager(base.BaseManager): diff --git a/stacktaskclient/v1/status.py b/adjutantclient/v1/status.py similarity index 92% rename from stacktaskclient/v1/status.py rename to adjutantclient/v1/status.py index 3f11120..c41c757 100644 --- a/stacktaskclient/v1/status.py +++ b/adjutantclient/v1/status.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from stacktaskclient.openstack.common.apiclient import base +from adjutantclient.common import base class StatusManager(base.BaseManager): diff --git a/stacktaskclient/v1/tasks.py b/adjutantclient/v1/tasks.py similarity index 96% rename from stacktaskclient/v1/tasks.py rename to adjutantclient/v1/tasks.py index 9990616..351ae64 100644 --- a/stacktaskclient/v1/tasks.py +++ b/adjutantclient/v1/tasks.py @@ -14,7 +14,7 @@ from six.moves.urllib import parse -from stacktaskclient.openstack.common.apiclient import base +from adjutantclient.common import base class Task(base.Resource): diff --git a/stacktaskclient/v1/tokens.py b/adjutantclient/v1/tokens.py similarity index 96% rename from stacktaskclient/v1/tokens.py rename to adjutantclient/v1/tokens.py index 7c1d9ef..d79270b 100644 --- a/stacktaskclient/v1/tokens.py +++ b/adjutantclient/v1/tokens.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from stacktaskclient.openstack.common.apiclient import base +from adjutantclient.common import base from six.moves.urllib import parse diff --git a/stacktaskclient/v1/users.py b/adjutantclient/v1/users.py similarity index 98% rename from stacktaskclient/v1/users.py rename to adjutantclient/v1/users.py index c58f4cb..f8dfc0f 100644 --- a/stacktaskclient/v1/users.py +++ b/adjutantclient/v1/users.py @@ -15,7 +15,7 @@ import six from six.moves.urllib import parse -from stacktaskclient.openstack.common.apiclient import base +from adjutantclient.common import base class User(base.Resource): diff --git a/requirements.txt b/requirements.txt index 484f39f..e85e7cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,14 +2,17 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -Babel>=1.3 -pbr>=3.0.0 -iso8601>=0.1.9 -PrettyTable<0.8,>=0.7 -oslo.i18n>=2.1.0 -oslo.serialization>=1.10.0 -oslo.utils>=3.16.0 -keystoneauth1>=2.11.0 -PyYAML>=3.10.0 -requests>=2.10.0 -six>=1.9.0 +Babel!=2.4.0,>=2.3.4 # BSD +pbr>=3.0.0 # Apache-2.0 +cliff>=2.6.0 # Apache-2.0 +iso8601>=0.1.11 # MIT +osc-lib>=1.5.1 # Apache-2.0 +PrettyTable<0.8,>=0.7.1 # BSD +python-openstackclient>=3.11.0 +oslo.i18n>=2.1.0 # Apache-2.0 +oslo.serialization>=1.10.0 # Apache-2.0 +oslo.utils>=3.20.0 # Apache-2.0 +keystoneauth1>=2.20.0 # Apache-2.0 +PyYAML>=3.10.0 # MIT +requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 +six>=1.9.0 # MIT diff --git a/setup.cfg b/setup.cfg index 3a23920..b61735a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,11 @@ [metadata] -name = python-stacktaskclient -summary = StackTask API Client Library +name = python-adjutantclient +summary = Adjutant API Client Library description-file = README.rst -author = Dale Smith -author-email = dale@catalyst-eu.net -home-page = https://github.com/catalyst/stacktask-client +author = Adrian Turjak +author-email = adriant@catalyst.net.nz +home-page = https://github.com/catalyst/python-adjutantclient classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -19,41 +19,38 @@ classifier = [files] packages = - stacktaskclient + adjutantclient [entry_points] -console_scripts = - stacktask = stacktaskclient.shell:main - openstack.cli.extension = - registration = stacktaskclient.osc.plugin + registration = adjutantclient.osc.plugin openstack.registration.v1 = - admin_task_list = stacktaskclient.osc.v1.tasks:TaskList - admin_task_show = stacktaskclient.osc.v1.tasks:TaskShow - admin_task_approve = stacktaskclient.osc.v1.tasks:TaskApprove - admin_task_cancel = stacktaskclient.osc.v1.tasks:TaskCancel - admin_task_update = stacktaskclient.osc.v1.tasks:TaskUpdate - admin_task_token_reissue = stacktaskclient.osc.v1.tasks:TaskTokenReissue - admin_task_token_list = stacktaskclient.osc.v1.tokens:TokenList - admin_task_token_show = stacktaskclient.osc.v1.tokens:TokenShow - admin_task_token_submit = stacktaskclient.osc.v1.tokens:TokenSubmit - admin_task_tokens_clear = stacktaskclient.osc.v1.tokens:TokenClear - admin_task_notification_list = stacktaskclient.osc.v1.notifications:NotificationList - admin_task_notification_show = stacktaskclient.osc.v1.notifications:NotificationShow - admin_task_notification_acknowledge = stacktaskclient.osc.v1.notifications:NotificationAcknowledge - project_user_list = stacktaskclient.osc.v1.users:UserList - project_user_show = stacktaskclient.osc.v1.users:UserShow - project_user_invite = stacktaskclient.osc.v1.users:UserInvite - project_user_invite_cancel = stacktaskclient.osc.v1.users:UserInviteCancel - project_user_role_list = stacktaskclient.osc.v1.users:UserRoleList - project_user_role_add = stacktaskclient.osc.v1.users:UserRoleAdd - project_user_role_remove = stacktaskclient.osc.v1.users:UserRoleRemove - project_manageable_roles = stacktaskclient.osc.v1.users:ManageableRolesList - password_forgot = stacktaskclient.osc.v1.users:PasswordForgot - password_reset = stacktaskclient.osc.v1.users:PasswordReset - signup = stacktaskclient.osc.v1.signup:Signup - stacktask_status = stacktaskclient.osc.v1.status:Status + admin_task_list = adjutantclient.osc.v1.tasks:TaskList + admin_task_show = adjutantclient.osc.v1.tasks:TaskShow + admin_task_approve = adjutantclient.osc.v1.tasks:TaskApprove + admin_task_cancel = adjutantclient.osc.v1.tasks:TaskCancel + admin_task_update = adjutantclient.osc.v1.tasks:TaskUpdate + admin_task_token_reissue = adjutantclient.osc.v1.tasks:TaskTokenReissue + admin_task_token_list = adjutantclient.osc.v1.tokens:TokenList + admin_task_token_show = adjutantclient.osc.v1.tokens:TokenShow + admin_task_token_submit = adjutantclient.osc.v1.tokens:TokenSubmit + admin_task_tokens_clear = adjutantclient.osc.v1.tokens:TokenClear + admin_task_notification_list = adjutantclient.osc.v1.notifications:NotificationList + admin_task_notification_show = adjutantclient.osc.v1.notifications:NotificationShow + admin_task_notification_acknowledge = adjutantclient.osc.v1.notifications:NotificationAcknowledge + project_user_list = adjutantclient.osc.v1.users:UserList + project_user_show = adjutantclient.osc.v1.users:UserShow + project_user_invite = adjutantclient.osc.v1.users:UserInvite + project_user_invite_cancel = adjutantclient.osc.v1.users:UserInviteCancel + project_user_role_list = adjutantclient.osc.v1.users:UserRoleList + project_user_role_add = adjutantclient.osc.v1.users:UserRoleAdd + project_user_role_remove = adjutantclient.osc.v1.users:UserRoleRemove + project_manageable_roles = adjutantclient.osc.v1.users:ManageableRolesList + password_forgot = adjutantclient.osc.v1.users:PasswordForgot + password_reset = adjutantclient.osc.v1.users:PasswordReset + signup = adjutantclient.osc.v1.signup:Signup + adjutant_status = adjutantclient.osc.v1.status:Status [global] setup-hooks = diff --git a/stacktaskclient/common/utils.py b/stacktaskclient/common/utils.py deleted file mode 100644 index 810fa2f..0000000 --- a/stacktaskclient/common/utils.py +++ /dev/null @@ -1,345 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# All 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 base64 -from fcntl import ioctl -import logging -import os -import textwrap -import uuid -import six -import struct -import sys -import termios - -from oslo_serialization import jsonutils -from oslo_utils import importutils -import prettytable -from six.moves.urllib import error -from six.moves.urllib import parse -from six.moves.urllib import request -import yaml - -from stacktaskclient import exc -from stacktaskclient.openstack.common._i18n import _ -from stacktaskclient.openstack.common._i18n import _LE -from stacktaskclient.openstack.common import cliutils - -LOG = logging.getLogger(__name__) - - -supported_formats = { - "json": lambda x: jsonutils.dumps(x, indent=2), - "yaml": yaml.safe_dump -} - -# Using common methods from oslo cliutils -arg = cliutils.arg -env = cliutils.env -print_list = cliutils.print_list - - -def link_formatter(links): - def format_link(l): - if 'rel' in l: - return "%s (%s)" % (l.get('href', ''), l.get('rel', '')) - else: - return "%s" % (l.get('href', '')) - return '\n'.join(format_link(l) for l in links or []) - - -def resource_nested_identifier(rsrc): - nested_link = [l for l in rsrc.links or [] - if l.get('rel') == 'nested'] - if nested_link: - nested_href = nested_link[0].get('href') - nested_identifier = nested_href.split("/")[-2:] - return "/".join(nested_identifier) - - -def json_formatter(js, wrap=None): - value = jsonutils.dumps(js, indent=2, ensure_ascii=False, - separators=(', ', ': ')) - # as json sort of does it's own line splitting, we have to check - # if each line is over the wrap limit, and split ourselves. - if wrap: - lines = [] - for line in value.split('\n'): - if len(line) > wrap: - lines.append(line[0:wrap-1]) - line = line[wrap:] - while line: - lines.append(line[0:wrap-1]) - line = line[wrap:] - else: - lines.append(line) - value = "" - for line in lines: - value += "%s\n" % line - return value - - -def text_wrap_formatter(d): - return '\n'.join(textwrap.wrap(d or '', 55)) - - -def newline_list_formatter(r): - return '\n'.join(r or []) - - -def print_dict(d, formatters=None, wrap=None): - if not wrap: - # 2 columns padded by 1 on each side = 4 - # 3 x '|' as border and separator = 3 - # total non-content padding = 7 - padding = 7 - # Now we need to find what the longest key is - longest_key = 0 - for key in d.keys(): - if len(key) > longest_key: - longest_key = len(key) - # the wrap for the value column is based on - # what is left after we account for the padding - # and longest key - wrap = terminal_width() - padding - longest_key - - formatters = formatters or {} - pt = prettytable.PrettyTable(['Property', 'Value'], - caching=False, print_empty=False) - pt.align = 'l' - - for field in d.keys(): - if field in formatters: - value = formatters[field](d[field], wrap) - pt.add_row([field, value]) - else: - value = textwrap.fill(six.text_type(d[field]), wrap) - pt.add_row([field, value]) - print(pt.get_string(sortby='Property')) - - -def event_log_formatter(events): - """Return the events in log format.""" - event_log = [] - log_format = _("%(event_date)s %(event_time)s %(event_id)s " - "[%(rsrc_name)s]: %(rsrc_status)s %(rsrc_status_reason)s") - for event in events: - event_time = getattr(event, 'event_time', '') - time_date = event_time.split('T') - try: - event_time = time_date[0] - event_date = time_date[1][:-1] - except IndexError: - event_time = event_date = '' - - log = log_format % { - 'event_date': event_date, 'event_time': event_time, - 'event_id': getattr(event, 'id', ''), - 'rsrc_name': getattr(event, 'resource_name', ''), - 'rsrc_status': getattr(event, 'resource_status', ''), - 'rsrc_status_reason': getattr(event, 'resource_status_reason', '') - } - event_log.append(log) - - return "\n".join(event_log) - - -def find_resource(manager, name_or_id): - """Helper for the _find_* methods.""" - # first try to get entity as integer id - try: - if isinstance(name_or_id, int) or name_or_id.isdigit(): - return manager.get(int(name_or_id)) - except exc.NotFound: - pass - - # now try to get entity as uuid - try: - uuid.UUID(str(name_or_id)) - return manager.get(name_or_id) - except (ValueError, exc.NotFound): - pass - - # finally try to find entity by name - try: - return manager.find(name=name_or_id) - except exc.NotFound: - msg = _("No %(name)s with a name or ID of " - "'%(name_or_id)s' exists.") % \ - { - 'name': manager.resource_class.__name__.lower(), - 'name_or_id': name_or_id - } - raise exc.CommandError(msg) - - -def import_versioned_module(version, submodule=None): - module = 'stacktaskclient.v%s' % version - if submodule: - module = '.'.join((module, submodule)) - return importutils.import_module(module) - - -def format_parameters(params, parse_semicolon=True): - '''Reformat parameters into dict of format expected by the API.''' - - if not params: - return {} - - if parse_semicolon: - # expect multiple invocations of --parameters but fall back - # to ; delimited if only one --parameters is specified - if len(params) == 1: - params = params[0].split(';') - - parameters = {} - for p in params: - try: - (n, v) = p.split(('='), 1) - except ValueError: - msg = _('Malformed parameter(%s). Use the key=value format.') % p - raise exc.CommandError(msg) - - if n not in parameters: - parameters[n] = v - else: - if not isinstance(parameters[n], list): - parameters[n] = [parameters[n]] - parameters[n].append(v) - - return parameters - - -def format_all_parameters(params, param_files, - template_file=None, template_url=None): - parameters = {} - parameters.update(format_parameters(params)) - parameters.update(format_parameter_file( - param_files, - template_file, - template_url)) - return parameters - - -def format_parameter_file(param_files, template_file=None, - template_url=None): - '''Reformat file parameters into dict of format expected by the API.''' - if not param_files: - return {} - params = format_parameters(param_files, False) - - template_base_url = None - if template_file or template_url: - template_base_url = base_url_for_url(get_template_url( - template_file, template_url)) - - param_file = {} - for key, value in iter(params.items()): - param_file[key] = resolve_param_get_file(value, - template_base_url) - return param_file - - -def resolve_param_get_file(file, base_url): - if base_url and not base_url.endswith('/'): - base_url = base_url + '/' - str_url = parse.urljoin(base_url, file) - return read_url_content(str_url) - - -def format_output(output, format='yaml'): - """Format the supplied dict as specified.""" - output_format = format.lower() - try: - return supported_formats[output_format](output) - except KeyError: - raise exc.HTTPUnsupported(_("The format(%s) is unsupported.") - % output_format) - - -def parse_query_url(url): - base_url, query_params = url.split('?') - return base_url, parse.parse_qs(query_params) - - -def get_template_url(template_file=None, template_url=None): - if template_file: - template_url = normalise_file_path_to_url(template_file) - return template_url - - -def read_url_content(url): - try: - content = request.urlopen(url).read() - except error.URLError: - raise exc.CommandError(_('Could not fetch contents for %s') % url) - - if content: - try: - content.decode('utf-8') - except ValueError: - content = base64.encodestring(content) - return content - - -def base_url_for_url(url): - parsed = parse.urlparse(url) - parsed_dir = os.path.dirname(parsed.path) - return parse.urljoin(url, parsed_dir) - - -def normalise_file_path_to_url(path): - if parse.urlparse(path).scheme: - return path - path = os.path.abspath(path) - return parse.urljoin('file:', request.pathname2url(path)) - - -def get_response_body(resp): - body = resp.content - if 'application/json' in resp.headers.get('content-type', ''): - try: - body = resp.json() - except ValueError: - LOG.error(_LE('Could not decode response body as JSON')) - else: - body = None - return body - - -def terminal_width(): - if hasattr(os, 'get_terminal_size'): - # python 3.3 onwards has built-in support for getting terminal size - try: - return os.get_terminal_size().columns - except OSError: - return None - try: - # winsize structure has 4 unsigned short fields - winsize = b'\0' * struct.calcsize('hhhh') - try: - winsize = ioctl(sys.stdout, termios.TIOCGWINSZ, winsize) - except IOError: - return None - except TypeError: - # this is raised in unit tests as stdout is sometimes a StringIO - return None - winsize = struct.unpack('hhhh', winsize) - columns = winsize[1] - if not columns: - return None - return columns - except IOError: - return None diff --git a/stacktaskclient/openstack/common/_i18n.py b/stacktaskclient/openstack/common/_i18n.py deleted file mode 100644 index e3f8323..0000000 --- a/stacktaskclient/openstack/common/_i18n.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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. - -"""oslo.i18n integration module. - -See http://docs.openstack.org/developer/oslo.i18n/usage.html - -""" - -try: - import oslo_i18n - - # NOTE(dhellmann): This reference to o-s-l-o will be replaced by the - # application name when this module is synced into the separate - # repository. It is OK to have more than one translation function - # using the same domain, since there will still only be one message - # catalog. - _translators = oslo_i18n.TranslatorFactory(domain='stacktaskclient') - - # The primary translation function using the well-known name "_" - _ = _translators.primary - - # Translators for log levels. - # - # The abbreviated names are meant to reflect the usual use of a short - # name like '_'. The "L" is for "log" and the other letter comes from - # the level. - _LI = _translators.log_info - _LW = _translators.log_warning - _LE = _translators.log_error - _LC = _translators.log_critical -except ImportError: - # NOTE(dims): Support for cases where a project wants to use - # code from oslo-incubator, but is not ready to be internationalized - # (like tempest) - _ = _LI = _LW = _LE = _LC = lambda x: x diff --git a/stacktaskclient/openstack/common/apiclient/__init__.py b/stacktaskclient/openstack/common/apiclient/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacktaskclient/openstack/common/apiclient/auth.py b/stacktaskclient/openstack/common/apiclient/auth.py deleted file mode 100644 index 1bd12d5..0000000 --- a/stacktaskclient/openstack/common/apiclient/auth.py +++ /dev/null @@ -1,234 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 Spanish National Research Council. -# All 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. - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -import abc -import argparse -import os - -import six -from stevedore import extension - -from stacktaskclient.openstack.common.apiclient import exceptions - - -_discovered_plugins = {} - - -def discover_auth_systems(): - """Discover the available auth-systems. - - This won't take into account the old style auth-systems. - """ - global _discovered_plugins - _discovered_plugins = {} - - def add_plugin(ext): - _discovered_plugins[ext.name] = ext.plugin - - ep_namespace = "stacktaskclient.openstack.common.apiclient.auth" - mgr = extension.ExtensionManager(ep_namespace) - mgr.map(add_plugin) - - -def load_auth_system_opts(parser): - """Load options needed by the available auth-systems into a parser. - - This function will try to populate the parser with options from the - available plugins. - """ - group = parser.add_argument_group("Common auth options") - BaseAuthPlugin.add_common_opts(group) - for name, auth_plugin in six.iteritems(_discovered_plugins): - group = parser.add_argument_group( - "Auth-system '%s' options" % name, - conflict_handler="resolve") - auth_plugin.add_opts(group) - - -def load_plugin(auth_system): - try: - plugin_class = _discovered_plugins[auth_system] - except KeyError: - raise exceptions.AuthSystemNotFound(auth_system) - return plugin_class(auth_system=auth_system) - - -def load_plugin_from_args(args): - """Load required plugin and populate it with options. - - Try to guess auth system if it is not specified. Systems are tried in - alphabetical order. - - :type args: argparse.Namespace - :raises: AuthPluginOptionsMissing - """ - auth_system = args.os_auth_system - if auth_system: - plugin = load_plugin(auth_system) - plugin.parse_opts(args) - plugin.sufficient_options() - return plugin - - for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)): - plugin_class = _discovered_plugins[plugin_auth_system] - plugin = plugin_class() - plugin.parse_opts(args) - try: - plugin.sufficient_options() - except exceptions.AuthPluginOptionsMissing: - continue - return plugin - raise exceptions.AuthPluginOptionsMissing(["auth_system"]) - - -@six.add_metaclass(abc.ABCMeta) -class BaseAuthPlugin(object): - """Base class for authentication plugins. - - An authentication plugin needs to override at least the authenticate - method to be a valid plugin. - """ - - auth_system = None - opt_names = [] - common_opt_names = [ - "auth_system", - "username", - "password", - "tenant_name", - "token", - "auth_url", - ] - - def __init__(self, auth_system=None, **kwargs): - self.auth_system = auth_system or self.auth_system - self.opts = dict((name, kwargs.get(name)) - for name in self.opt_names) - - @staticmethod - def _parser_add_opt(parser, opt): - """Add an option to parser in two variants. - - :param opt: option name (with underscores) - """ - dashed_opt = opt.replace("_", "-") - env_var = "OS_%s" % opt.upper() - arg_default = os.environ.get(env_var, "") - arg_help = "Defaults to env[%s]." % env_var - parser.add_argument( - "--os-%s" % dashed_opt, - metavar="<%s>" % dashed_opt, - default=arg_default, - help=arg_help) - parser.add_argument( - "--os_%s" % opt, - metavar="<%s>" % dashed_opt, - help=argparse.SUPPRESS) - - @classmethod - def add_opts(cls, parser): - """Populate the parser with the options for this plugin. - """ - for opt in cls.opt_names: - # use `BaseAuthPlugin.common_opt_names` since it is never - # changed in child classes - if opt not in BaseAuthPlugin.common_opt_names: - cls._parser_add_opt(parser, opt) - - @classmethod - def add_common_opts(cls, parser): - """Add options that are common for several plugins. - """ - for opt in cls.common_opt_names: - cls._parser_add_opt(parser, opt) - - @staticmethod - def get_opt(opt_name, args): - """Return option name and value. - - :param opt_name: name of the option, e.g., "username" - :param args: parsed arguments - """ - return (opt_name, getattr(args, "os_%s" % opt_name, None)) - - def parse_opts(self, args): - """Parse the actual auth-system options if any. - - This method is expected to populate the attribute `self.opts` with a - dict containing the options and values needed to make authentication. - """ - self.opts.update(dict(self.get_opt(opt_name, args) - for opt_name in self.opt_names)) - - def authenticate(self, http_client): - """Authenticate using plugin defined method. - - The method usually analyses `self.opts` and performs - a request to authentication server. - - :param http_client: client object that needs authentication - :type http_client: HTTPClient - :raises: AuthorizationFailure - """ - self.sufficient_options() - self._do_authenticate(http_client) - - @abc.abstractmethod - def _do_authenticate(self, http_client): - """Protected method for authentication. - """ - - def sufficient_options(self): - """Check if all required options are present. - - :raises: AuthPluginOptionsMissing - """ - missing = [opt - for opt in self.opt_names - if not self.opts.get(opt)] - if missing: - raise exceptions.AuthPluginOptionsMissing(missing) - - @abc.abstractmethod - def token_and_endpoint(self, endpoint_type, service_type): - """Return token and endpoint. - - :param service_type: Service type of the endpoint - :type service_type: string - :param endpoint_type: Type of endpoint. - Possible values: public or publicURL, - internal or internalURL, - admin or adminURL - :type endpoint_type: string - :returns: tuple of token and endpoint strings - :raises: EndpointException - """ diff --git a/stacktaskclient/openstack/common/apiclient/client.py b/stacktaskclient/openstack/common/apiclient/client.py deleted file mode 100644 index e414227..0000000 --- a/stacktaskclient/openstack/common/apiclient/client.py +++ /dev/null @@ -1,388 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 OpenStack Foundation -# Copyright 2011 Piston Cloud Computing, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 Grid Dynamics -# Copyright 2013 OpenStack Foundation -# All 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. - -""" -OpenStack Client interface. Handles the REST calls and responses. -""" - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -import hashlib -import logging -import time - -try: - import simplejson as json -except ImportError: - import json - -from oslo_utils import encodeutils -from oslo_utils import importutils -import requests - -from stacktaskclient.openstack.common._i18n import _ -from stacktaskclient.openstack.common.apiclient import exceptions - -_logger = logging.getLogger(__name__) -SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',) - - -class HTTPClient(object): - """This client handles sending HTTP requests to OpenStack servers. - - Features: - - - share authentication information between several clients to different - services (e.g., for compute and image clients); - - reissue authentication request for expired tokens; - - encode/decode JSON bodies; - - raise exceptions on HTTP errors; - - pluggable authentication; - - store authentication information in a keyring; - - store time spent for requests; - - register clients for particular services, so one can use - `http_client.identity` or `http_client.compute`; - - log requests and responses in a format that is easy to copy-and-paste - into terminal and send the same request with curl. - """ - - user_agent = "stacktaskclient.openstack.common.apiclient" - - def __init__(self, - auth_plugin, - region_name=None, - endpoint_type="publicURL", - original_ip=None, - verify=True, - cert=None, - timeout=None, - timings=False, - keyring_saver=None, - debug=False, - user_agent=None, - http=None): - self.auth_plugin = auth_plugin - - self.endpoint_type = endpoint_type - self.region_name = region_name - - self.original_ip = original_ip - self.timeout = timeout - self.verify = verify - self.cert = cert - - self.keyring_saver = keyring_saver - self.debug = debug - self.user_agent = user_agent or self.user_agent - - self.times = [] # [("item", starttime, endtime), ...] - self.timings = timings - - # requests within the same session can reuse TCP connections from pool - self.http = http or requests.Session() - - self.cached_token = None - self.last_request_id = None - - def _safe_header(self, name, value): - if name in SENSITIVE_HEADERS: - # because in python3 byte string handling is ... ug - v = value.encode('utf-8') - h = hashlib.sha1(v) - d = h.hexdigest() - return encodeutils.safe_decode(name), "{SHA1}%s" % d - else: - return (encodeutils.safe_decode(name), - encodeutils.safe_decode(value)) - - def _http_log_req(self, method, url, kwargs): - if not self.debug: - return - - string_parts = [ - "curl -g -i", - "-X '%s'" % method, - "'%s'" % url, - ] - - for element in kwargs['headers']: - header = ("-H '%s: %s'" % - self._safe_header(element, kwargs['headers'][element])) - string_parts.append(header) - - _logger.debug("REQ: %s" % " ".join(string_parts)) - if 'data' in kwargs: - _logger.debug("REQ BODY: %s\n" % (kwargs['data'])) - - def _http_log_resp(self, resp): - if not self.debug: - return - _logger.debug( - "RESP: [%s] %s\n", - resp.status_code, - resp.headers) - if resp._content_consumed: - _logger.debug( - "RESP BODY: %s\n", - resp.text) - - def serialize(self, kwargs): - if kwargs.get('json') is not None: - kwargs['headers']['Content-Type'] = 'application/json' - kwargs['data'] = json.dumps(kwargs['json']) - try: - del kwargs['json'] - except KeyError: - pass - - def get_timings(self): - return self.times - - def reset_timings(self): - self.times = [] - - def request(self, method, url, **kwargs): - """Send an http request with the specified characteristics. - - Wrapper around `requests.Session.request` to handle tasks such as - setting headers, JSON encoding/decoding, and error handling. - - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - requests.Session.request (such as `headers`) or `json` - that will be encoded as JSON and used as `data` argument - """ - kwargs.setdefault("headers", {}) - kwargs["headers"]["User-Agent"] = self.user_agent - if self.original_ip: - kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( - self.original_ip, self.user_agent) - if self.timeout is not None: - kwargs.setdefault("timeout", self.timeout) - kwargs.setdefault("verify", self.verify) - if self.cert is not None: - kwargs.setdefault("cert", self.cert) - self.serialize(kwargs) - - self._http_log_req(method, url, kwargs) - if self.timings: - start_time = time.time() - resp = self.http.request(method, url, **kwargs) - if self.timings: - self.times.append(("%s %s" % (method, url), - start_time, time.time())) - self._http_log_resp(resp) - - self.last_request_id = resp.headers.get('x-openstack-request-id') - - if resp.status_code >= 400: - _logger.debug( - "Request returned failure status: %s", - resp.status_code) - raise exceptions.from_response(resp, method, url) - - return resp - - @staticmethod - def concat_url(endpoint, url): - """Concatenate endpoint and final URL. - - E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to - "http://keystone/v2.0/tokens". - - :param endpoint: the base URL - :param url: the final URL - """ - return "%s/%s" % (endpoint.rstrip("/"), url.strip("/")) - - def client_request(self, client, method, url, **kwargs): - """Send an http request using `client`'s endpoint and specified `url`. - - If request was rejected as unauthorized (possibly because the token is - expired), issue one authorization attempt and send the request once - again. - - :param client: instance of BaseClient descendant - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - `HTTPClient.request` - """ - - filter_args = { - "endpoint_type": client.endpoint_type or self.endpoint_type, - "service_type": client.service_type, - } - token, endpoint = (self.cached_token, client.cached_endpoint) - just_authenticated = False - if not (token and endpoint): - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - pass - if not (token and endpoint): - self.authenticate() - just_authenticated = True - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - if not (token and endpoint): - raise exceptions.AuthorizationFailure( - _("Cannot find endpoint or token for request")) - - old_token_endpoint = (token, endpoint) - kwargs.setdefault("headers", {})["X-Auth-Token"] = token - self.cached_token = token - client.cached_endpoint = endpoint - # Perform the request once. If we get Unauthorized, then it - # might be because the auth token expired, so try to - # re-authenticate and try again. If it still fails, bail. - try: - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - except exceptions.Unauthorized as unauth_ex: - if just_authenticated: - raise - self.cached_token = None - client.cached_endpoint = None - if self.auth_plugin.opts.get('token'): - self.auth_plugin.opts['token'] = None - if self.auth_plugin.opts.get('endpoint'): - self.auth_plugin.opts['endpoint'] = None - self.authenticate() - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - raise unauth_ex - if (not (token and endpoint) or - old_token_endpoint == (token, endpoint)): - raise unauth_ex - self.cached_token = token - client.cached_endpoint = endpoint - kwargs["headers"]["X-Auth-Token"] = token - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - - def add_client(self, base_client_instance): - """Add a new instance of :class:`BaseClient` descendant. - - `self` will store a reference to `base_client_instance`. - - Example: - - >>> def test_clients(): - ... from keystoneclient.auth import keystone - ... from openstack.common.apiclient import client - ... auth = keystone.KeystoneAuthPlugin( - ... username="user", password="pass", tenant_name="tenant", - ... auth_url="http://auth:5000/v2.0") - ... openstack_client = client.HTTPClient(auth) - ... # create nova client - ... from novaclient.v1_1 import client - ... client.Client(openstack_client) - ... # create keystone client - ... from keystoneclient.v2_0 import client - ... client.Client(openstack_client) - ... # use them - ... openstack_client.identity.tenants.list() - ... openstack_client.compute.servers.list() - """ - service_type = base_client_instance.service_type - if service_type and not hasattr(self, service_type): - setattr(self, service_type, base_client_instance) - - def authenticate(self): - self.auth_plugin.authenticate(self) - # Store the authentication results in the keyring for later requests - if self.keyring_saver: - self.keyring_saver.save(self) - - -class BaseClient(object): - """Top-level object to access the OpenStack API. - - This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient` - will handle a bunch of issues such as authentication. - """ - - service_type = None - endpoint_type = None # "publicURL" will be used - cached_endpoint = None - - def __init__(self, http_client, extensions=None): - self.http_client = http_client - http_client.add_client(self) - - # Add in any extensions... - if extensions: - for extension in extensions: - if extension.manager_class: - setattr(self, extension.name, - extension.manager_class(self)) - - def client_request(self, method, url, **kwargs): - return self.http_client.client_request( - self, method, url, **kwargs) - - @property - def last_request_id(self): - return self.http_client.last_request_id - - def head(self, url, **kwargs): - return self.client_request("HEAD", url, **kwargs) - - def get(self, url, **kwargs): - return self.client_request("GET", url, **kwargs) - - def post(self, url, **kwargs): - return self.client_request("POST", url, **kwargs) - - def put(self, url, **kwargs): - return self.client_request("PUT", url, **kwargs) - - def delete(self, url, **kwargs): - return self.client_request("DELETE", url, **kwargs) - - def patch(self, url, **kwargs): - return self.client_request("PATCH", url, **kwargs) - - @staticmethod - def get_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = _("Invalid %(api_name)s client version '%(version)s'. " - "Must be one of: %(version_map)s") % { - 'api_name': api_name, - 'version': version, - 'version_map': ', '.join(version_map.keys())} - raise exceptions.UnsupportedVersion(msg) - - return importutils.import_class(client_path) diff --git a/stacktaskclient/openstack/common/apiclient/exceptions.py b/stacktaskclient/openstack/common/apiclient/exceptions.py deleted file mode 100644 index 7b8bc5d..0000000 --- a/stacktaskclient/openstack/common/apiclient/exceptions.py +++ /dev/null @@ -1,479 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 Nebula, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 OpenStack Foundation -# All 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. - -""" -Exception definitions. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -import inspect -import sys - -import six - -from stacktaskclient.openstack.common._i18n import _ - - -class ClientException(Exception): - """The base exception class for all exceptions this library raises. - """ - pass - - -class ValidationError(ClientException): - """Error in validation on API client side.""" - pass - - -class UnsupportedVersion(ClientException): - """User is trying to use an unsupported version of the API.""" - pass - - -class CommandError(ClientException): - """Error in CLI tool.""" - pass - - -class AuthorizationFailure(ClientException): - """Cannot authorize API client.""" - pass - - -class ConnectionError(ClientException): - """Cannot connect to API service.""" - pass - - -class ConnectionRefused(ConnectionError): - """Connection refused while trying to connect to API service.""" - pass - - -class AuthPluginOptionsMissing(AuthorizationFailure): - """Auth plugin misses some options.""" - def __init__(self, opt_names): - super(AuthPluginOptionsMissing, self).__init__( - _("Authentication failed. Missing options: %s") % - ", ".join(opt_names)) - self.opt_names = opt_names - - -class AuthSystemNotFound(AuthorizationFailure): - """User has specified an AuthSystem that is not installed.""" - def __init__(self, auth_system): - super(AuthSystemNotFound, self).__init__( - _("AuthSystemNotFound: %r") % auth_system) - self.auth_system = auth_system - - -class NoUniqueMatch(ClientException): - """Multiple entities found instead of one.""" - pass - - -class EndpointException(ClientException): - """Something is rotten in Service Catalog.""" - pass - - -class EndpointNotFound(EndpointException): - """Could not find requested endpoint in Service Catalog.""" - pass - - -class AmbiguousEndpoints(EndpointException): - """Found more than one matching endpoint in Service Catalog.""" - def __init__(self, endpoints=None): - super(AmbiguousEndpoints, self).__init__( - _("AmbiguousEndpoints: %r") % endpoints) - self.endpoints = endpoints - - -class HttpError(ClientException): - """The base exception class for all HTTP exceptions. - """ - http_status = 0 - message = _("HTTP Error") - - def __init__(self, message=None, details=None, - response=None, request_id=None, - url=None, method=None, http_status=None): - self.http_status = http_status or self.http_status - self.message = message or self.message - self.details = details - self.request_id = request_id - self.response = response - self.url = url - self.method = method - formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) - if request_id: - formatted_string += " (Request-ID: %s)" % request_id - super(HttpError, self).__init__(formatted_string) - - -class HTTPRedirection(HttpError): - """HTTP Redirection.""" - message = _("HTTP Redirection") - - -class HTTPClientError(HttpError): - """Client-side HTTP error. - - Exception for cases in which the client seems to have erred. - """ - message = _("HTTP Client Error") - - -class HttpServerError(HttpError): - """Server-side HTTP error. - - Exception for cases in which the server is aware that it has - erred or is incapable of performing the request. - """ - message = _("HTTP Server Error") - - -class MultipleChoices(HTTPRedirection): - """HTTP 300 - Multiple Choices. - - Indicates multiple options for the resource that the client may follow. - """ - - http_status = 300 - message = _("Multiple Choices") - - -class BadRequest(HTTPClientError): - """HTTP 400 - Bad Request. - - The request cannot be fulfilled due to bad syntax. - """ - http_status = 400 - message = _("Bad Request") - - -class Unauthorized(HTTPClientError): - """HTTP 401 - Unauthorized. - - Similar to 403 Forbidden, but specifically for use when authentication - is required and has failed or has not yet been provided. - """ - http_status = 401 - message = _("Unauthorized") - - -class PaymentRequired(HTTPClientError): - """HTTP 402 - Payment Required. - - Reserved for future use. - """ - http_status = 402 - message = _("Payment Required") - - -class Forbidden(HTTPClientError): - """HTTP 403 - Forbidden. - - The request was a valid request, but the server is refusing to respond - to it. - """ - http_status = 403 - message = _("Forbidden") - - -class NotFound(HTTPClientError): - """HTTP 404 - Not Found. - - The requested resource could not be found but may be available again - in the future. - """ - http_status = 404 - message = _("Not Found") - - -class MethodNotAllowed(HTTPClientError): - """HTTP 405 - Method Not Allowed. - - A request was made of a resource using a request method not supported - by that resource. - """ - http_status = 405 - message = _("Method Not Allowed") - - -class NotAcceptable(HTTPClientError): - """HTTP 406 - Not Acceptable. - - The requested resource is only capable of generating content not - acceptable according to the Accept headers sent in the request. - """ - http_status = 406 - message = _("Not Acceptable") - - -class ProxyAuthenticationRequired(HTTPClientError): - """HTTP 407 - Proxy Authentication Required. - - The client must first authenticate itself with the proxy. - """ - http_status = 407 - message = _("Proxy Authentication Required") - - -class RequestTimeout(HTTPClientError): - """HTTP 408 - Request Timeout. - - The server timed out waiting for the request. - """ - http_status = 408 - message = _("Request Timeout") - - -class Conflict(HTTPClientError): - """HTTP 409 - Conflict. - - Indicates that the request could not be processed because of conflict - in the request, such as an edit conflict. - """ - http_status = 409 - message = _("Conflict") - - -class Gone(HTTPClientError): - """HTTP 410 - Gone. - - Indicates that the resource requested is no longer available and will - not be available again. - """ - http_status = 410 - message = _("Gone") - - -class LengthRequired(HTTPClientError): - """HTTP 411 - Length Required. - - The request did not specify the length of its content, which is - required by the requested resource. - """ - http_status = 411 - message = _("Length Required") - - -class PreconditionFailed(HTTPClientError): - """HTTP 412 - Precondition Failed. - - The server does not meet one of the preconditions that the requester - put on the request. - """ - http_status = 412 - message = _("Precondition Failed") - - -class RequestEntityTooLarge(HTTPClientError): - """HTTP 413 - Request Entity Too Large. - - The request is larger than the server is willing or able to process. - """ - http_status = 413 - message = _("Request Entity Too Large") - - def __init__(self, *args, **kwargs): - try: - self.retry_after = int(kwargs.pop('retry_after')) - except (KeyError, ValueError): - self.retry_after = 0 - - super(RequestEntityTooLarge, self).__init__(*args, **kwargs) - - -class RequestUriTooLong(HTTPClientError): - """HTTP 414 - Request-URI Too Long. - - The URI provided was too long for the server to process. - """ - http_status = 414 - message = _("Request-URI Too Long") - - -class UnsupportedMediaType(HTTPClientError): - """HTTP 415 - Unsupported Media Type. - - The request entity has a media type which the server or resource does - not support. - """ - http_status = 415 - message = _("Unsupported Media Type") - - -class RequestedRangeNotSatisfiable(HTTPClientError): - """HTTP 416 - Requested Range Not Satisfiable. - - The client has asked for a portion of the file, but the server cannot - supply that portion. - """ - http_status = 416 - message = _("Requested Range Not Satisfiable") - - -class ExpectationFailed(HTTPClientError): - """HTTP 417 - Expectation Failed. - - The server cannot meet the requirements of the Expect request-header field. - """ - http_status = 417 - message = _("Expectation Failed") - - -class UnprocessableEntity(HTTPClientError): - """HTTP 422 - Unprocessable Entity. - - The request was well-formed but was unable to be followed due to semantic - errors. - """ - http_status = 422 - message = _("Unprocessable Entity") - - -class InternalServerError(HttpServerError): - """HTTP 500 - Internal Server Error. - - A generic error message, given when no more specific message is suitable. - """ - http_status = 500 - message = _("Internal Server Error") - - -# NotImplemented is a python keyword. -class HttpNotImplemented(HttpServerError): - """HTTP 501 - Not Implemented. - - The server either does not recognize the request method, or it lacks - the ability to fulfill the request. - """ - http_status = 501 - message = _("Not Implemented") - - -class BadGateway(HttpServerError): - """HTTP 502 - Bad Gateway. - - The server was acting as a gateway or proxy and received an invalid - response from the upstream server. - """ - http_status = 502 - message = _("Bad Gateway") - - -class ServiceUnavailable(HttpServerError): - """HTTP 503 - Service Unavailable. - - The server is currently unavailable. - """ - http_status = 503 - message = _("Service Unavailable") - - -class GatewayTimeout(HttpServerError): - """HTTP 504 - Gateway Timeout. - - The server was acting as a gateway or proxy and did not receive a timely - response from the upstream server. - """ - http_status = 504 - message = _("Gateway Timeout") - - -class HttpVersionNotSupported(HttpServerError): - """HTTP 505 - HttpVersion Not Supported. - - The server does not support the HTTP protocol version used in the request. - """ - http_status = 505 - message = _("HTTP Version Not Supported") - - -# _code_map contains all the classes that have http_status attribute. -_code_map = dict( - (getattr(obj, 'http_status', None), obj) - for name, obj in six.iteritems(vars(sys.modules[__name__])) - if inspect.isclass(obj) and getattr(obj, 'http_status', False) -) - - -def from_response(response, method, url): - """Returns an instance of :class:`HttpError` or subclass based on response. - - :param response: instance of `requests.Response` class - :param method: HTTP method used for request - :param url: URL used for request - """ - - req_id = response.headers.get("x-openstack-request-id") - # NOTE(hdd) true for older versions of nova and cinder - if not req_id: - req_id = response.headers.get("x-compute-request-id") - kwargs = { - "http_status": response.status_code, - "response": response, - "method": method, - "url": url, - "request_id": req_id, - } - if "retry-after" in response.headers: - kwargs["retry_after"] = response.headers["retry-after"] - - content_type = response.headers.get("Content-Type", "") - if content_type.startswith("application/json"): - try: - body = response.json() - except ValueError: - pass - else: - if isinstance(body, dict): - error = body.get(list(body)[0]) - if isinstance(error, dict): - kwargs["message"] = (error.get("message") or - error.get("faultstring")) - kwargs["details"] = (error.get("details") or - six.text_type(body)) - elif content_type.startswith("text/"): - kwargs["details"] = getattr(response, 'text', '') - - try: - cls = _code_map[response.status_code] - except KeyError: - if 500 <= response.status_code < 600: - cls = HttpServerError - elif 400 <= response.status_code < 500: - cls = HTTPClientError - else: - cls = HttpError - return cls(**kwargs) diff --git a/stacktaskclient/openstack/common/apiclient/fake_client.py b/stacktaskclient/openstack/common/apiclient/fake_client.py deleted file mode 100644 index 25d3ada..0000000 --- a/stacktaskclient/openstack/common/apiclient/fake_client.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# All 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. - -""" -A fake server that "responds" to API methods with pre-canned responses. - -All of these responses come from the spec, so if for some reason the spec's -wrong the tests might raise AssertionError. I've indicated in comments the -places where actual behavior differs from the spec. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -# W0102: Dangerous default value %s as argument -# pylint: disable=W0102 - -import json - -import requests -import six -from six.moves.urllib import parse - -from stacktaskclient.openstack.common.apiclient import client - - -def assert_has_keys(dct, required=None, optional=None): - required = required or [] - optional = optional or [] - for k in required: - try: - assert k in dct - except AssertionError: - extra_keys = set(dct.keys()).difference(set(required + optional)) - raise AssertionError("found unexpected keys: %s" % - list(extra_keys)) - - -class TestResponse(requests.Response): - """Wrap requests.Response and provide a convenient initialization. - """ - - def __init__(self, data): - super(TestResponse, self).__init__() - self._content_consumed = True - if isinstance(data, dict): - self.status_code = data.get('status_code', 200) - # Fake the text attribute to streamline Response creation - text = data.get('text', "") - if isinstance(text, (dict, list)): - self._content = json.dumps(text) - default_headers = { - "Content-Type": "application/json", - } - else: - self._content = text - default_headers = {} - if six.PY3 and isinstance(self._content, six.string_types): - self._content = self._content.encode('utf-8', 'strict') - self.headers = data.get('headers') or default_headers - else: - self.status_code = data - - def __eq__(self, other): - return (self.status_code == other.status_code and - self.headers == other.headers and - self._content == other._content) - - -class FakeHTTPClient(client.HTTPClient): - - def __init__(self, *args, **kwargs): - self.callstack = [] - self.fixtures = kwargs.pop("fixtures", None) or {} - if not args and "auth_plugin" not in kwargs: - args = (None, ) - super(FakeHTTPClient, self).__init__(*args, **kwargs) - - def assert_called(self, method, url, body=None, pos=-1): - """Assert than an API method was just called. - """ - expected = (method, url) - called = self.callstack[pos][0:2] - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - assert expected == called, 'Expected %s %s; got %s %s' % \ - (expected + called) - - if body is not None: - if self.callstack[pos][3] != body: - raise AssertionError('%r != %r' % - (self.callstack[pos][3], body)) - - def assert_called_anytime(self, method, url, body=None): - """Assert than an API method was called anytime in the test. - """ - expected = (method, url) - - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - found = False - entry = None - for entry in self.callstack: - if expected == entry[0:2]: - found = True - break - - assert found, 'Expected %s %s; got %s' % \ - (method, url, self.callstack) - if body is not None: - assert entry[3] == body, "%s != %s" % (entry[3], body) - - self.callstack = [] - - def clear_callstack(self): - self.callstack = [] - - def authenticate(self): - pass - - def client_request(self, client, method, url, **kwargs): - # Check that certain things are called correctly - if method in ["GET", "DELETE"]: - assert "json" not in kwargs - - # Note the call - self.callstack.append( - (method, - url, - kwargs.get("headers") or {}, - kwargs.get("json") or kwargs.get("data"))) - try: - fixture = self.fixtures[url][method] - except KeyError: - pass - else: - return TestResponse({"headers": fixture[0], - "text": fixture[1]}) - - # Call the method - args = parse.parse_qsl(parse.urlparse(url)[4]) - kwargs.update(args) - munged_url = url.rsplit('?', 1)[0] - munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') - munged_url = munged_url.replace('-', '_') - - callback = "%s_%s" % (method.lower(), munged_url) - - if not hasattr(self, callback): - raise AssertionError('Called unknown API method: %s %s, ' - 'expected fakes method name: %s' % - (method, url, callback)) - - resp = getattr(self, callback)(**kwargs) - if len(resp) == 3: - status, headers, body = resp - else: - status, body = resp - headers = {} - self.last_request_id = headers.get('x-openstack-request-id', - 'req-test') - return TestResponse({ - "status_code": status, - "text": body, - "headers": headers, - }) diff --git a/stacktaskclient/openstack/common/apiclient/utils.py b/stacktaskclient/openstack/common/apiclient/utils.py deleted file mode 100644 index 8f7200d..0000000 --- a/stacktaskclient/openstack/common/apiclient/utils.py +++ /dev/null @@ -1,100 +0,0 @@ -# -# 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. - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -from oslo_utils import encodeutils -from oslo_utils import uuidutils -import six - -from stacktaskclient.openstack.common._i18n import _ -from stacktaskclient.openstack.common.apiclient import exceptions - - -def find_resource(manager, name_or_id, **find_args): - """Look for resource in a given manager. - - Used as a helper for the _find_* methods. - Example: - - .. code-block:: python - - def _find_hypervisor(cs, hypervisor): - #Get a hypervisor by name or ID. - return cliutils.find_resource(cs.hypervisors, hypervisor) - """ - # first try to get entity as integer id - try: - return manager.get(int(name_or_id)) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # now try to get entity as uuid - try: - if six.PY2: - tmp_id = encodeutils.safe_encode(name_or_id) - else: - tmp_id = encodeutils.safe_decode(name_or_id) - - if uuidutils.is_uuid_like(tmp_id): - return manager.get(tmp_id) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # for str id which is not uuid - if getattr(manager, 'is_alphanum_id_allowed', False): - try: - return manager.get(name_or_id) - except exceptions.NotFound: - pass - - try: - try: - return manager.find(human_id=name_or_id, **find_args) - except exceptions.NotFound: - pass - - # finally try to find entity by name - try: - resource = getattr(manager, 'resource_class', None) - name_attr = resource.NAME_ATTR if resource else 'name' - kwargs = {name_attr: name_or_id} - kwargs.update(find_args) - return manager.find(**kwargs) - except exceptions.NotFound: - msg = _("No %(name)s with a name or " - "ID of '%(name_or_id)s' exists.") % \ - { - "name": manager.resource_class.__name__.lower(), - "name_or_id": name_or_id - } - raise exceptions.CommandError(msg) - except exceptions.NoUniqueMatch: - msg = _("Multiple %(name)s matches found for " - "'%(name_or_id)s', use an ID to be more specific.") % \ - { - "name": manager.resource_class.__name__.lower(), - "name_or_id": name_or_id - } - raise exceptions.CommandError(msg) diff --git a/stacktaskclient/openstack/common/cliutils.py b/stacktaskclient/openstack/common/cliutils.py deleted file mode 100644 index 3303706..0000000 --- a/stacktaskclient/openstack/common/cliutils.py +++ /dev/null @@ -1,271 +0,0 @@ -# Copyright 2012 Red Hat, Inc. -# -# 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. - -# W0603: Using the global statement -# W0621: Redefining name %s from outer scope -# pylint: disable=W0603,W0621 - -from __future__ import print_function - -import getpass -import inspect -import os -import sys -import textwrap - -from oslo_utils import encodeutils -from oslo_utils import strutils -import prettytable -import six -from six import moves - -from stacktaskclient.openstack.common._i18n import _ - - -class MissingArgs(Exception): - """Supplied arguments are not sufficient for calling a function.""" - def __init__(self, missing): - self.missing = missing - msg = _("Missing arguments: %s") % ", ".join(missing) - super(MissingArgs, self).__init__(msg) - - -def validate_args(fn, *args, **kwargs): - """Check that the supplied args are sufficient for calling a function. - - >>> validate_args(lambda a: None) - Traceback (most recent call last): - ... - MissingArgs: Missing argument(s): a - >>> validate_args(lambda a, b, c, d: None, 0, c=1) - Traceback (most recent call last): - ... - MissingArgs: Missing argument(s): b, d - - :param fn: the function to check - :param arg: the positional arguments supplied - :param kwargs: the keyword arguments supplied - """ - argspec = inspect.getargspec(fn) - - num_defaults = len(argspec.defaults or []) - required_args = argspec.args[:len(argspec.args) - num_defaults] - - def isbound(method): - return getattr(method, '__self__', None) is not None - - if isbound(fn): - required_args.pop(0) - - missing = [arg for arg in required_args if arg not in kwargs] - missing = missing[len(args):] - if missing: - raise MissingArgs(missing) - - -def arg(*args, **kwargs): - """Decorator for CLI args. - - Example: - - >>> @arg("name", help="Name of the new entity") - ... def entity_create(args): - ... pass - """ - def _decorator(func): - add_arg(func, *args, **kwargs) - return func - return _decorator - - -def env(*args, **kwargs): - """Returns the first environment variable set. - - If all are empty, defaults to '' or keyword arg `default`. - """ - for arg in args: - value = os.environ.get(arg) - if value: - return value - return kwargs.get('default', '') - - -def add_arg(func, *args, **kwargs): - """Bind CLI arguments to a shell.py `do_foo` function.""" - - if not hasattr(func, 'arguments'): - func.arguments = [] - - # NOTE(sirp): avoid dups that can occur when the module is shared across - # tests. - if (args, kwargs) not in func.arguments: - # Because of the semantics of decorator composition if we just append - # to the options list positional options will appear to be backwards. - func.arguments.insert(0, (args, kwargs)) - - -def unauthenticated(func): - """Adds 'unauthenticated' attribute to decorated function. - - Usage: - - >>> @unauthenticated - ... def mymethod(f): - ... pass - """ - func.unauthenticated = True - return func - - -def isunauthenticated(func): - """Checks if the function does not require authentication. - - Mark such functions with the `@unauthenticated` decorator. - - :returns: bool - """ - return getattr(func, 'unauthenticated', False) - - -def print_list(objs, fields, formatters=None, sortby_index=None, - mixed_case_fields=None, field_labels=None): - """Print a list or objects as a table, one row per object. - - :param objs: iterable of :class:`Resource` - :param fields: attributes that correspond to columns, in order - :param formatters: `dict` of callables for field formatting - :param sortby_index: index of the field for sorting table rows - :param mixed_case_fields: fields corresponding to object attributes that - have mixed case names (e.g., 'serverId') - :param field_labels: Labels to use in the heading of the table, default to - fields. - """ - formatters = formatters or {} - mixed_case_fields = mixed_case_fields or [] - field_labels = field_labels or fields - if len(field_labels) != len(fields): - raise ValueError(_("Field labels list %(labels)s has different number " - "of elements than fields list %(fields)s"), - {'labels': field_labels, 'fields': fields}) - - if sortby_index is None: - kwargs = {} - else: - kwargs = {'sortby': field_labels[sortby_index]} - pt = prettytable.PrettyTable(field_labels) - pt.align = 'l' - - for o in objs: - row = [] - for field in fields: - if field in formatters: - row.append(formatters[field](o)) - else: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = getattr(o, field_name, '') - row.append(data) - pt.add_row(row) - - if six.PY3: - print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) - else: - print(encodeutils.safe_encode(pt.get_string(**kwargs))) - - -def print_dict(dct, dict_property="Property", wrap=0): - """Print a `dict` as a table of two columns. - - :param dct: `dict` to print - :param dict_property: name of the first column - :param wrap: wrapping for the second column - """ - pt = prettytable.PrettyTable([dict_property, 'Value']) - pt.align = 'l' - for k, v in six.iteritems(dct): - # convert dict to str to check length - if isinstance(v, dict): - v = six.text_type(v) - if wrap > 0: - v = textwrap.fill(six.text_type(v), wrap) - # if value has a newline, add in multiple rows - # e.g. fault with stacktrace - if v and isinstance(v, six.string_types) and r'\n' in v: - lines = v.strip().split(r'\n') - col1 = k - for line in lines: - pt.add_row([col1, line]) - col1 = '' - else: - pt.add_row([k, v]) - - if six.PY3: - print(encodeutils.safe_encode(pt.get_string()).decode()) - else: - print(encodeutils.safe_encode(pt.get_string())) - - -def get_password(max_password_prompts=3): - """Read password from TTY.""" - verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD")) - pw = None - if hasattr(sys.stdin, "isatty") and sys.stdin.isatty(): - # Check for Ctrl-D - try: - for __ in moves.range(max_password_prompts): - pw1 = getpass.getpass("OS Password: ") - if verify: - pw2 = getpass.getpass("Please verify: ") - else: - pw2 = pw1 - if pw1 == pw2 and pw1: - pw = pw1 - break - except EOFError: - pass - return pw - - -def service_type(stype): - """Adds 'service_type' attribute to decorated function. - - Usage: - - .. code-block:: python - - @service_type('volume') - def mymethod(f): - ... - """ - def inner(f): - f.service_type = stype - return f - return inner - - -def get_service_type(f): - """Retrieves service type from function.""" - return getattr(f, 'service_type', None) - - -def pretty_choice_list(l): - return ', '.join("'%s'" % i for i in l) - - -def exit(msg=''): - if msg: - print (msg, file=sys.stderr) - sys.exit(1) diff --git a/stacktaskclient/osc/__init__.py b/stacktaskclient/osc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacktaskclient/osc/v1/__init__.py b/stacktaskclient/osc/v1/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stacktaskclient/shell.py b/stacktaskclient/shell.py deleted file mode 100644 index 896bd7e..0000000 --- a/stacktaskclient/shell.py +++ /dev/null @@ -1,593 +0,0 @@ -# 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. - -""" -Command-line interface to the Stacktask API. -""" - -from __future__ import print_function - -import argparse -import logging -import sys - -from oslo_utils import encodeutils -from oslo_utils import importutils -import six - -from keystoneauth1.identity import generic -from keystoneauth1 import session as kssession - -import stacktaskclient -from stacktaskclient import client as stacktask_client -from stacktaskclient.common import utils -from stacktaskclient import exc -from stacktaskclient.openstack.common._i18n import _ - -logger = logging.getLogger(__name__) -osprofiler_profiler = importutils.try_import("osprofiler.profiler") - - -class StacktaskShell(object): - - def _append_global_identity_args(self, parser): - parser.add_argument( - '-k', '--insecure', - default=False, - action='store_true', - help=_( - 'Explicitly allow this client to perform ' - '\"insecure SSL\" (https) requests. The server\'s ' - 'certificate will not be verified against any ' - 'certificate authorities. This option should ' - 'be used with caution.')) - - parser.add_argument( - '--os-cert', - help=_( - 'Path of certificate file to use in SSL ' - 'connection. This file can optionally be ' - 'prepended with the private key.')) - - parser.add_argument( - '--os-key', - help=_( - 'Path of client key to use in SSL ' - 'connection. This option is not necessary ' - 'if your key is prepended to your cert file.')) - - parser.add_argument( - '--os-cacert', - metavar='', - dest='os_cacert', - default=utils.env('OS_CACERT'), - help=_( - 'Path of CA TLS certificate(s) used to ' - 'verify the remote server\'s certificate. ' - 'Without this option glance looks for the ' - 'default system CA certificates.')) - - parser.add_argument( - '--os-username', - default=utils.env('OS_USERNAME'), - help=_('Defaults to %(value)s.') % {'value': 'env[OS_USERNAME]'}) - - parser.add_argument('--os_username', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-user-id', - default=utils.env('OS_USER_ID'), - help=_('Defaults to %(value)s.') % {'value': 'env[OS_USER_ID]'}) - - parser.add_argument('--os_user_id', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-user-domain-id', - default=utils.env('OS_USER_DOMAIN_ID'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_USER_DOMAIN_ID]' - }) - - parser.add_argument('--os_user_domain_id', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-user-domain-name', - default=utils.env('OS_USER_DOMAIN_NAME'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_USER_DOMAIN_NAME]' - }) - - parser.add_argument('--os_user_domain_name', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-project-id', - default=utils.env('OS_PROJECT_ID'), - help=( - _('Another way to specify tenant ID. ' - 'This option is mutually exclusive with ' - '%(arg)s. Defaults to %(value)s.') % - {'arg': '--os-tenant-id', 'value': 'env[OS_PROJECT_ID]'})) - - parser.add_argument('--os_project_id', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-project-name', - default=utils.env('OS_PROJECT_NAME'), - help=( - _('Another way to specify tenant name. ' - 'This option is mutually exclusive with ' - '%(arg)s. Defaults to %(value)s.') % - {'arg': '--os-tenant-name', 'value': 'env[OS_PROJECT_NAME]'})) - - parser.add_argument('--os_project_name', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-project-domain-id', - default=utils.env('OS_PROJECT_DOMAIN_ID'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_PROJECT_DOMAIN_ID]' - }) - - parser.add_argument('--os_project_domain_id', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-project-domain-name', - default=utils.env('OS_PROJECT_DOMAIN_NAME'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_PROJECT_DOMAIN_NAME]' - }) - - parser.add_argument('--os_project_domain_name', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-password', - default=utils.env('OS_PASSWORD'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_PASSWORD]' - }) - - parser.add_argument('--os_password', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-tenant-id', - default=utils.env('OS_TENANT_ID'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_TENANT_ID]' - }) - - parser.add_argument( - '--os_tenant_id', - default=utils.env('OS_TENANT_ID'), - help=argparse.SUPPRESS) - - parser.add_argument( - '--os-tenant-name', - default=utils.env('OS_TENANT_NAME'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_TENANT_NAME]' - }) - - parser.add_argument( - '--os_tenant_name', - default=utils.env('OS_TENANT_NAME'), - help=argparse.SUPPRESS) - - parser.add_argument( - '--os-auth-url', - default=utils.env('OS_AUTH_URL'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_AUTH_URL]' - }) - - parser.add_argument('--os_auth_url', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-region-name', - default=utils.env('OS_REGION_NAME'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_REGION_NAME]' - }) - - parser.add_argument('--os_region_name', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-token', - default=utils.env('OS_TOKEN'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_TOKEN]' - }) - - parser.add_argument('--os_token', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-service-type', - default=utils.env('OS_SERVICE_TYPE'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_SERVICE_TYPE]' - }) - - parser.add_argument('--os_service_type', help=argparse.SUPPRESS) - - parser.add_argument( - '--os-endpoint-type', - default=utils.env('OS_ENDPOINT_TYPE'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[OS_ENDPOINT_TYPE]' - }) - - parser.add_argument('--os_endpoint_type', help=argparse.SUPPRESS) - - def get_base_parser(self): - parser = argparse.ArgumentParser( - prog='stacktask', - description=__doc__.strip(), - epilog=_('See "%(arg)s" for help on a specific command.') % { - 'arg': 'stacktask help COMMAND' - }, - add_help=False, - formatter_class=HelpFormatter, - ) - - # Global arguments - parser.add_argument( - '-h', '--help', action='store_true', help=argparse.SUPPRESS) - - parser.add_argument( - '--version', action='version', - version=stacktaskclient.__version__, - help=_("Shows the client version and exits.")) - - parser.add_argument( - '-d', '--debug', - default=bool(utils.env('STACKTASKCLIENT_DEBUG')), - action='store_true', - help=_('Defaults to %(value)s.') % { - 'value': 'env[STACKTASKCLIENT_DEBUG]' - }) - - parser.add_argument( - '-v', '--verbose', - default=False, action="store_true", - help=_("Print more verbose output.")) - - parser.add_argument( - '--api-timeout', - help=_('Number of seconds to wait for an ' - 'API response, ' - 'defaults to system socket timeout')) - - # os-no-client-auth tells stacktaskclient to use token, instead of - # env[OS_AUTH_URL] - parser.add_argument( - '--os-no-client-auth', - default=utils.env('OS_NO_CLIENT_AUTH'), - action='store_true', - help=(_("Do not contact keystone for a token. " - "Defaults to %(value)s.") % - {'value': 'env[OS_NO_CLIENT_AUTH]'})) - - parser.add_argument( - '--bypass-url', - default=utils.env('STACKTASK_BYPASS_URL'), - help=_('Defaults to %(value)s.') % { - 'value': 'env[STACKTASK_BYPASS_URL]' - }) - - parser.add_argument( - '--api-version', - default=utils.env('STACKTASK_API_VERSION', - default='1'), - help=_('Defaults to %(value)s or 1.') % { - 'value': 'env[STACKTASK_API_VERSION]' - }) - - self._append_global_identity_args(parser) - - if osprofiler_profiler: - parser.add_argument( - '--profile', - metavar='HMAC_KEY', - help=_( - 'HMAC key to use for encrypting ' - 'context data for performance profiling of ' - 'operation. This key should be the value of ' - 'HMAC key configured in osprofiler middleware ' - 'in heat, it is specified in the paste ' - 'configuration (/etc/heat/api-paste.ini). ' - 'Without the key, profiling will not be ' - 'triggered even if osprofiler is enabled ' - 'on server side.')) - return parser - - def get_subcommand_parser(self, version): - parser = self.get_base_parser() - - self.subcommands = {} - subparsers = parser.add_subparsers(metavar='') - submodule = utils.import_versioned_module(version, 'shell') - self._find_actions(subparsers, submodule) - self._find_actions(subparsers, self) - self._add_bash_completion_subparser(subparsers) - - return parser - - def _add_bash_completion_subparser(self, subparsers): - subparser = subparsers.add_parser( - 'bash_completion', - add_help=False, - formatter_class=HelpFormatter - ) - self.subcommands['bash_completion'] = subparser - subparser.set_defaults(func=self.do_bash_completion) - - def _find_actions(self, subparsers, actions_module): - for attr in (a for a in dir(actions_module) if a.startswith('do_')): - # I prefer to be hyphen-separated instead of underscores. - command = attr[3:].replace('_', '-') - callback = getattr(actions_module, attr) - desc = callback.__doc__ or '' - help = desc.strip().split('\n')[0] - arguments = getattr(callback, 'arguments', []) - - subparser = subparsers.add_parser(command, - help=help, - description=desc, - add_help=False, - formatter_class=HelpFormatter) - subparser.add_argument('-h', '--help', - action='help', - help=argparse.SUPPRESS) - self.subcommands[command] = subparser - for (args, kwargs) in arguments: - subparser.add_argument(*args, **kwargs) - subparser.set_defaults(func=callback) - - def _setup_logging(self, debug): - log_lvl = logging.DEBUG if debug else logging.WARNING - logging.basicConfig( - format="%(levelname)s (%(module)s) %(message)s", - level=log_lvl) - logging.getLogger('iso8601').setLevel(logging.WARNING) - logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING) - - def _setup_verbose(self, verbose): - if verbose: - exc.verbose = 1 - - def _get_keystone_session(self, **kwargs): - # first create a Keystone session - cacert = kwargs.pop('cacert', None) - cert = kwargs.pop('cert', None) - key = kwargs.pop('key', None) - insecure = kwargs.pop('insecure', False) - timeout = kwargs.pop('timeout', None) - verify = kwargs.pop('verify', None) - - if verify is None: - if insecure: - verify = False - else: - verify = cacert or True - if cert and key: - # passing cert and key together is deprecated in favour of the - # requests lib form of having the cert and key as a tuple - cert = (cert, key) - - return kssession.Session(verify=verify, cert=cert, timeout=timeout) - - def main(self, argv): - # Parse args once to find version - parser = self.get_base_parser() - (options, args) = parser.parse_known_args(argv) - self._setup_logging(options.debug) - self._setup_verbose(options.verbose) - - # build available subcommands based on version - api_version = options.api_version - subcommand_parser = self.get_subcommand_parser(api_version) - self.parser = subcommand_parser - - # Handle top-level --help/-h before attempting to parse - # a command off the command line - if not args and options.help or not argv: - self.do_help(options) - return 0 - - # Parse args again and call whatever callback was selected - args = subcommand_parser.parse_args(argv) - - # Short-circuit and deal with help command right away. - if args.func == self.do_help: - self.do_help(args) - return 0 - elif args.func == self.do_bash_completion: - self.do_bash_completion(args) - return 0 - - if (not args.os_username and not args.os_token and - not args.os_no_client_auth): - raise exc.CommandError( - _("You must provide a username via either " - "--os-username or env[OS_USERNAME] " - "or a token via --os-token or " - "env[OS_TOKEN]")) - - if (not args.os_password and not args.os_token and - not args.os_no_client_auth): - raise exc.CommandError( - _("You must provide a password via either " - "--os-password or env[OS_PASSWORD] " - "or a token via --os-token or " - "env[OS_TOKEN]")) - - if args.os_no_client_auth: - if not args.bypass_url: - raise exc.CommandError( - _("If you specify --os-no-client-auth " - "you must also specify a Stacktask API " - "URL via either --bypass-url or " - "env[STACKTASK_BYPASS_URL]")) - else: - # Tenant/project name or ID is needed to make keystoneclient - # retrieve a service catalog, it's not required if - # os_no_client_auth is specified, neither is the auth URL - - if not (args.os_tenant_id or args.os_tenant_name or - args.os_project_id or args.os_project_name): - raise exc.CommandError( - _("You must provide a tenant id via either " - "--os-tenant-id or env[OS_TENANT_ID] or a tenant name " - "via either --os-tenant-name or env[OS_TENANT_NAME] " - "or a project id via either --os-project-id or " - "env[OS_PROJECT_ID] or a project name via " - "either --os-project-name or env[OS_PROJECT_NAME]")) - - if not args.os_auth_url: - raise exc.CommandError( - _("You must provide an auth url via " - "either --os-auth-url or via " - "env[OS_AUTH_URL]")) - kwargs = { - 'insecure': args.insecure, - 'cacert': args.os_cacert, - 'cert': args.os_cert, - 'key': args.os_key, - 'timeout': args.api_timeout - } - - endpoint = args.bypass_url - service_type = args.os_service_type or 'registration' - if args.os_no_client_auth: - # Do not use session since no_client_auth means using heat to - # to authenticate - kwargs = { - 'username': args.os_username, - 'password': args.os_password, - 'auth_url': args.os_auth_url, - 'token': args.os_token, - 'include_pass': None, # args.include_password, - 'insecure': args.insecure, - 'timeout': args.api_timeout - } - else: - keystone_session = self._get_keystone_session(**kwargs) - endpoint_type = args.os_endpoint_type or 'publicURL' - if args.os_token: - kwargs = { - 'token': args.os_token, - 'auth_url': args.os_auth_url - } - keystone_auth = generic.Token(**kwargs) - else: - project_id = args.os_project_id or args.os_tenant_id - project_name = args.os_project_name or args.os_tenant_name - kwargs = { - 'username': args.os_username, - 'user_id': args.os_user_id, - 'user_domain_id': args.os_user_domain_id, - 'user_domain_name': args.os_user_domain_name, - 'password': args.os_password, - 'auth_url': args.os_auth_url, - 'project_id': project_id, - 'project_name': project_name, - 'project_domain_id': args.os_project_domain_id, - 'project_domain_name': args.os_project_domain_name, - } - keystone_auth = generic.Password(**kwargs) - if not endpoint: - svc_type = service_type - region_name = args.os_region_name - endpoint = keystone_auth.get_endpoint(keystone_session, - service_type=svc_type, - interface=endpoint_type, - region_name=region_name) - kwargs = { - 'auth_url': args.os_auth_url, - 'session': keystone_session, - 'auth': keystone_auth, - 'service_type': service_type, - 'endpoint_type': endpoint_type, - 'region_name': args.os_region_name, - 'username': args.os_username, - 'password': args.os_password, - 'include_pass': None # args.include_password - } - - client = stacktask_client.Client(api_version, endpoint, **kwargs) - - profile = osprofiler_profiler and options.profile - if profile: - osprofiler_profiler.init(options.profile) - - args.func(client, args) - - if profile: - trace_id = osprofiler_profiler.get().get_base_id() - print(_("Trace ID: %s") % trace_id) - print(_("To display trace use next command:\n" - "osprofiler trace show --html %s ") % trace_id) - - def do_bash_completion(self, args): - """Prints all of the commands and options to stdout. - - The heat.bash_completion script doesn't have to hard code them. - """ - commands = set() - options = set() - for sc_str, sc in self.subcommands.items(): - commands.add(sc_str) - for option in list(sc._optionals._option_string_actions): - options.add(option) - - commands.remove('bash-completion') - commands.remove('bash_completion') - print(' '.join(commands | options)) - - @utils.arg('command', metavar='', nargs='?', - help=_('Display help for .')) - def do_help(self, args): - """Display help about this program or one of its subcommands.""" - if getattr(args, 'command', None): - if args.command in self.subcommands: - self.subcommands[args.command].print_help() - else: - raise exc.CommandError("'%s' is not a valid subcommand" % - args.command) - else: - self.parser.print_help() - - -class HelpFormatter(argparse.HelpFormatter): - def start_section(self, heading): - # Title-case the headings - heading = '%s%s' % (heading[0].upper(), heading[1:]) - super(HelpFormatter, self).start_section(heading) - - -def main(args=None): - try: - if args is None: - args = sys.argv[1:] - - StacktaskShell().main(args) - except KeyboardInterrupt: - print(_("... terminating stacktask client"), file=sys.stderr) - sys.exit(130) - except Exception as e: - if '--debug' in args or '-d' in args: - raise - else: - print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/stacktaskclient/v1/build_info.py b/stacktaskclient/v1/build_info.py deleted file mode 100644 index 75c6b61..0000000 --- a/stacktaskclient/v1/build_info.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# All 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. - -from stacktaskclient.common import utils -from stacktaskclient.openstack.common.apiclient import base - - -class BuildInfo(base.Resource): - def __repr__(self): - return "" % self._info - - def build_info(self): - return self.manager.build_info() - - -class BuildInfoManager(base.BaseManager): - resource_class = BuildInfo - - def build_info(self): - resp = self.client.get('/build_info') - body = utils.get_response_body(resp) - return body diff --git a/stacktaskclient/v1/shell.py b/stacktaskclient/v1/shell.py deleted file mode 100644 index c4461af..0000000 --- a/stacktaskclient/v1/shell.py +++ /dev/null @@ -1,525 +0,0 @@ -# Copyright (C) 2016 Catalyst IT Ltd -# -# 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 logging - -import json -from stacktaskclient.common import utils -from stacktaskclient.common import http - -from stacktaskclient.openstack.common._i18n import _ - -import stacktaskclient.exc as exc - -logger = logging.getLogger(__name__) - - -def _authenticated_fetcher(sc): - """ - A wrapper around the stacktask client object to fetch a template. - """ - def _do(*args, **kwargs): - if isinstance(sc.http_client, http.SessionClient): - method, url = args - return sc.http_client.request(url, method, **kwargs).content - else: - return sc.http_client.raw_request(*args, **kwargs).content - - return _do - - -def show_deprecated(deprecated, recommended): - logger.warning('"%(old)s" is deprecated, ' - 'please use "%(new)s" instead', - {'old': deprecated, - 'new': recommended} - ) - - -# Tasks - -@utils.arg('task_id', metavar='', - help=_('Task ID.')) -def do_task_show(sc, args): - """ - Get individual task. - """ - show_deprecated('stacktask task-show', 'openstack admin task show') - - try: - task = sc.tasks.get(task_id=args.task_id) - - formatters = { - 'actions': utils.json_formatter, - 'action_notes': utils.json_formatter, - 'keystone_user': utils.json_formatter, - 'approved_by': utils.json_formatter - } - utils.print_dict(task.to_dict(), formatters=formatters) - except exc.HTTPNotFound: - raise exc.CommandError(_('Task not found: %s') % - args.task_id) - except exc.HTTPBadRequest as e: - print e.message - - -@utils.arg('--filters', default={}, - help=_('Filters to use when getting the list.')) -def do_task_list(sc, args): - """ - Show all tasks in the current project - """ - show_deprecated('stacktask task-list', 'openstack admin task list') - fields = [ - 'uuid', 'task_type', 'created_on', - 'approved_on', 'completed_on', 'cancelled'] - tasks_list = sc.tasks.list(filters=args.filters) - utils.print_list(tasks_list, fields) - - -@utils.arg('task_id', metavar='', - help=_('Task ID.')) -@utils.arg('--data', required=True, - help=_('New data to update the Task with.')) -def do_task_update(sc, args): - """ - Update a task with new data and rerun pre-approve validation. - """ - show_deprecated('stacktask task-update', 'openstack admin task update') - try: - resp = sc.tasks.update(args.task_id, args.data) - except exc.HTTPNotFound as e: - print e.message - except exc.HTTPBadRequest as e: - print e.message - else: - print 'Success:', ' '.join(resp.notes) - do_task_show(sc, args) - - -@utils.arg('task_id', metavar='', - help=_('Task ID.')) -def do_task_approve(sc, args): - """ - Approve a task. - - If already approved will rerun post-approve validation - and resend token. - """ - show_deprecated('stacktask task-approve', 'openstack admin task approve') - try: - resp = sc.tasks.approve(args.task_id) - except exc.HTTPNotFound as e: - print e.message - except exc.HTTPBadRequest as e: - print e.message - else: - print 'Success:', ' '.join(resp.notes) - do_task_show(sc, args) - - -@utils.arg('task_id', metavar='', - help=_('Task ID.')) -def do_task_reissue_token(sc, args): - """ - Re-issues the token for the provided pending task. - """ - show_deprecated('stacktask task-reissue-token', - 'openstack admin task token reissue') - try: - resp = sc.tokens.reissue(task_id=args.task_id) - except exc.HTTPNotFound as e: - print e.message - except exc.HTTPBadRequest as e: - print e.message - else: - print 'Success:', ' '.join(resp.notes) - do_task_show(sc, args) - - -@utils.arg('task_id', metavar='', - help=_('Task ID.')) -def do_task_cancel(sc, args): - """ - Canel the task. - """ - show_deprecated('stacktask task-approve', 'openstack admin task cancel') - try: - resp = sc.tasks.cancel(args.task_id) - except exc.HTTPNotFound as e: - print e.message - except exc.HTTPBadRequest as e: - print e.message - else: - print 'Success: %s' % resp.json()['notes'] - do_task_show(sc, args) - - -# Notifications - -@utils.arg('note_id', metavar='', - help=_('Notification ID.')) -def do_notification_show(sc, args): - """ - Get individual notification. - """ - show_deprecated('stacktask notification-show', - 'openstack admin task notification show') - - try: - notification = sc.notifications.get(note_id=args.note_id) - - formatters = { - 'notes': utils.json_formatter - } - utils.print_dict(notification.to_dict(), formatters=formatters) - except exc.HTTPNotFound: - raise exc.CommandError(_('Notification not found: %s') % - args.note_id) - except exc.HTTPBadRequest as e: - print e.message - - -@utils.arg('--filters', default={}, - help=_('Filters to use when getting the list.')) -def do_notification_list(sc, args): - """ - Show all notification. - - This is an admin only endpoint. - """ - show_deprecated('stacktask notification-list', - 'openstack admin task notificaion list') - - fields = ['uuid', 'task', 'acknowledged', 'created_on'] - notification_list = sc.notifications.list(filters=args.filters) - utils.print_list(notification_list, fields) - - -@utils.arg('note_ids', metavar='', nargs='+', - help=_('Notification IDs to acknowledge.')) -def do_notification_acknowledge(sc, args): - """ - Acknowledge notification. - """ - show_deprecated('stacktask notification-acknowledge', - 'openstack admin task notification acknowledge') - - try: - resp = sc.notifications.acknowledge(note_list=args.note_ids) - - print 'Success:', ' '.join(resp.notes) - except exc.HTTPNotFound: - raise exc.CommandError(_('Notification not found: %s') % - args.note_id) - except exc.HTTPBadRequest as e: - print e.message - - -# Tokens - -@utils.arg('--filters', default={}, - help=_('Filters to use when getting the list.')) -def do_token_list(sc, args): - """ - Show all tokens. - - This is an admin only endpoint. - """ - show_deprecated('stacktask token-list', - 'openstack admin task token list') - fields = ['token', 'task', 'created_on', 'expires'] - token_list = sc.tokens.list(filters=args.filters) - utils.print_list(token_list, fields) - - -@utils.arg('token', metavar='', - help=_('Token id of the task')) -def do_token_show(sc, args): - """ - Show details of this token - including the arguments required for submit - """ - show_deprecated('stacktask token-show', - 'openstack admin task token show') - try: - token = sc.tokens.get(args.token) - except exc.HTTPNotFound as e: - print e.message - print "Requested Token was not found." - else: - utils.print_dict(token.to_dict()) - - -@utils.arg('token', metavar='', - help=_('Token id of the task')) -@utils.arg('--password', metavar='', required=True, - help=_('Password of the new user.')) -def do_token_submit_password(sc, args): - """ - Submit this token to set or update your password. - """ - show_deprecated('stacktask token-submit-password', - 'openstack admin task token submit') - json_data = {'password': args.password} - _token_submit(sc, args, json_data) - - -@utils.arg('token', metavar='', - help=_('Token id of the task')) -@utils.arg('--data', metavar='', required=True, - help=_('Json with the data to submit.')) -def do_token_submit(sc, args): - """ - Submit this token to finalise Task. - """ - show_deprecated('stacktask token-submit', - 'openstack admin task token submit') - try: - json_data = json.loads(args.data) - except ValueError as e: - print e.message - print "Json data invalid." - return - _token_submit(sc, args, json_data) - - -def _token_submit(sc, args, json_data): - try: - sc.tokens.submit(args.token, json_data) - except exc.HTTPNotFound as e: - print e.message - print "Requested token was not found." - except exc.BadRequest as e: - print e.message - print "Bad request. Did you omit a required parameter?" - else: - print "Token submitted." - - -def do_token_clear_expired(sc, args): - """ - Clear all expired tokens. - - This is an admin only endpoint. - """ - show_deprecated('stacktask token-clear-expired', - 'openstack admin task token clear') - try: - resp = sc.tokens.clear_expired() - except exc.HTTPNotFound as e: - print e.message - except exc.HTTPBadRequest as e: - print e.message - else: - print 'Success: %s' % resp.json()['notes'] - fields = ['token', 'task', 'created_on', 'expires'] - token_list = sc.tokens.list({}) - utils.print_list(token_list, fields) - - -# User - -@utils.arg('user_id', metavar='', - help=_('User id.')) -def do_user_show(sc, args): - """ - Show user details. - """ - show_deprecated('stacktask user-show', 'openstack project user show') - - try: - user = sc.users.get(args.user_id) - except exc.HTTPNotFound as e: - print e.message - print "Requested User was not found." - else: - utils.print_dict(user.to_dict()) - - -def do_user_list(sc, args): - """List all users in project""" - show_deprecated('stacktask user-list', 'openstack project user list') - - kwargs = {} - fields = ['id', 'email', 'name', 'roles', 'cohort', 'status'] - - project_users = sc.users.list(**kwargs) - utils.print_list(project_users, fields, sortby_index=1) - - -@utils.arg('--roles', metavar='', nargs='+', required=True, - help=_('Roles to grant to new user')) -@utils.arg('--username', metavar='', default=None, - help=_('username of user to invite')) -@utils.arg('--email', metavar='', required=True, - help=_('Email address of user to invite')) -def do_user_invite(sc, args): - """ - Invites a user to become a member of a project. - User does not need to have an existing openstack account. - """ - show_deprecated('stacktask user-invite', 'openstack project user invite') - - roles = args.roles or ['Member'] - - try: - sc.users.invite( - username=args.username, email=args.email, role_list=roles) - except exc.HTTPNotFound as e: - print e.message - print e - except exc.HTTPBadRequest as e: - print "400 Bad Request: " + e.message - print e - else: - print "Invitation sent." - do_user_list(sc, args) - - -@utils.arg('user_id', metavar='', - help=_('User id for unconfirmed user.')) -def do_user_invite_cancel(sc, args): - """ Cancel user invitation. """ - show_deprecated('stacktask user-invite-cancel', - 'openstack project user invite cancel') - try: - resp = sc.users.cancel(args.user_id) - print 'Success: %s' % resp.json() - except exc.HTTPNotFound as e: - print e.message - print "Requested User was not found." - - -@utils.arg('--user', '--user-id', metavar='', required=True, - help=_('Name or ID of user.')) -def do_user_role_list(sc, args): - """ List the current roles of a user""" - show_deprecated('stacktask user-role-list', - 'openstack project user role list') - fields = ['id', 'name'] - user = utils.find_resource(sc.users, args.user) - kwargs = {'user': user.id} - roles = sc.user_roles.list(**kwargs) - utils.print_list(roles, fields, sortby_index=0) - - -@utils.arg('--user', '--user-id', metavar='', required=True, - help=_('Name or ID of user.')) -@utils.arg('--role', '--role-id', metavar='', required=True, - help=_('Name or ID of role.')) -def do_user_role_add(sc, args): - """Add a role to user""" - show_deprecated('stacktask user-role-add', - 'openstack project user role add') - - role = utils.find_resource(sc.managed_roles, args.role) - user = utils.find_resource(sc.users, args.user) - if sc.user_roles.add(user.id, role=role.name): - print "Task has been sucessfully completed.\n" - do_user_list(sc, args) - else: - print "Your task was not sucessfully completed." - - -@utils.arg('--user', '--user-id', metavar='', - help=_('Name or ID of user.')) -@utils.arg('--role', '--role-id', metavar='', required=True, - help=_('Name or ID of role.')) -def do_user_role_remove(sc, args): - """Remove a role from a user""" - show_deprecated('stacktask user-role-remove', - 'openstack project user role remove') - - role = utils.find_resource(sc.managed_roles, args.role) - user = utils.find_resource(sc.users, args.user) - if sc.user_roles.remove(user.id, role=role.name): - print "Task has been sucessfully completed.\n" - do_user_list(sc, args) - else: - print "Your task was not sucessfully completed." - - -@utils.arg('email', metavar='', - help=_('email of the user account to reset')) -def do_user_password_forgot(sc, args): - show_deprecated('stacktask user-password-forgot', - 'openstack password forgot') - - """Request a password reset email for a user.""" - sc.users.password_forgot(args.email) - print "Task has been sucessfully submitted." - print "If a user with that email exists, a reset token will be issued." - - -@utils.arg('email', metavar='', - help=_('email of the user account to reset')) -def do_user_password_reset(sc, args): - show_deprecated('stacktask user-password-reset', - 'openstack password-reset') - - """Force a password reset for a user. This is an admin only function.""" - sc.users.password_force_reset(args.email) - print "Task has been sucessfully submitted." - print "If a user with that email exists, a reset token will be issued." - - -def do_managed_role_list(sc, args): - """List roles that may be managed in a given project""" - show_deprecated('stacktask managed-role-list', - 'openstack project manageable roles') - - fields = ['id', 'name'] - kwargs = {} - roles = sc.managed_roles.list(**kwargs) - utils.print_list(roles, fields, sortby_index=1) - - -# Status - -def do_status(sc, args): - """Requests server status endpoint and returns details of the api server""" - show_deprecated('stacktask status', 'openstack stacktask status') - - status = sc.status.get() - if status.status_code != 200: - print "Failed: %s" % status.reason - return - print json.dumps( - status.json(), sort_keys=True, - indent=4, separators=(',', ': ')) - - -# Sign-up -@utils.arg('user', metavar='', - help=_('User name for new account.')) -@utils.arg('email', metavar='', - help=_('email of the new account')) -@utils.arg('project_name', metavar='', - help=_('name of the new project')) -def do_sign_up(sc, args): - """Submits a sign-up from a user requesting a new project and account. - - Note: You can perform an unauthenticated request to this endpoint using - --os-no-client-auth and --bypass-url - """ - show_deprecated('stacktask sign-up', 'openstack signup') - - status = sc.signup.post(args.user, args.email, args.project_name) - if status.status_code != 200: - print "Failed: %s" % status.reason - return - print json.dumps( - status.json(), sort_keys=True, - indent=4, separators=(',', ': '))