From 3d2ccb0b60e80fce468c1e03a073d564f45da459 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah <chmouel@chmouel.com> Date: Fri, 26 Aug 2011 16:00:38 -0700 Subject: [PATCH] Add anotherjesse keystone here. --- README.rst | 8 +++ novaclient/base.py | 8 ++- novaclient/keystone/__init__.py | 2 + novaclient/keystone/client.py | 65 ++++++++++++++++++++ novaclient/keystone/tenants.py | 93 ++++++++++++++++++++++++++++ novaclient/keystone/users.py | 105 ++++++++++++++++++++++++++++++++ 6 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 novaclient/keystone/__init__.py create mode 100644 novaclient/keystone/client.py create mode 100644 novaclient/keystone/tenants.py create mode 100644 novaclient/keystone/users.py diff --git a/README.rst b/README.rst index 1e76e3668..962c93929 100644 --- a/README.rst +++ b/README.rst @@ -157,6 +157,14 @@ Quick-start using keystone:: [...] >>> nt.keypairs.list() [...] + + # if you want to use the keystone api to modify users/tenants: + >>> from novaclient import client + >>> conn = client.HTTPClient(USER, PASS, TENANT, KEYSTONE_URL) + >>> from novaclient import keystone + >>> kc = keystone.Client(conn.client) + >>> kc.tenants.list() + [...] What's new? ----------- diff --git a/novaclient/base.py b/novaclient/base.py index 7928f8d5c..d2e18fe3e 100644 --- a/novaclient/base.py +++ b/novaclient/base.py @@ -67,8 +67,12 @@ class Manager(object): if obj_class is None: obj_class = self.resource_class - return [obj_class(self, res) - for res in body[response_key] if res] + data = body[response_key] + # NOTE(ja): keystone returns values as list as {'values': [ ... ]} + # unlike other services which just return the list... + if type(data) is dict: + data = data['values'] + return [obj_class(self, res) for res in data if res] def _get(self, url, response_key): resp, body = self.api.client.get(url) diff --git a/novaclient/keystone/__init__.py b/novaclient/keystone/__init__.py new file mode 100644 index 000000000..10105dd65 --- /dev/null +++ b/novaclient/keystone/__init__.py @@ -0,0 +1,2 @@ +from novaclient.keystone.client import Client + diff --git a/novaclient/keystone/client.py b/novaclient/keystone/client.py new file mode 100644 index 000000000..ce776e3db --- /dev/null +++ b/novaclient/keystone/client.py @@ -0,0 +1,65 @@ +# Copyright 2011 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 copy +from novaclient.keystone import tenants +from novaclient.keystone import users + + +class Client(object): + """ + Top-level object to access the OpenStack Keystone API. + + Create an instance with your creds:: + + >>> from novaclient import client + >>> conn = client.HTTPClient(USER, PASS, TENANT, KEYSTONE_URL) + >>> from novaclient import keystone + >>> kc = keystone.Client(conn) + + Then call methods on its managers:: + + >>> kc.tenants.list() + ... + >>> kc.users.list() + ... + + """ + + def __init__(self, client): + # FIXME(ja): managers work by making calls against self.client + # which assumes management_url is set for the service. + # with keystone you get a token/endpoints for multiple + # services - so we have to clone and override the endpoint + # NOTE(ja): need endpoint from service catalog... no lazy auth + client.authenticate() + self.client = copy.copy(client) + endpoint = client.service_catalog.url_for('identity', 'admin') + self.client.management_url = endpoint + + self.tenants = tenants.TenantManager(self) + self.users = users.UserManager(self) + + def authenticate(self): + """ + Authenticate against the server. + + Normally this is called automatically when you first access the API, + but you can call this method to force authentication right now. + + Returns on success; raises :exc:`exceptions.Unauthorized` if the + credentials are wrong. + """ + self.client.authenticate() diff --git a/novaclient/keystone/tenants.py b/novaclient/keystone/tenants.py new file mode 100644 index 000000000..5ef1469b0 --- /dev/null +++ b/novaclient/keystone/tenants.py @@ -0,0 +1,93 @@ +# Copyright 2011 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. + +from novaclient import base + + +class RoleRefs(base.Resource): + def __repr__(self): + return "<Roleref %s>" % self._info + + +class Tenant(base.Resource): + def __repr__(self): + return "<Tenant %s>" % self._info + + def delete(self): + self.manager.delete(self) + + def update(self, description=None, enabled=None): + # FIXME(ja): set the attributes in this object if successful + self.manager.update(self.id, description, enabled) + + def add_user(self, user): + self.manager.add_user_to_tenant(self.id, base.getid(user)) + + +class TenantManager(base.ManagerWithFind): + resource_class = Tenant + + def get(self, tenant_id): + return self._get("/tenants/%s" % tenant_id, "tenant") + + # FIXME(ja): finialize roles once finalized in keystone + # right now the only way to add/remove a tenant is to + # give them a role within a project + def get_user_role_refs(self, user_id): + return self._get("/users/%s/roleRefs" % user_id, "roleRefs") + + def add_user_to_tenant(self, tenant_id, user_id): + params = {"roleRef": {"tenantId": tenant_id, "roleId": "Member"}} + return self._create("/users/%s/roleRefs" % user_id, params, "roleRef") + + def remove_user_from_tenant(self, tenant_id, user_id): + params = {"roleRef": {"tenantId": tenant_id, "roleId": "Member"}} + # FIXME(ja): we have to get the roleref? what is 5? + return self._delete("/users/%s/roleRefs/5" % user_id) + + def create(self, tenant_id, description=None, enabled=True): + """ + Create a new tenant. + """ + params = {"tenant": {"id": tenant_id, + "description": description, + "enabled": enabled}} + + return self._create('/tenants', params, "tenant") + + def list(self): + """ + Get a list of tenants. + :rtype: list of :class:`Tenant` + """ + return self._list("/tenants", "tenants") + + def update(self, tenant_id, description=None, enabled=None): + """ + update a tenant with a new name and description + """ + body = {"tenant": {'id': tenant_id }} + if enabled is not None: + body['tenant']['enabled'] = enabled + if description: + body['tenant']['description'] = description + + self._update("/tenants/%s" % tenant_id, body) + + def delete(self, tenant): + """ + Delete a tenant. + """ + self._delete("/tenants/%s" % (base.getid(tenant))) diff --git a/novaclient/keystone/users.py b/novaclient/keystone/users.py new file mode 100644 index 000000000..d7ae617f9 --- /dev/null +++ b/novaclient/keystone/users.py @@ -0,0 +1,105 @@ +# Copyright 2011 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. + +from novaclient import base + + +class User(base.Resource): + def __repr__(self): + return "<User %s>" % self._info + + def delete(self): + self.manager.delete(self) + + +class UserManager(base.ManagerWithFind): + resource_class = User + + def get(self, user): + return self._get("/users/%s" % base.getid(user), "user") + + def update_email(self, user, email): + """ + Update email + """ + # FIXME(ja): why do we have to send id in params and url? + params = {"user": {"id": base.getid(user), + "email": email }} + + self._update("/users/%s" % base.getid(user), params) + + def update_enabled(self, user, enabled): + """ + Update enabled-ness + """ + params = {"user": {"id": base.getid(user), + "enabled": enabled }} + + self._update("/users/%s/enabled" % base.getid(user), params) + + def update_password(self, user, password): + """ + Update password + """ + params = {"user": {"id": base.getid(user), + "password": password }} + + self._update("/users/%s/password" % base.getid(user), params) + + def update_tenant(self, user, tenant): + """ + Update default tenant. + """ + params = {"user": {"id": base.getid(user), + "tenantId": base.getid(tenant) }} + + # FIXME(ja): seems like a bad url - default tenant is an attribute + # not a subresource!??? + self._update("/users/%s/tenant" % base.getid(user), params) + + def create(self, user_id, password, email, tenant_id=None, enabled=True): + """ + Create a user. + """ + # FIXME(ja): email should be optional but keystone currently requires it + params = {"user": {"id": user_id, + "password": password, + "tenantId": tenant_id, + "email": email, + "enabled": enabled}} + return self._create('/users', params, "user") + + def _create(self, url, body, response_key): + # NOTE(ja): since we post the id, we have to use a PUT instead of POST + resp, body = self.api.client.put(url, body=body) + return self.resource_class(self, body[response_key]) + + def delete(self, user): + """ + Delete a user. + """ + self._delete("/users/%s" % base.getid(user)) + + def list(self, tenant_id=None): + """ + Get a list of users (optionally limited to a tenant) + + :rtype: list of :class:`User` + """ + + if not tenant_id: + return self._list("/users", "users") + else: + return self._list("/tenants/%s/users" % tenant_id, "users")