Skip to content

Commit 818c948

Browse files
author
Dean Troyer
committed
Clean up properties (metadata) formatting
* Reformat default dict output to key='value' using utils.format_dict() * Changes utils.get_item_properties() to pass the specific field to the formatter function rather than the entire resource object, this allows the formatter to handle multiple attributes. * Updates server, volume, volume type commands Change-Id: I90eebf6b84ae200532f09cd925f371598ea54a64
1 parent ce72256 commit 818c948

4 files changed

Lines changed: 154 additions & 78 deletions

File tree

‎openstackclient/common/utils.py‎

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2012-2013 OpenStack, LLC.
1+
# Copyright 2012-2013 OpenStack Foundation
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain
@@ -73,6 +73,20 @@ def find_resource(manager, name_or_id):
7373
raise
7474

7575

76+
def format_dict(data):
77+
"""Return a formatted string of key value pairs
78+
79+
:param data: a dict
80+
:param format: optional formatting hints
81+
:rtype: a string formatted to key='value'
82+
"""
83+
84+
output = ""
85+
for s in data:
86+
output = output + s + "='" + data[s] + "', "
87+
return output[:-2]
88+
89+
7690
def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
7791
"""Return a tuple containing the item properties.
7892
@@ -85,14 +99,14 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}):
8599
row = []
86100

87101
for field in fields:
102+
if field in mixed_case_fields:
103+
field_name = field.replace(' ', '_')
104+
else:
105+
field_name = field.lower().replace(' ', '_')
106+
data = getattr(item, field_name, '')
88107
if field in formatters:
89-
row.append(formatters[field](item))
108+
row.append(formatters[field](data))
90109
else:
91-
if field in mixed_case_fields:
92-
field_name = field.replace(' ', '_')
93-
else:
94-
field_name = field.lower().replace(' ', '_')
95-
data = getattr(item, field_name, '')
96110
row.append(data)
97111
return tuple(row)
98112

‎openstackclient/compute/v2/server.py‎

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2012-2013 OpenStack, LLC.
1+
# Copyright 2012-2013 OpenStack Foundation
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain
@@ -13,7 +13,7 @@
1313
# under the License.
1414
#
1515

16-
"""Server action implementations"""
16+
"""Compute v2 Server action implementations"""
1717

1818
import logging
1919
import os
@@ -25,17 +25,18 @@
2525

2626
from novaclient.v1_1 import servers
2727
from openstackclient.common import exceptions
28+
from openstackclient.common import parseractions
2829
from openstackclient.common import utils
2930

3031

31-
def _format_servers_list_networks(server):
32-
"""Return a string containing the networks a server is attached to.
32+
def _format_servers_list_networks(networks):
33+
"""Return a formatted string of a server's networks
3334
34-
:param server: a single Server resource
35+
:param server: a Server.networks field
3536
:rtype: a string of formatted network addresses
3637
"""
3738
output = []
38-
for (network, addresses) in server.networks.items():
39+
for (network, addresses) in networks.items():
3940
if not addresses:
4041
continue
4142
addresses_csv = ', '.join(addresses)
@@ -73,7 +74,12 @@ def _prep_server_detail(compute_client, server):
7374

7475
# NOTE(dtroyer): novaclient splits these into separate entries...
7576
# Format addresses in a useful way
76-
info['addresses'] = _format_servers_list_networks(server)
77+
info['addresses'] = _format_servers_list_networks(server.networks)
78+
79+
# Map 'metadata' field to 'properties'
80+
info.update(
81+
{'properties': utils.format_dict(info.pop('metadata'))}
82+
)
7783

7884
# Remove values that are long and not too useful
7985
info.pop('links', None)
@@ -116,7 +122,7 @@ def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5,
116122

117123

118124
class CreateServer(show.ShowOne):
119-
"""Create server command"""
125+
"""Create a new server"""
120126

121127
log = logging.getLogger(__name__ + '.CreateServer')
122128

