Add security group commands
* Add security group: create, delete, list, set, show * Add server: add secgroup, remove secgroup * Add security group rule: create, delete, list * Add Oslo's strutils and gettextutils * Adds parseractions.RangeAction() to handle option arguments of either a single number or a range of numbers: '--port 25' or '--port 1024:65535' Blueprint: nova-client Change-Id: Iad2de1b273ba29197709fc4c6a1036b4ae99725f
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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] | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										394
									
								
								openstackclient/compute/v2/security_group.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										394
									
								
								openstackclient/compute/v2/security_group.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| @@ -141,6 +141,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""" | ||||
|  | ||||
| @@ -731,6 +768,42 @@ class RebuildServer(show.ShowOne): | ||||
|         return zip(*sorted(details.iteritems())) | ||||
|  | ||||
|  | ||||
| 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""" | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										259
									
								
								openstackclient/openstack/common/gettextutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								openstackclient/openstack/common/gettextutils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
							
								
								
									
										218
									
								
								openstackclient/openstack/common/strutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								openstackclient/openstack/common/strutils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
							
								
								
									
										11
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Dean Troyer
					Dean Troyer