From: Angus Salkeld Date: Wed, 28 Mar 2012 08:36:18 +0000 (+1100) Subject: Add the ValidateTemplate API. X-Git-Tag: 2014.1~2142 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=3bc820fba48fd725a80c6bf0237b205121340ea9;p=openstack-build%2Fheat-build.git Add the ValidateTemplate API. Implementing the validate is still a TODO. Re: issue #1 Signed-off-by: Angus Salkeld --- diff --git a/bin/heat b/bin/heat index 1791bbce..3d480164 100755 --- a/bin/heat +++ b/bin/heat @@ -87,7 +87,18 @@ def catch_error(action): def template_validate(options, arguments): ''' ''' - pass + parameters = {} + if options.template_file: + parameters['TemplateBody'] = open(options.template_file).read() + elif options.template_url: + parameters['TemplateUrl'] = options.template_url + else: + print 'Please specify a template file or url' + return FAILURE + + c = get_client(options) + result = c.validate_template(**parameters) + print json.dumps(result, indent=2) @catch_error('gettemplate') def get_template(options, arguments): diff --git a/heat/api/v1/__init__.py b/heat/api/v1/__init__.py index a906f169..8ce18894 100644 --- a/heat/api/v1/__init__.py +++ b/heat/api/v1/__init__.py @@ -48,5 +48,7 @@ class API(wsgi.Router): action="update", conditions=dict(method=["PUT"])) mapper.connect("/DescribeStackEvents", controller=stacks_resource, action="events_list", conditions=dict(method=["GET"])) + mapper.connect("/ValidateTemplate", controller=stacks_resource, + action="validate_template", conditions=dict(method=["GET"])) super(API, self).__init__(mapper) diff --git a/heat/api/v1/stacks.py b/heat/api/v1/stacks.py index 2e88faf2..2b195e66 100644 --- a/heat/api/v1/stacks.py +++ b/heat/api/v1/stacks.py @@ -123,6 +123,28 @@ class StackController(object): return c.create_stack(stack, **req.params) + def validate_template(self, req): + + c = engine.get_engine_client(req.context) + + try: + templ = self._get_template(req) + except socket.gaierror: + msg = _('Invalid Template URL') + return webob.exc.HTTPBadRequest(explanation=msg) + if templ is None: + msg = _("TemplateBody or TemplateUrl were not given.") + return webob.exc.HTTPBadRequest(explanation=msg) + + try: + stack = json.loads(templ) + except ValueError: + msg = _("The Template must be a JSON document.") + return webob.exc.HTTPBadRequest(explanation=msg) + + logger.info('validate_template') + return c.validate_template(stack, **req.params) + def delete(self, req): """ Returns the following information for all stacks: diff --git a/heat/client.py b/heat/client.py index 1d75337c..fae1a3e8 100644 --- a/heat/client.py +++ b/heat/client.py @@ -88,6 +88,14 @@ class V1Client(base_client.BaseClient): data = json.loads(res.read()) return data + def validate_template(self, **kwargs): + params = self._extract_params(kwargs, SUPPORTED_PARAMS) + self._insert_common_parameters(params) + + res = self.do_request("GET", "/ValidateTemplate", params=params) + data = json.loads(res.read()) + return data + HeatClient = V1Client diff --git a/heat/engine/api/v1/__init__.py b/heat/engine/api/v1/__init__.py index 170f7af8..cb1960a7 100644 --- a/heat/engine/api/v1/__init__.py +++ b/heat/engine/api/v1/__init__.py @@ -29,6 +29,9 @@ class API(wsgi.Router): mapper.resource("stack", "stacks", controller=stacks_resource, collection={'detail': 'GET'}) mapper.connect("/", controller=stacks_resource, action="index") + mapper.connect("/validate_template", controller=stacks_resource, + action="validate_template", conditions=dict(method=["POST"])) + events_resource = events.create_resource(conf) mapper.resource("event", "events", controller=events_resource, diff --git a/heat/engine/api/v1/stacks.py b/heat/engine/api/v1/stacks.py index fb5e5945..99f82da6 100644 --- a/heat/engine/api/v1/stacks.py +++ b/heat/engine/api/v1/stacks.py @@ -100,6 +100,18 @@ class StacksController(object): return {'stack': {'id': body['StackName']}} + def validate_template(self, req, body=None): + + logger.info('validate_template') + if body is None: + msg = _("No Template provided.") + return webob.exc.HTTPBadRequest(explanation=msg) + + s = parser.Stack('validate', body, req.params) + res = s.validate() + + return res + def delete(self, req, id): if not stack_db.has_key(id): return webob.exc.HTTPNotFound('No stack by that name') diff --git a/heat/engine/client.py b/heat/engine/client.py index 4ccad480..2bae31db 100644 --- a/heat/engine/client.py +++ b/heat/engine/client.py @@ -87,6 +87,20 @@ class EngineClient(BaseClient): data = json.loads(res.read())['stacks'] return data + def validate_template(self, template, **kwargs): + """ + Validate the template + """ + headers = { + 'Content-Type': 'application/json', + } + + logger.info(template) + res = self.do_request("POST", "/validate_template", body=json.dumps(template), + headers=headers, params=kwargs) + data = json.loads(res.read()) + logger.info(data) + return data def create_stack(self, template, **kwargs): """ diff --git a/heat/engine/parser.py b/heat/engine/parser.py index c8757d4d..e9fd1be9 100644 --- a/heat/engine/parser.py +++ b/heat/engine/parser.py @@ -20,7 +20,6 @@ from heat.engine import resources logger = logging.getLogger('heat.engine.parser') - class Stack: def __init__(self, stack_name, template, parms=None): @@ -62,6 +61,34 @@ class Stack: self.calulate_dependancies(self.t['Resources'][r], self.resources[r]) + def validate(self): + ''' + If you are wondering where the actual validation is, me too. + it is just not obvious how to respond to validation failures. + http://docs.amazonwebservices.com/AWSCloudFormation/latest/APIReference/API_ValidateTemplate.html + ''' + response = { 'ValidateTemplateResult': { + 'Description': 'bla', + 'Parameters': [] + } + } + + for p in self.parms: + jp = {'member': {}} + res = jp['member'] + res['NoEcho'] = 'false' + res['ParameterKey'] = p + if self.parms[p].has_key('Description'): + res['Description'] = self.parms[p]['Description'] + else: + res['Description'] = '' + if self.parms[p].has_key('Default'): + res['DefaultValue'] = self.parms[p]['Default'] + else: + res['DefaultValue'] = '' + response['ValidateTemplateResult']['Parameters'].append(res) + return response + def start(self): # start Volumes first. for r in self.t['Resources']: @@ -91,16 +118,12 @@ class Stack: for index, item in enumerate(s): self.calulate_dependancies(item, r) - def _apply_user_parameter(self, key, value): - logger.info('_apply_user_parameter %s=%s ' % (key, value)) - if not self.t.has_key('Parameters'): - self.t['Parameters'] = {} - - if not self.t['Parameters'].has_key(key): - self.t['Parameters'][key] = {} + logger.debug('appling user parameter %s=%s ' % (key, value)) - self.t['Parameters'][key]['Value'] = value + if not self.parms.has_key(key): + self.parms[key] = {} + self.parms[key]['Value'] = value def _apply_user_parameters(self, parms): for p in parms: @@ -111,21 +134,19 @@ class Stack: value_name = 'Parameters.member.%s.ParameterValue' % s[2] self._apply_user_parameter(parms[key_name], parms[value_name]) except: - logger.error('could not apply parameter %s' % p) - + logger.error('Could not apply parameter %s' % p) def parameter_get(self, key): if self.parms[key] == None: - #print 'None Ref: %s' % key - return '=EMPTY=' + logger.warn('Trying to reference parameter: %s, but it is empty' % key) + return '' elif self.parms[key].has_key('Value'): return self.parms[key]['Value'] elif self.parms[key].has_key('Default'): return self.parms[key]['Default'] else: - #print 'Missing Ref: %s' % key - return '=EMPTY=' - + logger.warn('Trying to reference parameter: %s, but no Value or Default' % key) + return '' def resolve_static_refs(self, s): '''