Skip to content

Commit ff9bd34

Browse files
committed
[Compute]Make column content readable for both human and machine
Currently, we use utils.format_dict(), utils.format_list(), utils.format_list_of_dicts to make column value can be easy to read by human, but osc support to format the CLI output into several format, like: json, shell, csv, yaml, most of these should be understand by program and code, so keeping the column content as the original value make sense, like {u'name': u'RuiChen'} than name='RuiChen' The patch include all compute commands. Change-Id: I313a52f94895625e6045df870320840fee157759 Implements: blueprint osc-formattable-columns Partial-Bug: #1538015 Partial-Bug: #1538006
1 parent 5309bf5 commit ff9bd34

File tree

15 files changed

+335
-175
lines changed

15 files changed

+335
-175
lines changed

openstackclient/compute/v2/aggregate.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import logging
2020

21+
from osc_lib.cli import format_columns
2122
from osc_lib.cli import parseractions
2223
from osc_lib.command import command
2324
from osc_lib import exceptions
@@ -324,7 +325,9 @@ def take_action(self, parsed_args):
324325
# 'metadata' --> 'properties'
325326
data._info.update(
326327
{
327-
'properties': utils.format_dict(data._info.pop('metadata')),
328+
'properties': format_columns.DictColumn(
329+
data._info.pop('metadata')
330+
),
328331
},
329332
)
330333

openstackclient/compute/v2/flavor.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import logging
1919

20+
from osc_lib.cli import format_columns
2021
from osc_lib.cli import parseractions
2122
from osc_lib.command import command
2223
from osc_lib import exceptions
@@ -180,7 +181,14 @@ def take_action(self, parsed_args):
180181

181182
flavor_info = flavor._info.copy()
182183
flavor_info.pop("links")
183-
flavor_info['properties'] = utils.format_dict(flavor.get_keys())
184+
flavor_info['properties'] = format_columns.DictColumn(
185+
# NOTE(RuiChen): novaclient flavor.get_keys() return a mixin class
186+
# DictWithMeta, that can't be represented properly
187+
# in yaml format(-f yaml), wrapping it in base type
188+
# dict is a workaround, please do not remove the
189+
# conversion.
190+
dict(flavor.get_keys())
191+
)
184192

185193
return zip(*sorted(six.iteritems(flavor_info)))
186194

@@ -291,13 +299,19 @@ def take_action(self, parsed_args):
291299
"Properties",
292300
)
293301
for f in data:
294-
f.properties = f.get_keys()
302+
# NOTE(RuiChen): novaclient flavor.get_keys() return a mixin
303+
# class DictWithMeta, that can't be represented
304+
# properly in yaml format(-f yaml), wrapping it
305+
# in base type dict is a workaround, please do
306+
# not remove the conversion.
307+
f.properties = dict(f.get_keys())
295308

296309
column_headers = columns
297310

298311
return (column_headers,
299312
(utils.get_item_properties(
300-
s, columns, formatters={'Properties': utils.format_dict},
313+
s, columns,
314+
formatters={'Properties': format_columns.DictColumn},
301315
) for s in data))
302316

303317

@@ -405,9 +419,7 @@ def take_action(self, parsed_args):
405419
flavor=resource_flavor.id)
406420
projects = [utils.get_field(access, 'tenant_id')
407421
for access in flavor_access]
408-
# TODO(Huanxuan Ao): This format case can be removed after
409-
# patch https://review.openstack.org/#/c/330223/ merged.
410-
access_projects = utils.format_list(projects)
422+
access_projects = format_columns.ListColumn(projects)
411423
except Exception as e:
412424
msg = _("Failed to get access projects list "
413425
"for flavor '%(flavor)s': %(e)s")
@@ -419,7 +431,14 @@ def take_action(self, parsed_args):
419431
})
420432
flavor.pop("links", None)
421433

422-
flavor['properties'] = utils.format_dict(resource_flavor.get_keys())
434+
flavor['properties'] = format_columns.DictColumn(
435+
# NOTE(RuiChen): novaclient flavor.get_keys() return a mixin class
436+
# DictWithMeta, that can't be represented properly
437+
# in yaml format(-f yaml), wrapping it in base type
438+
# dict is a workaround, please do not remove the
439+
# conversion.
440+
dict(resource_flavor.get_keys())
441+
)
423442

424443
return zip(*sorted(six.iteritems(flavor)))
425444

