diff options
author | Jenkins <jenkins@review.openstack.org> | 2013-07-31 15:16:48 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2013-07-31 15:16:48 +0000 |
commit | 661da45f58d3a48cce4720aa2f10b6cd616376e8 (patch) | |
tree | b146866aff2fa0c87c6511f0154b08ea705c145c | |
parent | 9ec1cf385ee1434ebdb13a9de2f35024925ff50f (diff) | |
parent | c94e262df8d2d37e6c2043a3c3d0bc1cb78348a5 (diff) | |
download | python-openstackclient-661da45f58d3a48cce4720aa2f10b6cd616376e8.tar.gz |
Merge "Add security group commands"
-rw-r--r-- | openstack-common.conf | 1 | ||||
-rw-r--r-- | openstackclient/common/parseractions.py | 24 | ||||
-rw-r--r-- | openstackclient/common/utils.py | 5 | ||||
-rw-r--r-- | openstackclient/compute/v2/security_group.py | 394 | ||||
-rw-r--r-- | openstackclient/compute/v2/server.py | 73 | ||||
-rw-r--r-- | openstackclient/identity/client.py | 13 | ||||
-rw-r--r-- | openstackclient/openstack/common/gettextutils.py | 259 | ||||
-rw-r--r-- | openstackclient/openstack/common/strutils.py | 218 | ||||
-rw-r--r-- | setup.cfg | 11 |
9 files changed, 996 insertions, 2 deletions
diff --git a/openstack-common.conf b/openstack-common.conf index 867e204a..5e55d586 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -5,6 +5,7 @@ module=cfg module=iniparser module=install_venv_common module=openstackkeyring +module=strutils # The base module to hold the copy of openstack.common base=openstackclient diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index f111c264..644472d8 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -32,3 +32,27 @@ class KeyValueAction(argparse.Action): getattr(namespace, self.dest, {}).update([values.split('=', 1)]) else: getattr(namespace, self.dest, {}).pop(values, None) + + +class RangeAction(argparse.Action): + """A custom action to parse a single value or a range of values.""" + def __call__(self, parser, namespace, values, option_string=None): + range = values.split(':') + if len(range) == 0: + # Nothing passed, return a zero default + setattr(namespace, self.dest, (0, 0)) + elif len(range) == 1: + # Only a single value is present + setattr(namespace, self.dest, (int(range[0]), int(range[0]))) + elif len(range) == 2: + # Range of two values + if int(range[0]) <= int(range[1]): + setattr(namespace, self.dest, (int(range[0]), int(range[1]))) + else: + msg = "Invalid range, %s is not less than %s" % \ + (range[0], range[1]) + raise argparse.ArgumentError(self, msg) + else: + # Too many values + msg = "Invalid range, too many values" + raise argparse.ArgumentError(self, msg) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index fd504ea1..4d2afd15 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -16,11 +16,13 @@ """Common client utilities""" import os +import six import sys import time import uuid from openstackclient.common import exceptions +from openstackclient.openstack.common import strutils def find_resource(manager, name_or_id): @@ -84,7 +86,8 @@ def format_dict(data): output = "" for s in data: - output = output + s + "='" + data[s] + "', " + output = output + s + "='" + \ + strutils.safe_encode(six.text_type(data[s])) + "', " return output[:-2] diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py new file mode 100644 index 00000000..a1dc786d --- /dev/null +++ b/openstackclient/compute/v2/security_group.py @@ -0,0 +1,394 @@ +# Copyright 2012 OpenStack Foundation +# Copyright 2013 Nebula 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. +# + +"""Compute v2 Security Group action implementations""" + +import logging +import six + +from cliff import command +from cliff import lister +from cliff import show + +from novaclient.v1_1 import security_group_rules +from openstackclient.common import parseractions +from openstackclient.common import utils + + +def _xform_security_group_rule(sgroup): + info = {} + info.update(sgroup) + info.update( + {'port_range': "%u:%u" % ( + info.pop('from_port'), + info.pop('to_port'), + )} + ) + info['ip_range'] = info['ip_range']['cidr'] + if info['ip_protocol'] == 'icmp': + info['port_range'] = '' + return info + + +class CreateSecurityGroup(show.ShowOne): + """Create a new security group""" + + log = logging.getLogger(__name__ + ".CreateSecurityGroup") + + def get_parser(self, prog_name): + parser = super(CreateSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="<name>", + help="New security group name", + ) + parser.add_argument( + "--description", + metavar="<description>", + help="Security group description", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + data = compute_client.security_groups.create( + parsed_args.name, + parsed_args.description, + ) + + info = {} + info.update(data._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteSecurityGroup(command.Command): + """Delete a security group""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroup') + + def get_parser(self, prog_name): + parser = super(DeleteSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='<group>', + help='Name or ID of security group to delete', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + data = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + compute_client.security_groups.delete(data.id) + return + + +class ListSecurityGroup(lister.Lister): + """List all security groups""" + + log = logging.getLogger(__name__ + ".ListSecurityGroup") + + def get_parser(self, prog_name): + parser = super(ListSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Display information from all projects (admin only)', + ) + return parser + + def take_action(self, parsed_args): + + def _get_project(project_id): + try: + return getattr(project_hash[project_id], 'name', project_id) + except KeyError: + return project_id + + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + columns = ( + "ID", + "Name", + "Description", + ) + column_headers = columns + if parsed_args.all_projects: + # TODO(dtroyer): Translate Project_ID to Project (name) + columns = columns + ('Tenant ID',) + column_headers = column_headers + ('Project',) + search = {'all_tenants': parsed_args.all_projects} + data = compute_client.security_groups.list(search_opts=search) + + projects = self.app.client_manager.identity.projects.list() + project_hash = {} + for project in projects: + project_hash[project.id] = project + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Tenant ID': _get_project}, + ) for s in data)) + + +class SetSecurityGroup(show.ShowOne): + """Set security group properties""" + + log = logging.getLogger(__name__ + '.SetSecurityGroup') + + def get_parser(self, prog_name): + parser = super(SetSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='<group>', + help='Name or ID of security group to change', + ) + parser.add_argument( + '--name', + metavar='<new-name>', + help='New security group name', + ) + parser.add_argument( + "--description", + metavar="<description>", + help="New security group name", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + data = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + if parsed_args.name: + data.name = parsed_args.name + if parsed_args.description: + data.description = parsed_args.description + + info = {} + info.update(compute_client.security_groups.update( + data, + data.name, + data.description, + )._info) + + if info: + return zip(*sorted(six.iteritems(info))) + else: + return ({}, {}) + + +class ShowSecurityGroup(show.ShowOne): + """Show a specific security group""" + + log = logging.getLogger(__name__ + '.ShowSecurityGroup') + + def get_parser(self, prog_name): + parser = super(ShowSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='<group>', + help='Name or ID of security group to change', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + info = {} + info.update(utils.find_resource( + compute_client.security_groups, + parsed_args.group, + )._info) + rules = [] + for r in info['rules']: + rules.append(utils.format_dict(_xform_security_group_rule(r))) + + # Format rules into a list of strings + info.update( + {'rules': rules} + ) + # Map 'tenant_id' column to 'project_id' + info.update( + {'project_id': info.pop('tenant_id')} + ) + + return zip(*sorted(six.iteritems(info))) + + +class CreateSecurityGroupRule(show.ShowOne): + """Create a new security group rule""" + + log = logging.getLogger(__name__ + ".CreateSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='<group>', + help='Create rule in this security group', + ) + parser.add_argument( + "--proto", + metavar="<proto>", + default="tcp", + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + parser.add_argument( + "--src-ip", + metavar="<ip-address>", + default="0.0.0.0/0", + help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + ) + parser.add_argument( + "--dst-port", + metavar="<port-range>", + action=parseractions.RangeAction, + help="Destination port, may be a range: 137:139 (default: 0; " + "only required for proto tcp and udp)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + from_port, to_port = parsed_args.dst_port + data = compute_client.security_group_rules.create( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + ) + + info = _xform_security_group_rule(data._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteSecurityGroupRule(command.Command): + """Delete a security group rule""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') + + def get_parser(self, prog_name): + parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='<group>', + help='Create rule in this security group', + ) + parser.add_argument( + "--proto", + metavar="<proto>", + default="tcp", + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + parser.add_argument( + "--src-ip", + metavar="<ip-address>", + default="0.0.0.0/0", + help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + ) + parser.add_argument( + "--dst-port", + metavar="<port-range>", + action=parseractions.RangeAction, + help="Destination port, may be a range: 137:139 (default: 0; " + "only required for proto tcp and udp)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + from_port, to_port = parsed_args.dst_port + # sigh...delete by ID? + compute_client.security_group_rules.delete( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + ) + return + + +class ListSecurityGroupRule(lister.Lister): + """List all security group rules""" + + log = logging.getLogger(__name__ + ".ListSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(ListSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='<group>', + help='Create rule in this security group', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + # Argh, the rules are not Resources... + rules = [] + for rule in group.rules: + rules.append(security_group_rules.SecurityGroupRule( + compute_client.security_group_rules, + _xform_security_group_rule(rule), + )) + + columns = column_headers = ( + "ID", + "IP Protocol", + "IP Range", + "Port Range", + ) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 793461eb..950f0e02 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -142,6 +142,43 @@ class AddServerVolume(command.Command): ) +class AddServerSecurityGroup(command.Command): + """Add security group to server""" + + log = logging.getLogger(__name__ + '.AddServerSecurityGroup') + + def get_parser(self, prog_name): + parser = super(AddServerSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help='Name or ID of server to use', + ) + parser.add_argument( + 'group', + metavar='<group>', + help='Name or ID of security group to add to server', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + security_group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + server.add_security_group(security_group) + return + + class CreateServer(show.ShowOne): """Create a new server""" @@ -732,6 +769,42 @@ class RebuildServer(show.ShowOne): return zip(*sorted(six.iteritems(details))) +class RemoveServerSecurityGroup(command.Command): + """Remove security group from server""" + + log = logging.getLogger(__name__ + '.RemoveServerSecurityGroup') + + def get_parser(self, prog_name): + parser = super(RemoveServerSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help='Name or ID of server to use', + ) + parser.add_argument( + 'group', + metavar='<group>', + help='Name or ID of security group to remove from server', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + security_group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + server.remove_security_group(security_group) + + class RemoveServerVolume(command.Command): """Remove volume from server""" diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 748d1666..0f8fbb81 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -15,6 +15,7 @@ import logging +from keystoneclient.v2_0 import client as identity_client_v2_0 from openstackclient.common import utils @@ -22,7 +23,7 @@ LOG = logging.getLogger(__name__) API_NAME = 'identity' API_VERSIONS = { - '2.0': 'keystoneclient.v2_0.client.Client', + '2.0': 'openstackclient.identity.client.IdentityClientv2_0', '3': 'keystoneclient.v3.client.Client', } @@ -48,3 +49,13 @@ def make_client(instance): auth_url=instance._auth_url, region_name=instance._region_name) return client + + +class IdentityClientv2_0(identity_client_v2_0.Client): + """Tweak the earlier client class to deal with some changes""" + def __getattr__(self, name): + # Map v3 'projects' back to v2 'tenants' + if name == "projects": + return self.tenants + else: + raise AttributeError, name diff --git a/openstackclient/openstack/common/gettextutils.py b/openstackclient/openstack/common/gettextutils.py new file mode 100644 index 00000000..2dd5449e --- /dev/null +++ b/openstackclient/openstack/common/gettextutils.py @@ -0,0 +1,259 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat, Inc. +# All Rights Reserved. +# Copyright 2013 IBM Corp. +# +# 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. + +""" +gettext for openstack-common modules. + +Usual usage in an openstack.common module: + + from openstackclient.openstack.common.gettextutils import _ +""" + +import copy +import gettext +import logging.handlers +import os +import re +import UserString + +import six + +_localedir = os.environ.get('openstackclient'.upper() + '_LOCALEDIR') +_t = gettext.translation('openstackclient', localedir=_localedir, fallback=True) + + +def _(msg): + return _t.ugettext(msg) + + +def install(domain): + """Install a _() function using the given translation domain. + + Given a translation domain, install a _() function using gettext's + install() function. + + The main difference from gettext.install() is that we allow + overriding the default localedir (e.g. /usr/share/locale) using + a translation-domain-specific environment variable (e.g. + NOVA_LOCALEDIR). + """ + gettext.install(domain, + localedir=os.environ.get(domain.upper() + '_LOCALEDIR'), + unicode=True) + + +""" +Lazy gettext functionality. + +The following is an attempt to introduce a deferred way +to do translations on messages in OpenStack. We attempt to +override the standard _() function and % (format string) operation +to build Message objects that can later be translated when we have +more information. Also included is an example LogHandler that +translates Messages to an associated locale, effectively allowing +many logs, each with their own locale. +""" + + +def get_lazy_gettext(domain): + """Assemble and return a lazy gettext function for a given domain. + + Factory method for a project/module to get a lazy gettext function + for its own translation domain (i.e. nova, glance, cinder, etc.) + """ + + def _lazy_gettext(msg): + """Create and return a Message object. + + Message encapsulates a string so that we can translate it later when + needed. + """ + return Message(msg, domain) + + return _lazy_gettext + + +class Message(UserString.UserString, object): + """Class used to encapsulate translatable messages.""" + def __init__(self, msg, domain): + # _msg is the gettext msgid and should never change + self._msg = msg + self._left_extra_msg = '' + self._right_extra_msg = '' + self.params = None + self.locale = None + self.domain = domain + + @property + def data(self): + # NOTE(mrodden): this should always resolve to a unicode string + # that best represents the state of the message currently + + localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR') + if self.locale: + lang = gettext.translation(self.domain, + localedir=localedir, + languages=[self.locale], + fallback=True) + else: + # use system locale for translations + lang = gettext.translation(self.domain, + localedir=localedir, + fallback=True) + + full_msg = (self._left_extra_msg + + lang.ugettext(self._msg) + + self._right_extra_msg) + + if self.params is not None: + full_msg = full_msg % self.params + + return six.text_type(full_msg) + + def _save_dictionary_parameter(self, dict_param): + full_msg = self.data + # look for %(blah) fields in string; + # ignore %% and deal with the + # case where % is first character on the line + keys = re.findall('(?:[^%]|^)%\((\w*)\)[a-z]', full_msg) + + # if we don't find any %(blah) blocks but have a %s + if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg): + # apparently the full dictionary is the parameter + params = copy.deepcopy(dict_param) + else: + params = {} + for key in keys: + try: + params[key] = copy.deepcopy(dict_param[key]) + except TypeError: + # cast uncopyable thing to unicode string + params[key] = unicode(dict_param[key]) + + return params + + def _save_parameters(self, other): + # we check for None later to see if + # we actually have parameters to inject, + # so encapsulate if our parameter is actually None + if other is None: + self.params = (other, ) + elif isinstance(other, dict): + self.params = self._save_dictionary_parameter(other) + else: + # fallback to casting to unicode, + # this will handle the problematic python code-like + # objects that cannot be deep-copied + try: + self.params = copy.deepcopy(other) + except TypeError: + self.params = unicode(other) + + return self + + # overrides to be more string-like + def __unicode__(self): + return self.data + + def __str__(self): + return self.data.encode('utf-8') + + def __getstate__(self): + to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg', + 'domain', 'params', 'locale'] + new_dict = self.__dict__.fromkeys(to_copy) + for attr in to_copy: + new_dict[attr] = copy.deepcopy(self.__dict__[attr]) + + return new_dict + + def __setstate__(self, state): + for (k, v) in state.items(): + setattr(self, k, v) + + # operator overloads + def __add__(self, other): + copied = copy.deepcopy(self) + copied._right_extra_msg += other.__str__() + return copied + + def __radd__(self, other): + copied = copy.deepcopy(self) + copied._left_extra_msg += other.__str__() + return copied + + def __mod__(self, other): + # do a format string to catch and raise + # any possible KeyErrors from missing parameters + self.data % other + copied = copy.deepcopy(self) + return copied._save_parameters(other) + + def __mul__(self, other): + return self.data * other + + def __rmul__(self, other): + return other * self.data + + def __getitem__(self, key): + return self.data[key] + + def __getslice__(self, start, end): + return self.data.__getslice__(start, end) + + def __getattribute__(self, name): + # NOTE(mrodden): handle lossy operations that we can't deal with yet + # These override the UserString implementation, since UserString + # uses our __class__ attribute to try and build a new message + # after running the inner data string through the operation. + # At that point, we have lost the gettext message id and can just + # safely resolve to a string instead. + ops = ['capitalize', 'center', 'decode', 'encode', + 'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip', + 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] + if name in ops: + return getattr(self.data, name) + else: + return UserString.UserString.__getattribute__(self, name) + + +class LocaleHandler(logging.Handler): + """Handler that can have a locale associated to translate Messages. + + A quick example of how to utilize the Message class above. + LocaleHandler takes a locale and a target logging.Handler object + to forward LogRecord objects to after translating the internal Message. + """ + + def __init__(self, locale, target): + """Initialize a LocaleHandler + + :param locale: locale to use for translating messages + :param target: logging.Handler object to forward + LogRecord objects to after translation + """ + logging.Handler.__init__(self) + self.locale = locale + self.target = target + + def emit(self, record): + if isinstance(record.msg, Message): + # set the locale and resolve to a string + record.msg.locale = self.locale + + self.target.emit(record) diff --git a/openstackclient/openstack/common/strutils.py b/openstackclient/openstack/common/strutils.py new file mode 100644 index 00000000..e3f26a78 --- /dev/null +++ b/openstackclient/openstack/common/strutils.py @@ -0,0 +1,218 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 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. + +""" +System-level utilities and helper functions. +""" + +import re +import sys +import unicodedata + +import six + +from openstackclient.openstack.common.gettextutils import _ # noqa + + +# Used for looking up extensions of text +# to their 'multiplied' byte amount +BYTE_MULTIPLIERS = { + '': 1, + 't': 1024 ** 4, + 'g': 1024 ** 3, + 'm': 1024 ** 2, + 'k': 1024, +} +BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)') + +TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') +FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') + +SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") +SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") + + +def int_from_bool_as_string(subject): + """Interpret a string as a boolean and return either 1 or 0. + + Any string value in: + + ('True', 'true', 'On', 'on', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + return bool_from_string(subject) and 1 or 0 + + +def bool_from_string(subject, strict=False): + """Interpret a string as a boolean. + + A case-insensitive match is performed such that strings matching 't', + 'true', 'on', 'y', 'yes', or '1' are considered True and, when + `strict=False`, anything else is considered False. + + Useful for JSON-decoded stuff and config file parsing. + + If `strict=True`, unrecognized values, including None, will raise a + ValueError which is useful when parsing values passed in from an API call. + Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. + """ + if not isinstance(subject, six.string_types): + subject = str(subject) + + lowered = subject.strip().lower() + + if lowered in TRUE_STRINGS: + return True + elif lowered in FALSE_STRINGS: + return False + elif strict: + acceptable = ', '.join( + "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) + msg = _("Unrecognized value '%(val)s', acceptable values are:" + " %(acceptable)s") % {'val': subject, + 'acceptable': acceptable} + raise ValueError(msg) + else: + return False + + +def safe_decode(text, incoming=None, errors='strict'): + """Decodes incoming str using `incoming` if they're not already unicode. + + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a unicode `incoming` encoded + representation of it. + :raises TypeError: If text is not an isntance of str + """ + if not isinstance(text, six.string_types): + raise TypeError("%s can't be decoded" % type(text)) + + if isinstance(text, six.text_type): + return text + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + try: + return text.decode(incoming, errors) + except UnicodeDecodeError: + # Note(flaper87) If we get here, it means that + # sys.stdin.encoding / sys.getdefaultencoding + # didn't return a suitable encoding to decode + # text. This happens mostly when global LANG + # var is not set correctly and there's no + # default encoding. In this case, most likely + # python will use ASCII or ANSI encoders as + # default encodings but they won't be capable + # of decoding non-ASCII characters. + # + # Also, UTF-8 is being used since it's an ASCII + # extension. + return text.decode('utf-8', errors) + + +def safe_encode(text, incoming=None, + encoding='utf-8', errors='strict'): + """Encodes incoming str/unicode using `encoding`. + + If incoming is not specified, text is expected to be encoded with + current python's default encoding. (`sys.getdefaultencoding`) + + :param incoming: Text's current encoding + :param encoding: Expected encoding for text (Default UTF-8) + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a bytestring `encoding` encoded + representation of it. + :raises TypeError: If text is not an isntance of str + """ + if not isinstance(text, six.string_types): + raise TypeError("%s can't be encoded" % type(text)) + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + if isinstance(text, six.text_type): + return text.encode(encoding, errors) + elif text and encoding != incoming: + # Decode text before encoding it with `encoding` + text = safe_decode(text, incoming, errors) + return text.encode(encoding, errors) + + return text + + +def to_bytes(text, default=0): + """Converts a string into an integer of bytes. + + Looks at the last characters of the text to determine + what conversion is needed to turn the input text into a byte number. + Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive) + + :param text: String input for bytes size conversion. + :param default: Default return value when text is blank. + + """ + match = BYTE_REGEX.search(text) + if match: + magnitude = int(match.group(1)) + mult_key_org = match.group(2) + if not mult_key_org: + return magnitude + elif text: + msg = _('Invalid string format: %s') % text + raise TypeError(msg) + else: + return default + mult_key = mult_key_org.lower().replace('b', '', 1) + multiplier = BYTE_MULTIPLIERS.get(mult_key) + if multiplier is None: + msg = _('Unknown byte multiplier: %s') % mult_key_org + raise TypeError(msg) + return magnitude * multiplier + + +def to_slug(value, incoming=None, errors="strict"): + """Normalize string. + + Convert to lowercase, remove non-word characters, and convert spaces + to hyphens. + + Inspired by Django's `slugify` filter. + + :param value: Text to slugify + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: slugified unicode representation of `value` + :raises TypeError: If text is not an instance of str + """ + value = safe_decode(value, incoming, errors) + # NOTE(aababilov): no need to use safe_(encode|decode) here: + # encodings are always "ascii", error handling is always "ignore" + # and types are always known (first: unicode; second: str) + value = unicodedata.normalize("NFKD", value).encode( + "ascii", "ignore").decode("ascii") + value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() + return SLUGIFY_HYPHENATE_RE.sub("-", value) @@ -210,6 +210,16 @@ openstack.compute.v2 = project_usage_list = openstackclient.compute.v2.usage:ListUsage + security_group_create = openstackclient.compute.v2.secgroup:CreateSecurityGroup + security_group_delete = openstackclient.compute.v2.secgroup:DeleteSecurityGroup + security_group_list = openstackclient.compute.v2.secgroup:ListSecurityGroup + security_group_set = openstackclient.compute.v2.secgroup:SetSecurityGroup + security_group_show = openstackclient.compute.v2.secgroup:ShowSecurityGroup + security_group_rule_create = openstackclient.compute.v2.secgroup:CreateSecurityGroupRule + security_group_rule_delete = openstackclient.compute.v2.secgroup:DeleteSecurityGroupRule + security_group_rule_list = openstackclient.compute.v2.secgroup:ListSecurityGroupRule + + server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer @@ -219,6 +229,7 @@ openstack.compute.v2 = server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer + server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSecurityGroup server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer server_resize = openstackclient.compute.v2.server:ResizeServer |