Source code for heat.engine.resources.stack_user

#
#    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 keystoneauth1.exceptions as kc_exception
from oslo_log import log as logging

from heat.common import exception
from heat.common.i18n import _
from heat.engine import resource

LOG = logging.getLogger(__name__)


[docs]class StackUser(resource.Resource): # Subclasses create a user, and optionally keypair associated with a # resource in a stack. Users are created in the heat stack user domain # (in a project specific to the stack)
[docs] def handle_create(self): self._create_user()
def _create_user(self): if self.data().get('user_id'): # a user has been created already return # Check for stack user project, create if not yet set if not self.stack.stack_user_project_id: project_id = self.keystone().create_stack_domain_project( self.stack.id) self.stack.set_stack_user_project_id(project_id) # Create a keystone user in the stack domain project user_id = self.keystone().create_stack_domain_user( username=self.physical_resource_name(), password=getattr(self, 'password', None), project_id=self.stack.stack_user_project_id) # Store the ID in resource data, for compatibility with SignalResponder self.data_set('user_id', user_id) def _user_token(self): project_id = self.stack.stack_user_project_id if not project_id: raise ValueError(_("Can't get user token, user not yet created")) password = getattr(self, 'password', None) # FIXME(shardy): the create and getattr here could allow insane # passwords, e.g a zero length string, if these happen it almost # certainly means a bug elsewhere in heat, so add assertion to catch if password is None: raise ValueError(_("Can't get user token without password")) return self.keystone().stack_domain_user_token( user_id=self._get_user_id(), project_id=project_id, password=password) def _get_user_id(self): user_id = self.data().get('user_id') if user_id: return user_id
[docs] def handle_delete(self): self._delete_user() return super(StackUser, self).handle_delete()
def _delete_user(self): user_id = self._get_user_id() if user_id is None: return # the user is going away, so we want the keypair gone as well self._delete_keypair() try: self.keystone().delete_stack_domain_user( user_id=user_id, project_id=self.stack.stack_user_project_id) except kc_exception.NotFound: pass except ValueError: # FIXME(shardy): This is a legacy delete path for backwards # compatibility with resources created before the migration # to stack_user.StackUser domain users. After an appropriate # transitional period, this should be removed. LOG.warning('Reverting to legacy user delete path') try: self.keystone().delete_stack_user(user_id) except kc_exception.NotFound: pass self.data_delete('user_id')
[docs] def handle_suspend(self): user_id = self._get_user_id() try: self.keystone().disable_stack_domain_user( user_id=user_id, project_id=self.stack.stack_user_project_id) except ValueError: # FIXME(shardy): This is a legacy path for backwards compatibility self.keystone().disable_stack_user(user_id=user_id)
[docs] def handle_resume(self): user_id = self._get_user_id() try: self.keystone().enable_stack_domain_user( user_id=user_id, project_id=self.stack.stack_user_project_id) except ValueError: # FIXME(shardy): This is a legacy path for backwards compatibility self.keystone().enable_stack_user(user_id=user_id)
def _create_keypair(self): # Subclasses may optionally call this in handle_create to create # an ec2 keypair associated with the user, the resulting keys are # stored in resource_data if self.data().get('credential_id'): return # a keypair was created already user_id = self._get_user_id() kp = self.keystone().create_stack_domain_user_keypair( user_id=user_id, project_id=self.stack.stack_user_project_id) if not kp: raise exception.Error(_("Error creating ec2 keypair for user %s") % user_id) else: try: credential_id = kp.id except AttributeError: # keystone v2 keypairs do not have an id attribute. Use the # access key instead. credential_id = kp.access self.data_set('credential_id', credential_id, redact=True) self.data_set('access_key', kp.access, redact=True) self.data_set('secret_key', kp.secret, redact=True) return kp def _delete_keypair(self): # Subclasses may optionally call this to delete a keypair created # via _create_keypair credential_id = self.data().get('credential_id') if not credential_id: return user_id = self._get_user_id() if user_id is None: return try: self.keystone().delete_stack_domain_user_keypair( user_id=user_id, project_id=self.stack.stack_user_project_id, credential_id=credential_id) except kc_exception.NotFound: pass except ValueError: self.keystone().delete_ec2_keypair( user_id=user_id, credential_id=credential_id) for data_key in ('access_key', 'secret_key', 'credential_id'): self.data_delete(data_key) def _register_access_key(self): """Access is limited to this resource, which created the keypair.""" def access_allowed(resource_name): return resource_name == self.name if self.access_key is not None: self.stack.register_access_allowed_handler( self.access_key, access_allowed) if self._get_user_id() is not None: self.stack.register_access_allowed_handler( self._get_user_id(), access_allowed)