diff --git a/heat/engine/resources.py b/heat/engine/resources.py index 70e6f68c7d..137b41be0b 100644 --- a/heat/engine/resources.py +++ b/heat/engine/resources.py @@ -17,7 +17,9 @@ import base64 from datetime import datetime import logging -from novaclient.v1_1 import client +from novaclient.v1_1 import client as nc +from keystoneclient.v2_0 import client as kc + from novaclient.exceptions import BadRequest from novaclient.exceptions import NotFound @@ -75,10 +77,22 @@ class Resource(object): self.state = None self.id = None self._nova = {} + self._keystone = None def parsed_template(self): return self.stack.resolve_runtime_data(self.t) + def keystone(self): + if self._keystone: + return self._keystone + + con = self.stack.context + self._keystone = kc.Client(username=con.username, + password=con.password, + tenant_name=con.tenant, + auth_url=con.auth_url) + return self._keystone + def nova(self, service_type='compute'): if service_type in self._nova: return self._nova[service_type] @@ -89,14 +103,14 @@ class Resource(object): service_name = None con = self.stack.context - self._nova[service_type] = client.Client(con.username, - con.password, - con.tenant, - con.auth_url, - proxy_token=con.auth_token, - proxy_tenant_id=con.tenant_id, - service_type=service_type, - service_name=service_name) + self._nova[service_type] = nc.Client(con.username, + con.password, + con.tenant, + con.auth_url, + proxy_token=con.auth_token, + proxy_tenant_id=con.tenant_id, + service_type=service_type, + service_name=service_name) return self._nova[service_type] def calculate_properties(self): diff --git a/heat/engine/user.py b/heat/engine/user.py index 58e64a80a5..70f1d75041 100644 --- a/heat/engine/user.py +++ b/heat/engine/user.py @@ -14,32 +14,79 @@ # under the License. import logging -from novaclient.exceptions import BadRequest from heat.common import exception from heat.engine.resources import Resource + logger = logging.getLogger('heat.engine.user') +# +# We are ignoring Policies and Groups as keystone does not support them. +# +# For now support users and accesskeys. +# + + +class DummyId: + def __init__(self, id): + self.id = id + class User(Resource): properties_schema = {'Path': {'Type': 'String', 'Implemented': False}, 'Groups': {'Type': 'CommaDelimitedList', 'Implemented': False}, - 'LoginProfile': {'Type': 'String', - 'Implemented': False}, - 'Policies': {'Type': 'List'}} + 'LoginProfile': {'Type': 'List'}, + 'Policies': {'Type': 'List', + 'Implemented': False}} def __init__(self, name, json_snippet, stack): super(User, self).__init__(name, json_snippet, stack) def create(self): + if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: + return + self.state_set(self.CREATE_IN_PROGRESS) + super(User, self).create() + + passwd = '' + if 'LoginProfile' in self.properties: + if self.properties['LoginProfile'] and \ + 'Password' in self.properties['LoginProfile']: + passwd = self.properties['LoginProfile']['Password'] + + tenant_id = self.stack.context.tenant_id + user = self.keystone().users.create(self.name, passwd, + '%s@heat-api.org' % self.name, + tenant_id=tenant_id, + enabled=True) + self.instance_id_set(user.id) self.state_set(self.CREATE_COMPLETE) + def delete(self): + if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: + return + self.state_set(self.DELETE_IN_PROGRESS) + super(User, self).delete() + + try: + user = self.keystone().users.get(DummyId(self.instance_id)) + except Exception as ex: + logger.info('user %s/%s does not exist' % (self.name, + self.instance_id)) + else: + user.delete() + + self.state_set(self.DELETE_COMPLETE) + + def FnGetRefId(self): + return unicode(self.name) + def FnGetAtt(self, key): res = None if key == 'Policies': - res = self.t['Properties']['Policies'] + res = self.properties['Policies'] else: raise exception.InvalidTemplateAttribute(resource=self.name, key=key) @@ -50,28 +97,82 @@ class User(Resource): class AccessKey(Resource): properties_schema = {'Serial': {'Type': 'Integer', - 'Implemented': False}, + 'Implemented': False}, 'UserName': {'Type': 'String', - 'Required': True}, + 'Required': True}, 'Status': {'Type': 'String', - 'Implemented': False, - 'AllowedValues': ['Active', 'Inactive']}} + 'Implemented': False, + 'AllowedValues': ['Active', 'Inactive']}} def __init__(self, name, json_snippet, stack): super(AccessKey, self).__init__(name, json_snippet, stack) + self._secret = None + + def _user_from_name(self, username): + tenant_id = self.stack.context.tenant_id + users = self.keystone().users.list(tenant_id=tenant_id) + for u in users: + if u.name == self.properties['UserName']: + return u + return None def create(self): + if self.state in [self.CREATE_IN_PROGRESS, self.CREATE_COMPLETE]: + return + self.state_set(self.CREATE_IN_PROGRESS) + super(AccessKey, self).create() + + user = self._user_from_name(self.properties['UserName']) + if user is None: + raise exception.NotFound('could not find user %s' % + self.properties['UserName']) + + tenant_id = self.stack.context.tenant_id + cred = self.keystone().ec2.create(user.id, tenant_id) + self.instance_id_set(cred.access) + self._secret = cred.secret + self.state_set(self.CREATE_COMPLETE) - def FnGetRefId(self): - return unicode(self.name) + def delete(self): + if self.state in [self.DELETE_IN_PROGRESS, self.DELETE_COMPLETE]: + return + self.state_set(self.DELETE_IN_PROGRESS) + super(AccessKey, self).delete() + + user = self._user_from_name(self.properties['UserName']) + if user and self.instance_id: + self.keystone().ec2.delete(user.id, self.instance_id) + + self.state_set(self.DELETE_COMPLETE) + + def _secret_accesskey(self): + ''' + Return the user's access key, fetching it from keystone if necessary + ''' + if self._secret is None: + user = self._user_from_name(self.properties['UserName']) + if user is None: + logger.warn('could not find user %s' % + self.properties['UserName']) + else: + try: + cred = self.keystone().ec2.get(user.id, self.instance_id) + self._secret = cred.secret + self.instance_id_set(cred.access) + except Exception as ex: + logger.warn('could not get secret for %s Error:%s' % + (self.properties['UserName'], + str(ex))) + + return self._secret or '000-000-000' def FnGetAtt(self, key): res = None if key == 'UserName': - res = self.t['Properties']['UserName'] + res = self.properties['UserName'] if key == 'SecretAccessKey': - res = 'TODO-Add-Real-SecreateAccessKey' + res = self._secret_accesskey() else: raise exception.InvalidTemplateAttribute(resource=self.name, key=key) diff --git a/tools/pip-requires b/tools/pip-requires index a364bcf0b9..d32fb78481 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -16,6 +16,7 @@ httplib2 kombu iso8601>=0.1.4 python-novaclient +https://github.com/openstack/python-keystoneclient/zipball/master#egg=python-keystoneclient glance # Note you will need gcc buildtools installed and must