heat/heat/engine/stack_user.py

155 lines
6.1 KiB
Python

#
# 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 keystoneclient.exceptions as kc_exception
from heat.common import exception
from heat.db import api as db_api
from heat.engine import resource
from heat.openstack.common.gettextutils import _
from heat.openstack.common import log as logging
logger = logging.getLogger(__name__)
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)
def __init__(self, name, json_snippet, stack):
super(StackUser, self).__init__(name, json_snippet, stack)
self.password = None
def handle_create(self):
self._create_user()
def _create_user(self):
# 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=self.password,
project_id=self.stack.stack_user_project_id)
# Store the ID in resource data, for compatibility with SignalResponder
db_api.resource_data_set(self, 'user_id', user_id)
def _get_user_id(self):
try:
return db_api.resource_data_get(self, 'user_id')
except exception.NotFound:
# FIXME(shardy): This is a legacy hack for backwards compatibility
# remove after an appropriate transitional period...
# Assume this is a resource that was created with
# a previous version of heat and that the resource_id
# is the user_id
if self.resource_id:
db_api.resource_data_set(self, 'user_id', self.resource_id)
return self.resource_id
def handle_delete(self):
self._delete_user()
def _delete_user(self):
user_id = self._get_user_id()
if user_id is None:
return
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.
logger.warning(_('Reverting to legacy user delete path'))
try:
self.keystone().delete_stack_user(user_id)
except kc_exception.NotFound:
pass
for data_key in ('credential_id', 'access_key', 'secret_key'):
try:
db_api.resource_data_delete(self, data_key)
except exception.NotFound:
pass
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)
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
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:
db_api.resource_data_set(self, 'credential_id', kp.id,
redact=True)
db_api.resource_data_set(self, 'access_key', kp.access,
redact=True)
db_api.resource_data_set(self, '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
user_id = self._get_user_id()
try:
credential_id = db_api.resource_data_get(self, 'credential_id')
except exception.NotFound:
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 ValueError:
self.keystone().delete_ec2_keypair(
user_id=user_id, credential_id=credential_id)
for data_key in ('access_key', 'secret_key', 'credential_id'):
try:
db_api.resource_data_delete(self, data_key)
except exception.NotFound:
pass