Renamed to Adjutant and cull legacy code

* Renaming from StackTask to Adjutant.
* Cull unused or old elements of code originally
  from heatclient.
* Remove old cli as deprecation is redundant with the
  rename as well. Rename is now a blank slate for the cli
  and the cli is entirely OSC based.

Change-Id: Ic548bdd0f5dadfe537ffe5e1eed7725358c13fce
This commit is contained in:
adrian-turjak 2017-05-25 20:55:07 +12:00
parent 18a17265a8
commit bf62bc73a0
47 changed files with 198 additions and 3388 deletions

3
.gitignore vendored
View File

@ -13,9 +13,10 @@ doc/source/api
doc/build
*.egg
.eggs/*
stacktaskclient/versioninfo
adjutantclient/versioninfo
*.egg-info
*.log
.testrepository
.pypirc
env/
*~

View File

@ -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

View File

@ -1,2 +0,0 @@
Dale Smith <dale@catalyst-eu.net>
adriant <adriant@catalyst.net.nz>

View File

@ -1,4 +1,4 @@
include AUTHORS
include LICENSE
include README.md
include README.rst
include ChangeLog

View File

View File

@ -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().

View File

@ -13,4 +13,4 @@
import pbr.version
__version__ = pbr.version.VersionInfo('python-stacktaskclient').version_string()
__version__ = pbr.version.VersionInfo('python-adjutantclient').version_string()

37
adjutantclient/_i18n.py Normal file
View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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") %
{

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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()))

View File

@ -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.")

View File

@ -15,4 +15,4 @@
__all__ = ['Client']
from stacktaskclient.v1.client import Client # noqa
from adjutantclient.v1.client import Client # noqa

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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 =

View File

@ -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

View File

@ -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

View File

@ -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
"""

View File

@ -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)

View File

@ -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)

View File

@ -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,
})

View File

@ -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)

View File

@ -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)

View File

@ -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='<ca-certificate-file>',
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='<subcommand>')
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='<subcommand>', nargs='?',
help=_('Display help for <subcommand>.'))
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()

View File

@ -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 "<BuildInfo %s>" % 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

View File

@ -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='<taskid>',
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='<taskid>',
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='<taskid>',
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='<taskid>',
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='<taskid>',
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='<noteid>',
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='<noteids>', 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='<token>',
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='<token>',
help=_('Token id of the task'))
@utils.arg('--password', metavar='<password>', 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='<token>',
help=_('Token id of the task'))
@utils.arg('--data', metavar='<data>', 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='<userid>',
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='<roles>', nargs='+', required=True,
help=_('Roles to grant to new user'))
@utils.arg('--username', metavar='<username>', default=None,
help=_('username of user to invite'))
@utils.arg('--email', metavar='<email>', 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='<userid>',
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='<user>', 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='<user>', required=True,
help=_('Name or ID of user.'))
@utils.arg('--role', '--role-id', metavar='<role>', 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='<user>',
help=_('Name or ID of user.'))
@utils.arg('--role', '--role-id', metavar='<role>', 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='<email>',
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='<email>',
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='<user>',
help=_('User name for new account.'))
@utils.arg('email', metavar='<email>',
help=_('email of the new account'))
@utils.arg('project_name', metavar='<project_name>',
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 <stacktask 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=(',', ': '))