summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2013-07-31 15:16:48 +0000
committerGerrit Code Review <review@openstack.org>2013-07-31 15:16:48 +0000
commit661da45f58d3a48cce4720aa2f10b6cd616376e8 (patch)
treeb146866aff2fa0c87c6511f0154b08ea705c145c
parent9ec1cf385ee1434ebdb13a9de2f35024925ff50f (diff)
parentc94e262df8d2d37e6c2043a3c3d0bc1cb78348a5 (diff)
downloadpython-openstackclient-661da45f58d3a48cce4720aa2f10b6cd616376e8.tar.gz
Merge "Add security group commands"
-rw-r--r--openstack-common.conf1
-rw-r--r--openstackclient/common/parseractions.py24
-rw-r--r--openstackclient/common/utils.py5
-rw-r--r--openstackclient/compute/v2/security_group.py394
-rw-r--r--openstackclient/compute/v2/server.py73
-rw-r--r--openstackclient/identity/client.py13
-rw-r--r--openstackclient/openstack/common/gettextutils.py259
-rw-r--r--openstackclient/openstack/common/strutils.py218
-rw-r--r--setup.cfg11
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)
diff --git a/setup.cfg b/setup.cfg
index f1f5cf82..41f1c254 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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