openstackclient/compute/v2/server.py

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import sys
2424

2525
from novaclient.v2 import servers
26+
from osc_lib.cli import format_columns
2627
from osc_lib.cli import parseractions
2728
from osc_lib.command import command
2829
from osc_lib import exceptions
@@ -37,22 +38,6 @@
3738
LOG = logging.getLogger(__name__)
3839

3940

40-
def _format_servers_list_networks(networks):
41-
"""Return a formatted string of a server's networks
42-
43-
:param networks: a Server.networks field
44-
:rtype: a string of formatted network addresses
45-
"""
46-
output = []
47-
for (network, addresses) in networks.items():
48-
if not addresses:
49-
continue
50-
addresses_csv = ', '.join(addresses)
51-
group = "%s=%s" % (network, addresses_csv)
52-
output.append(group)
53-
return '; '.join(output)
54-
55-
5641
def _format_servers_list_power_state(state):
5742
"""Return a formatted string of a server's power state
5843
@@ -154,24 +139,26 @@ def _prep_server_detail(compute_client, image_client, server):
154139
if 'os-extended-volumes:volumes_attached' in info:
155140
info.update(
156141
{
157-
'volumes_attached': utils.format_list_of_dicts(
158-
info.pop('os-extended-volumes:volumes_attached'))
142+
'volumes_attached': format_columns.ListDictColumn(
143+
info.pop('os-extended-volumes:volumes_attached')
144+
)
159145
}
160146
)
161147
if 'security_groups' in info:
162148
info.update(
163149
{
164-
'security_groups': utils.format_list_of_dicts(
165-
info.pop('security_groups'))
150+
'security_groups': format_columns.ListDictColumn(
151+
info.pop('security_groups')
152+
)
166153
}
167154
)
168155
# NOTE(dtroyer): novaclient splits these into separate entries...
169156
# Format addresses in a useful way
170-
info['addresses'] = _format_servers_list_networks(server.networks)
157+
info['addresses'] = format_columns.DictListColumn(server.networks)
171158

172159
# Map 'metadata' field to 'properties'
173160
info.update(
174-
{'properties': utils.format_dict(info.pop('metadata'))}
161+
{'properties': format_columns.DictColumn(info.pop('metadata'))}
175162
)
176163

177164
# Migrate tenant_id to project_id naming
@@ -1161,8 +1148,8 @@ def take_action(self, parsed_args):
11611148
formatters={
11621149
'OS-EXT-STS:power_state':
11631150
_format_servers_list_power_state,
1164-
'Networks': _format_servers_list_networks,
1165-
'Metadata': utils.format_dict,
1151+
'Networks': format_columns.DictListColumn,
1152+
'Metadata': format_columns.DictColumn,
11661153
},
11671154
) for s in data))
11681155
return table

openstackclient/compute/v2/server_backup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import sys
1919

20+
from osc_lib.cli import format_columns
2021
from osc_lib.command import command
2122
from osc_lib import exceptions
2223
from osc_lib import utils
@@ -122,7 +123,9 @@ def take_action(self, parsed_args):
122123
if self.app.client_manager._api_version['image'] == '1':
123124
info = {}
124125
info.update(image._info)
125-
info['properties'] = utils.format_dict(info.get('properties', {}))
126+
info['properties'] = format_columns.DictColumn(
127+
info.get('properties', {})
128+
)
126129
else:
127130
# Get the right image module to format the output
128131
image_module = importutils.import_module(

openstackclient/compute/v2/server_group.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import logging
1919

20+
from osc_lib.cli import format_columns
2021
from osc_lib.command import command
2122
from osc_lib import exceptions
2223
from osc_lib import utils
@@ -28,8 +29,8 @@
2829

2930

3031
_formatters = {
31-
'policies': utils.format_list,
32-
'members': utils.format_list,
32+
'policies': format_columns.ListColumn,
33+
'members': format_columns.ListColumn,
3334
}
3435

3536

@@ -155,8 +156,8 @@ def take_action(self, parsed_args):
155156
(utils.get_item_properties(
156157
s, columns,
157158
formatters={
158-
'Policies': utils.format_list,
159-
'Members': utils.format_list,
159+
'Policies': format_columns.ListColumn,
160+
'Members': format_columns.ListColumn,
160161
}
161162
) for s in data))
162163

openstackclient/compute/v2/server_image.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import logging
1919
import sys
2020

21+
from osc_lib.cli import format_columns
2122
from osc_lib.command import command
2223
from osc_lib import exceptions
2324
from osc_lib import utils
@@ -101,7 +102,9 @@ def take_action(self, parsed_args):
101102
if self.app.client_manager._api_version['image'] == '1':
102103
info = {}
103104
info.update(image._info)
104-
info['properties'] = utils.format_dict(info.get('properties', {}))
105+
info['properties'] = format_columns.DictColumn(
106+
info.get('properties', {})
107+
)
105108
else:
106109
# Get the right image module to format the output
107110
image_module = importutils.import_module(

openstackclient/tests/functional/compute/v2/test_aggregate.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,7 @@ def test_aggregate_set_and_unset(self):
136136
'internal',
137137
cmd_output['availability_zone']
138138
)
139-
self.assertIn(
140-
"c='d'",
141-
cmd_output['properties']
142-
)
143-
self.assertNotIn(
144-
"a='b'",
145-
cmd_output['properties']
146-
)
139+
self.assertEqual({'c': 'd'}, cmd_output['properties'])
147140

148141
# Test unset
149142
raw_output = self.openstack(
@@ -157,10 +150,7 @@ def test_aggregate_set_and_unset(self):
157150
'aggregate show -f json ' +
158151
name2
159152
))
160-
self.assertNotIn(
161-
"c='d'",
162-
cmd_output['properties']
163-
)
153+
self.assertEqual({}, cmd_output['properties'])
164154

165155
def test_aggregate_add_and_remove_host(self):
166156
"""Test aggregate add and remove host"""

openstackclient/tests/functional/compute/v2/test_flavor.py

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def test_flavor_list(self):
9191
"--ram 123 " +
9292
"--private " +
9393
"--property a=b2 " +
94-
"--property b=d2 " +
94+
"--property c=d2 " +
9595
name2
9696
))
9797
self.addCleanup(self.openstack, "flavor delete " + name2)
@@ -116,10 +116,7 @@ def test_flavor_list(self):
116116
False,
117117
cmd_output["os-flavor-access:is_public"],
118118
)
119-
self.assertEqual(
120-
"a='b2', b='d2'",
121-
cmd_output["properties"],
122-
)
119+
self.assertEqual({'a': 'b2', 'c': 'd2'}, cmd_output["properties"])
123120

124121
# Test list
125122
cmd_output = json.loads(self.openstack(
@@ -135,11 +132,11 @@ def test_flavor_list(self):
135132
"--long"
136133
))
137134
col_name = [x["Name"] for x in cmd_output]
138-
col_properties = [x['Properties'] for x in cmd_output]
139135
self.assertIn(name1, col_name)
140-
self.assertIn("a='b', c='d'", col_properties)
141136
self.assertNotIn(name2, col_name)
142-
self.assertNotIn("b2', b='d2'", col_properties)
137+
138+
props = [x['Properties'] for x in cmd_output]
139+
self.assertIn({'a': 'b', 'c': 'd'}, props)
143140

144141
# Test list --public
145142
cmd_output = json.loads(self.openstack(
@@ -203,10 +200,8 @@ def test_flavor_properties(self):
203200
False,
204201
cmd_output["os-flavor-access:is_public"],
205202
)
206-
self.assertEqual(
207-
"a='first', b='second'",
208-
cmd_output["properties"],
209-
)
203+
self.assertEqual({'a': 'first', 'b': 'second'},
204+
cmd_output['properties'])
210205

211206
raw_output = self.openstack(
212207
"flavor set " +
@@ -224,10 +219,8 @@ def test_flavor_properties(self):
224219
"qaz",
225220
cmd_output["id"],
226221
)
227-
self.assertEqual(
228-
"a='third and 10', b='second', g='fourth'",
229-
cmd_output['properties'],
230-
)
222+
self.assertEqual({'a': 'third and 10', 'b': 'second', 'g': 'fourth'},
223+
cmd_output['properties'])
231224

232225
raw_output = self.openstack(
233226
"flavor unset " +
@@ -240,7 +233,5 @@ def test_flavor_properties(self):
240233
"flavor show -f json " +
241234
name1
242235
))
243-
self.assertEqual(
244-
"a='third and 10', g='fourth'",
245-
cmd_output["properties"],
246-
)
236+
self.assertEqual({'a': 'third and 10', 'g': 'fourth'},
237+
cmd_output['properties'])

0 commit comments

Comments
 (0)