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:
parent
65d2a14e3e
commit
c94e262df8
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user