@@ -150,9 +156,8 @@ def get_parser(self, prog_name):
150156
parser.add_argument(
151157
'--property',
152158
metavar='<key=value>',
153-
action='append',
154-
default=[],
155-
help='Property to store for this server '
159+
action=parseractions.KeyValueAction,
160+
help='Set a property on this server '
156161
'(repeat for multiple values)')
157162
parser.add_argument(
158163
'--file',
@@ -228,8 +233,6 @@ def take_action(self, parsed_args):
228233

229234
boot_args = [parsed_args.server_name, image, flavor]
230235

231-
meta = dict(v.split('=', 1) for v in parsed_args.property)
232-
233236
files = {}
234237
for f in parsed_args.file:
235238
dst, src = f.split('=', 1)
@@ -288,7 +291,7 @@ def take_action(self, parsed_args):
288291
config_drive = parsed_args.config_drive
289292

290293
boot_kwargs = dict(
291-
meta=meta,
294+
meta=parsed_args.property,
292295
files=files,
293296
reservation_id=None,
294297
min_count=parsed_args.min,
@@ -337,7 +340,7 @@ def take_action(self, parsed_args):
337340

338341

339342
class ListServer(lister.Lister):
340-
"""List server command"""
343+
"""List servers"""
341344

342345
log = logging.getLogger(__name__ + '.ListServer')
343346

@@ -385,6 +388,11 @@ def get_parser(self, prog_name):
385388
action='store_true',
386389
default=bool(int(os.environ.get("ALL_TENANTS", 0))),
387390
help='display information from all tenants (admin only)')
391+
parser.add_argument(
392+
'--long',
393+
action='store_true',
394+
default=False,
395+
help='Additional fields are listed in output')
388396
return parser
389397

390398
def take_action(self, parsed_args):
@@ -403,13 +411,43 @@ def take_action(self, parsed_args):
403411
'all_tenants': parsed_args.all_tenants,
404412
}
405413
self.log.debug('search options: %s', search_opts)
406-
# FIXME(dhellmann): Consider adding other columns
407-
columns = ('ID', 'Name', 'Status', 'Networks')
414+
415+
if parsed_args.long:
416+
columns = (
417+
'ID',
418+
'Name',
419+
'Status',
420+
'Networks',
421+
'OS-EXT-AZ:availability_zone',
422+
'OS-EXT-SRV-ATTR:host',
423+
'Metadata',
424+
)
425+
column_headers = (
426+
'ID',
427+
'Name',
428+
'Status',
429+
'Networks',
430+
'Availability Zone',
431+
'Host',
432+
'Properties',
433+
)
434+
mixed_case_fields = [
435+
'OS-EXT-AZ:availability_zone',
436+
'OS-EXT-SRV-ATTR:host',
437+
]
438+
else:
439+
columns = ('ID', 'Name', 'Status', 'Networks')
440+
column_headers = columns
441+
mixed_case_fields = []
408442
data = compute_client.servers.list(search_opts=search_opts)
409-
return (columns,
443+
return (column_headers,
410444
(utils.get_item_properties(
411445
s, columns,
412-
formatters={'Networks': _format_servers_list_networks},
446+
mixed_case_fields=mixed_case_fields,
447+
formatters={
448+
'Networks': _format_servers_list_networks,
449+
'Metadata': utils.format_dict,
450+
},
413451
) for s in data))
414452

415453

‎openstackclient/volume/v1/type.py‎

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2012-2013 OpenStack, LLC.
1+
# Copyright 2012-2013 OpenStack Foundation
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain
@@ -26,7 +26,7 @@
2626

2727

2828
class CreateVolumeType(show.ShowOne):
29-
"""Create volume type command"""
29+
"""Create new volume type"""
3030

3131
log = logging.getLogger(__name__ + '.CreateVolumeType')
3232

@@ -37,6 +37,13 @@ def get_parser(self, prog_name):
3737
metavar='<name>',
3838
help='New volume type name',
3939
)
40+
parser.add_argument(
41+
'--property',
42+
metavar='<key=value>',
43+
action=parseractions.KeyValueAction,
44+
help='Property to add for this volume type '
45+
'(repeat option to set multiple properties)',
46+
)
4047
return parser
4148

4249
def take_action(self, parsed_args):
@@ -45,14 +52,21 @@ def take_action(self, parsed_args):
4552
volume_type = volume_client.volume_types.create(
4653
parsed_args.name
4754
)
55+
if parsed_args.property:
56+
volume_type.set_keys(parsed_args.property)
57+
# Map 'extra_specs' column to 'properties'
58+
volume_type._info.update(
59+
{'properties': utils.format_dict(
60+
volume_type._info.pop('extra_specs'))}
61+
)
4862

4963
info = {}
5064
info.update(volume_type._info)
5165
return zip(*sorted(info.iteritems()))
5266

5367

5468
class DeleteVolumeType(command.Command):
55-
"""Delete volume type command"""
69+
"""Delete volume type"""
5670

5771
log = logging.getLogger(__name__ + '.DeleteVolumeType')
5872

@@ -75,7 +89,7 @@ def take_action(self, parsed_args):
7589

7690

7791
class ListVolumeType(lister.Lister):
78-
"""List volume type command"""
92+
"""List volume types"""
7993

8094
log = logging.getLogger(__name__ + '.ListVolumeType')
8195

@@ -92,18 +106,20 @@ def take_action(self, parsed_args):
92106
self.log.debug('take_action(%s)' % parsed_args)
93107
if parsed_args.long:
94108
columns = ('ID', 'Name', 'Extra Specs')
109+
column_headers = ('ID', 'Name', 'Properties')
95110
else:
96111
columns = ('ID', 'Name')
112+
column_headers = columns
97113
data = self.app.client_manager.volume.volume_types.list()
98-
return (columns,
114+
return (column_headers,
99115
(utils.get_item_properties(
100116
s, columns,
101-
formatters={'Extra Specs': _format_type_list_extra_specs},
117+
formatters={'Extra Specs': utils.format_dict},
102118
) for s in data))
103119

104120

105121
class SetVolumeType(command.Command):
106-
"""Set volume type command"""
122+
"""Set volume type property"""
107123

108124
log = logging.getLogger(__name__ + '.SetVolumeType')
109125

@@ -136,7 +152,7 @@ def take_action(self, parsed_args):
136152

137153

138154
class UnsetVolumeType(command.Command):
139-
"""Unset volume type command"""
155+
"""Unset volume type property"""
140156

141157
log = logging.getLogger(__name__ + '.UnsetVolumeType')
142158

@@ -173,17 +189,3 @@ def take_action(self, parsed_args):
173189
else:
174190
self.app.log.error("No changes requested\n")
175191
return
176-
177-
178-
def _format_type_list_extra_specs(vol_type):
179-
"""Return a string containing the key value pairs
180-
181-
:param server: a single VolumeType resource
182-
:rtype: a string formatted to key=value
183-
"""
184-
185-
keys = vol_type.get_keys()
186-
output = ""
187-
for s in keys:
188-
output = output + s + "=" + keys[s] + "; "
189-
return output

0 commit comments

Comments
 (0)