Fixes #123.
Change-Id: I89affe471b4df898c7d3157ff23f9b64003c2893
Signed-off-by: Zane Bitter <zbitter@redhat.com>
return results
-def stack_get_by_name(context, stack_name):
+def stack_get_by_name(context, stack_name, owner_id=None):
result = model_query(context, models.Stack).\
+ filter_by(owner_id=owner_id).\
filter_by(name=stack_name).first()
if (result is not None and context is not None and
result.username != context.username):
def stack_get_all(context):
- results = model_query(context, models.Stack).all()
+ results = model_query(context, models.Stack).\
+ filter_by(owner_id=None).all()
return results
def stack_get_by_user(context):
results = model_query(context, models.Stack).\
- filter_by(username=context.username).all()
+ filter_by(owner_id=None).\
+ filter_by(username=context.username).all()
return results
--- /dev/null
+from sqlalchemy import *
+from migrate import *
+
+
+def upgrade(migrate_engine):
+ meta = MetaData(bind=migrate_engine)
+
+ # This was unused
+ resource = Table('resource', meta, autoload=True)
+ resource.c.depends_on.drop()
+
+ stack = Table('stack', meta, autoload=True)
+ Column('owner_id', Integer, nullable=True).create(stack)
+
+
+def downgrade(migrate_engine):
+ meta = MetaData(bind=migrate_engine)
+
+ resource = Table('resource', meta, autoload=True)
+ Column('depends_on', Integer).create(resource)
+
+ stack = Table('stack', meta, autoload=True)
+ stack.c.owner_id.drop()
class Stack(BASE, HeatBase):
- """Represents an generated by the heat engine."""
+ """Represents a stack created by the heat engine."""
__tablename__ = 'stack'
id = Column(Integer, primary_key=True)
- name = Column(String, unique=True)
+ name = Column(String)
raw_template_id = Column(Integer, ForeignKey('raw_template.id'),
nullable=False)
raw_template = relationship(RawTemplate,
username = Column(String)
user_creds_id = Column(Integer, ForeignKey('user_creds.id'),
nullable=False)
+ owner_id = Column(Integer, nullable=True)
class UserCreds(BASE, HeatBase):
nullable=False)
stack = relationship(Stack, backref=backref('resources'))
- depends_on = Column(Integer)
-
class WatchRule(BASE, HeatBase):
"""Represents a watch_rule created by the heat engine."""
raise ValueError('%s: %s is out of range' % (self.name,
key))
+ elif t == 'Map':
+ if not isinstance(value, dict):
+ raise ValueError('%s: %s Value must be a map' %
+ (self.name, key))
+
elif t == 'List':
if not isinstance(value, (list, tuple)):
raise ValueError('%s: %s Value must be a list' %
self.context = context
self.t = template
self.maps = self.t.get('Mappings', {})
- self.outputs = self.t.get('Outputs', {})
+ self.outputs = self.resolve_static_data(self.t.get('Outputs', {}))
self.res = {}
self.doc = None
self.name = stack_name
self.state_set(self.DELETE_COMPLETE, 'Deleted successfully')
db_api.stack_delete(self.context, self.id)
- def get_outputs(self):
- outputs = self.resolve_runtime_data(self.outputs)
+ def output(self, key):
+ value = self.outputs[key].get('Value', '')
+ return self.resolve_runtime_data(value)
+ def get_outputs(self):
def output_dict(k):
- return {'Description': outputs[k].get('Description',
- 'No description given'),
+ return {'Description': self.outputs[k].get('Description',
+ 'No description given'),
'OutputKey': k,
- 'OutputValue': outputs[k].get('Value', '')}
+ 'OutputValue': self.output(k)}
- return [output_dict(key) for key in outputs]
+ return [output_dict(key) for key in self.outputs]
def restart_resource(self, resource_name):
'''
from heat.engine import eip
from heat.engine import instance
from heat.engine import security_group
+from heat.engine import stack
from heat.engine import user
from heat.engine import volume
from heat.engine import wait_condition
_resource_classes = {
+ 'AWS::CloudFormation::Stack': stack.Stack,
'AWS::CloudFormation::WaitCondition': wait_condition.WaitCondition,
'AWS::CloudFormation::WaitConditionHandle':
wait_condition.WaitConditionHandle,
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+#
+# 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 urllib2
+import json
+import logging
+
+from heat.common import exception
+from heat.engine.resources import Resource
+from heat.db import api as db_api
+from heat.engine import parser
+
+logger = logging.getLogger(__file__)
+
+
+(PROP_TEMPLATE_URL,
+ PROP_TIMEOUT_MINS,
+ PROP_PARAMETERS) = ('TemplateURL', 'TimeoutInMinutes', 'Parameters')
+
+
+class Stack(Resource):
+ properties_schema = {PROP_TEMPLATE_URL: {'Type': 'String',
+ 'Required': True},
+ PROP_TIMEOUT_MINS: {'Type': 'Number'},
+ PROP_PARAMETERS: {'Type': 'Map'}}
+
+ def __init__(self, name, json_snippet, stack):
+ Resource.__init__(self, name, json_snippet, stack)
+ self._nested = None
+
+ def _params(self):
+ p = self.stack.resolve_runtime_data(self.properties[PROP_PARAMETERS])
+ return p
+
+ def nested(self):
+ if self._nested is None:
+ if self.instance_id is None:
+ return None
+
+ st = db_api.stack_get(self.stack.context, self.instance_id)
+ if not st:
+ raise exception.NotFound('Nested stack not found in DB')
+
+ n = parser.Stack(self.stack.context, st.name,
+ st.raw_template.parsed_template.template,
+ self.instance_id, self._params())
+ self._nested = n
+
+ return self._nested
+
+ def handle_create(self):
+ response = urllib2.urlopen(self.properties[PROP_TEMPLATE_URL])
+ child_template = json.loads(response.read())
+
+ self._nested = parser.Stack(self.stack.context,
+ self.name,
+ child_template,
+ parms=self._params(),
+ metadata_server=self.stack.metadata_server)
+
+ rt = {'template': child_template, 'stack_name': self.name}
+ new_rt = db_api.raw_template_create(None, rt)
+
+ parent_stack = db_api.stack_get(self.stack.context, self.stack.id)
+
+ s = {'name': self.name,
+ 'owner_id': self.stack.id,
+ 'raw_template_id': new_rt.id,
+ 'user_creds_id': parent_stack.user_creds_id,
+ 'username': self.stack.context.username}
+ new_s = db_api.stack_create(None, s)
+ self._nested.id = new_s.id
+
+ pt = {'template': self._nested.t, 'raw_template_id': new_rt.id}
+ new_pt = db_api.parsed_template_create(None, pt)
+
+ self._nested.parsed_template_id = new_pt.id
+
+ self._nested.create()
+ self.instance_id_set(self._nested.id)
+
+ def handle_delete(self):
+ try:
+ stack = self.nested()
+ except exception.NotFound:
+ logger.info("Stack not found to delete")
+ else:
+ if stack is not None:
+ stack.delete()
+
+ def FnGetAtt(self, key):
+ if not key.startswith('Outputs.'):
+ raise exception.InvalidTemplateAttribute(resource=self.name,
+ key=key)
+
+ prefix, dot, op = key.partition('.')
+ stack = self.nested()
+ if stack is None:
+ # This seems like a hack, to get past validation
+ return ''
+ if op not in self.nested().outputs:
+ raise exception.InvalidTemplateAttribute(resource=self.name,
+ key=key)
+
+ return stack.output(op)