diff --git a/glanceclient/common/utils.py b/glanceclient/common/utils.py index 8bbb7ac..957f713 100644 --- a/glanceclient/common/utils.py +++ b/glanceclient/common/utils.py @@ -14,6 +14,7 @@ # under the License. import os +import sys import uuid import prettytable @@ -47,7 +48,7 @@ def print_list(objs, fields, formatters={}): row.append(formatters[field](o)) else: field_name = field.lower().replace(' ', '_') - data = getattr(o, field_name, '') + data = getattr(o, field_name, None) or '' row.append(data) pt.add_row(row) @@ -56,7 +57,7 @@ def print_list(objs, fields, formatters={}): def print_dict(d): pt = prettytable.PrettyTable(['Property', 'Value'], caching=False) - pt.aligns = ['l', 'l'] + pt.align = 'l' [pt.add_row(list(r)) for r in d.iteritems()] print pt.get_string(sortby='Property') @@ -123,3 +124,9 @@ def import_versioned_module(version, submodule=None): if submodule: module = '.'.join((module, submodule)) return importutils.import_module(module) + + +def exit(msg=''): + if msg: + print >> sys.stderr, msg + sys.exit(1) diff --git a/glanceclient/v2/client.py b/glanceclient/v2/client.py index 0435f64..cc0c383 100644 --- a/glanceclient/v2/client.py +++ b/glanceclient/v2/client.py @@ -15,6 +15,8 @@ import logging +import warlock + from glanceclient.common import http from glanceclient.v2 import images from glanceclient.v2 import schemas @@ -36,5 +38,10 @@ class Client(object): def __init__(self, endpoint, token=None, timeout=600, **kwargs): self.http_client = http.HTTPClient( endpoint, token=token, timeout=timeout) - self.images = images.Controller(self.http_client) self.schemas = schemas.Controller(self.http_client) + self.images = images.Controller(self.http_client, + self._get_image_model()) + + def _get_image_model(self): + schema = self.schemas.get('image') + return warlock.model_factory(schema.raw()) diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index 6199086..0da8ceb 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -14,16 +14,27 @@ # under the License. -class Image(object): - def __init__(self, id, name): - self.id = id - self.name = name - - class Controller(object): - def __init__(self, http_client): + def __init__(self, http_client, model): self.http_client = http_client + self.model = model def list(self): + """Retrieve a listing of Image objects + + :returns generator over list of Images + """ resp, body = self.http_client.json_request('GET', '/v2/images') - return [Image(i['id'], i['name']) for i in body['images']] + for image in body['images']: + #NOTE(bcwaldon): remove 'self' for now until we have an elegant + # way to pass it into the model constructor without conflict + image.pop('self', None) + yield self.model(**image) + + def get(self, image_id): + url = '/v2/images/%s' % image_id + resp, body = self.http_client.json_request('GET', url) + #NOTE(bcwaldon): remove 'self' for now until we have an elegant + # way to pass it into the model constructor without conflict + body['image'].pop('self', None) + return self.model(**body['image']) diff --git a/glanceclient/v2/schemas.py b/glanceclient/v2/schemas.py index 8757da1..6e178d2 100644 --- a/glanceclient/v2/schemas.py +++ b/glanceclient/v2/schemas.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from glanceclient import exc @@ -40,6 +42,9 @@ class Schema(object): raw_properties = raw_schema['properties'] self.properties = translate_schema_properties(raw_properties) + def raw(self): + return copy.deepcopy(self._raw_schema) + class Controller(object): def __init__(self, http_client): @@ -52,7 +57,7 @@ class Controller(object): def _find_schema_uri(self, schema_name): _, schema_index = self.http_client.json_request('GET', '/v2/schemas') - for link in schema_index['links']: - if link['rel'] == schema_name: - return link['href'] - raise exc.SchemaNotFound(schema_name) + try: + return schema_index[schema_name] + except KeyError: + raise exc.SchemaNotFound(schema_name) diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 90c2ea8..7a100a7 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -14,6 +14,7 @@ # under the License. from glanceclient.common import utils +from glanceclient import exc def do_image_list(gc, args): @@ -23,9 +24,21 @@ def do_image_list(gc, args): utils.print_list(images, columns) -@utils.arg('name', metavar='', help='Name of model to describe.') +@utils.arg('id', metavar='', help='ID of image to describe.') +def do_image_show(gc, args): + """Describe a specific image.""" + image = gc.images.get(args.id) + utils.print_dict(image) + + +@utils.arg('model', metavar='', help='Name of model to describe.') def do_explain(gc, args): """Describe a specific model.""" - schema = gc.schemas.get(args.name) - columns = ['Name', 'Description'] - utils.print_list(schema.properties, columns) + try: + schema = gc.schemas.get(args.model) + except exc.SchemaNotFound: + utils.exit('Unable to find requested model \'%s\'' % args.model) + else: + formatters = {'Attribute': lambda m: m.name} + columns = ['Attribute', 'Description'] + utils.print_list(schema.properties, columns, formatters) diff --git a/tests/v2/test_images.py b/tests/v2/test_images.py new file mode 100644 index 0000000..298d5c1 --- /dev/null +++ b/tests/v2/test_images.py @@ -0,0 +1,75 @@ +# Copyright 2012 OpenStack LLC. +# 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. + +import unittest + +import warlock + +from glanceclient.v2 import images +from tests import utils + + +fixtures = { + '/v2/images': { + 'GET': ( + {}, + {'images': [ + { + 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', + 'name': 'image-1', + }, + { + 'id': '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810', + 'name': 'image-2', + }, + ]}, + ), + }, + '/v2/images/3a4560a1-e585-443e-9b39-553b46ec92d1': { + 'GET': ( + {}, + { + 'image': { + 'id': '3a4560a1-e585-443e-9b39-553b46ec92d1', + 'name': 'image-1', + }, + }, + ), + }, +} + + +fake_schema = {'name': 'image', 'properties': {'id': {}, 'name': {}}} +FakeModel = warlock.model_factory(fake_schema) + + +class TestController(unittest.TestCase): + def setUp(self): + super(TestController, self).setUp() + self.api = utils.FakeAPI(fixtures) + self.controller = images.Controller(self.api, FakeModel) + + def test_list_images(self): + #NOTE(bcwaldon): cast to list since the controller returns a generator + images = list(self.controller.list()) + self.assertEqual(images[0].id, '3a4560a1-e585-443e-9b39-553b46ec92d1') + self.assertEqual(images[0].name, 'image-1') + self.assertEqual(images[1].id, '6f99bf80-2ee6-47cf-acfe-1f1fabb7e810') + self.assertEqual(images[1].name, 'image-2') + + def test_get_image(self): + image = self.controller.get('3a4560a1-e585-443e-9b39-553b46ec92d1') + self.assertEqual(image.id, '3a4560a1-e585-443e-9b39-553b46ec92d1') + self.assertEqual(image.name, 'image-1') diff --git a/tests/v2/test_schemas.py b/tests/v2/test_schemas.py index b67b976..b11b480 100644 --- a/tests/v2/test_schemas.py +++ b/tests/v2/test_schemas.py @@ -23,10 +23,10 @@ fixtures = { '/v2/schemas': { 'GET': ( {}, - {'links': [ - {'rel': 'image', 'href': '/v2/schemas/image'}, - {'rel': 'access', 'href': '/v2/schemas/image/access'}, - ]}, + { + 'image': '/v2/schemas/image', + 'access': '/v2/schemas/image/access', + }, ), }, '/v2/schemas/image': { @@ -67,6 +67,11 @@ class TestSchema(unittest.TestCase): self.assertEqual(schema.name, 'Country') self.assertEqual([p.name for p in schema.properties], ['size']) + def test_raw(self): + raw_schema = {'name': 'Country', 'properties': {}} + schema = schemas.Schema(raw_schema) + self.assertEqual(schema.raw(), raw_schema) + class TestController(unittest.TestCase): def setUp(self): diff --git a/tools/pip-requires b/tools/pip-requires index f517a02..eec261e 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -2,3 +2,4 @@ argparse httplib2 prettytable==0.6 python-keystoneclient>=0.1,<0.2 +warlock==0.1.0