2
# Licensed under the Apache License, Version 2.0 (the "License"); you may
3
# not use this file except in compliance with the License. You may obtain
4
# a copy of the License at
6
# http://www.apache.org/licenses/LICENSE-2.0
8
# Unless required by applicable law or agreed to in writing, software
9
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
# License for the specific language governing permissions and limitations
14
import keystoneclient.exceptions as kc_exception
15
from oslo_log import log as logging
17
from heat.common import exception
18
from heat.common.i18n import _
19
from heat.common.i18n import _LW
20
from heat.engine import resource
22
LOG = logging.getLogger(__name__)
25
class StackUser(resource.Resource):
27
# Subclasses create a user, and optionally keypair associated with a
28
# resource in a stack. Users are created in the heat stack user domain
29
# (in a project specific to the stack)
30
def __init__(self, name, json_snippet, stack):
31
super(StackUser, self).__init__(name, json_snippet, stack)
33
def handle_create(self):
36
def _create_user(self):
37
# Check for stack user project, create if not yet set
38
if not self.stack.stack_user_project_id:
39
project_id = self.keystone().create_stack_domain_project(
41
self.stack.set_stack_user_project_id(project_id)
43
# Create a keystone user in the stack domain project
44
user_id = self.keystone().create_stack_domain_user(
45
username=self.physical_resource_name(),
46
password=getattr(self, 'password', None),
47
project_id=self.stack.stack_user_project_id)
49
# Store the ID in resource data, for compatibility with SignalResponder
50
self.data_set('user_id', user_id)
52
def _user_token(self):
53
project_id = self.stack.stack_user_project_id
55
raise ValueError(_("Can't get user token, user not yet created"))
56
password = getattr(self, 'password', None)
57
# FIXME(shardy): the create and getattr here could allow insane
58
# passwords, e.g a zero length string, if these happen it almost
59
# certainly means a bug elsewhere in heat, so add assertion to catch
61
raise ValueError(_("Can't get user token without password"))
63
return self.keystone().stack_domain_user_token(
64
user_id=self._get_user_id(),
65
project_id=project_id, password=password)
67
def _get_user_id(self):
68
user_id = self.data().get('user_id')
72
# FIXME(shardy): This is a legacy hack for backwards compatibility
73
# remove after an appropriate transitional period...
74
# Assume this is a resource that was created with
75
# a previous version of heat and that the resource_id
78
self.data_set('user_id', self.resource_id)
79
return self.resource_id
81
def handle_delete(self):
84
def _delete_user(self):
85
user_id = self._get_user_id()
89
self.keystone().delete_stack_domain_user(
90
user_id=user_id, project_id=self.stack.stack_user_project_id)
91
except kc_exception.NotFound:
94
# FIXME(shardy): This is a legacy delete path for backwards
95
# compatibility with resources created before the migration
96
# to stack_user.StackUser domain users. After an appropriate
97
# transitional period, this should be removed.
98
LOG.warn(_LW('Reverting to legacy user delete path'))
100
self.keystone().delete_stack_user(user_id)
101
except kc_exception.NotFound:
103
for data_key in ('credential_id', 'access_key', 'secret_key'):
104
self.data_delete(data_key)
106
def handle_suspend(self):
107
user_id = self._get_user_id()
109
self.keystone().disable_stack_domain_user(
110
user_id=user_id, project_id=self.stack.stack_user_project_id)
112
# FIXME(shardy): This is a legacy path for backwards compatibility
113
self.keystone().disable_stack_user(user_id=user_id)
115
def handle_resume(self):
116
user_id = self._get_user_id()
118
self.keystone().enable_stack_domain_user(
119
user_id=user_id, project_id=self.stack.stack_user_project_id)
121
# FIXME(shardy): This is a legacy path for backwards compatibility
122
self.keystone().enable_stack_user(user_id=user_id)
124
def _create_keypair(self):
125
# Subclasses may optionally call this in handle_create to create
126
# an ec2 keypair associated with the user, the resulting keys are
127
# stored in resource_data
128
user_id = self._get_user_id()
129
kp = self.keystone().create_stack_domain_user_keypair(
130
user_id=user_id, project_id=self.stack.stack_user_project_id)
132
raise exception.Error(_("Error creating ec2 keypair for user %s") %
136
credential_id = kp.id
137
except AttributeError:
138
# keystone v2 keypairs do not have an id attribute. Use the
139
# access key instead.
140
credential_id = kp.access
141
self.data_set('credential_id', credential_id, redact=True)
142
self.data_set('access_key', kp.access, redact=True)
143
self.data_set('secret_key', kp.secret, redact=True)
146
def _delete_keypair(self):
147
# Subclasses may optionally call this to delete a keypair created
148
# via _create_keypair
149
user_id = self._get_user_id()
150
credential_id = self.data().get('credential_id')
151
if not credential_id:
155
self.keystone().delete_stack_domain_user_keypair(
156
user_id=user_id, project_id=self.stack.stack_user_project_id,
157
credential_id=credential_id)
159
self.keystone().delete_ec2_keypair(
160
user_id=user_id, credential_id=credential_id)
162
for data_key in ('access_key', 'secret_key', 'credential_id'):
163
self.data_delete(data_key)