OSC plugin for orchestration template validate

This change implements "openstack orchestration template validate" command
Based from the existing heat commands:
   heat template-validate

This is different from the stack create/update --dry-run
which used the preview api, this uses the validate api.

Change-Id: Icf5794ad6bb35574a060f095d7ef10e6a46ca2fe
Blueprint: heat-support-python-openstackclient
This commit is contained in:
Mark Vanderwiel 2016-03-04 14:36:13 -06:00
parent f13d36ae01
commit 1d4c407fc5
7 changed files with 178 additions and 32 deletions

View File

@ -39,6 +39,19 @@ SENSITIVE_HEADERS = ('X-Auth-Token',)
osprofiler_web = importutils.try_import("osprofiler.web")
def authenticated_fetcher(hc):
"""A wrapper around the heat client object to fetch a template."""
def _do(*args, **kwargs):
if isinstance(hc.http_client, SessionClient):
method, url = args
return hc.http_client.request(url, method, **kwargs).content
else:
return hc.http_client.raw_request(*args, **kwargs).content
return _do
def get_system_ca_file():
"""Return path to system default CA file."""
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,

View File

@ -37,17 +37,6 @@ from heatclient.openstack.common._i18n import _
from heatclient.openstack.common._i18n import _LI
def _authenticated_fetcher(client):
def _do(*args, **kwargs):
if isinstance(client.http_client, http.SessionClient):
method, url = args
return client.http_client.request(url, method, **kwargs).content
else:
return client.http_client.raw_request(*args, **kwargs).content
return _do
class CreateStack(show.ShowOne):
"""Create a stack."""
@ -136,7 +125,7 @@ class CreateStack(show.ShowOne):
tpl_files, template = template_utils.process_template_path(
parsed_args.template,
object_request=_authenticated_fetcher(client))
object_request=http.authenticated_fetcher(client))
env_files, env = (
template_utils.process_multiple_environments_and_files(
@ -297,7 +286,7 @@ class UpdateStack(show.ShowOne):
tpl_files, template = template_utils.process_template_path(
parsed_args.template,
object_request=_authenticated_fetcher(client),
object_request=http.authenticated_fetcher(client),
existing=parsed_args.existing)
env_files, env = (

View File

@ -13,10 +13,16 @@
# Copyright 2015 IBM Corp.
import logging
import six
from cliff import lister
from openstackclient.common import utils
from heatclient.common import format_utils
from heatclient.common import http
from heatclient.common import template_utils
from heatclient.common import utils as heat_utils
from heatclient import exc
from heatclient.openstack.common._i18n import _
@ -72,3 +78,80 @@ class FunctionList(lister.Lister):
fields,
(utils.get_item_properties(s, fields) for s in functions)
)
class Validate(format_utils.YamlFormat):
"""Validate a template"""
log = logging.getLogger(__name__ + ".Validate")
def get_parser(self, prog_name):
parser = super(Validate, self).get_parser(prog_name)
parser.add_argument(
'-t', '--template',
metavar='<template>',
required=True,
help=_('Path to the template')
)
parser.add_argument(
'-e', '--environment',
metavar='<environment>',
action='append',
help=_('Path to the environment. Can be specified multiple times')
)
parser.add_argument(
'--show-nested',
action='store_true',
help=_('Resolve parameters from nested templates as well')
)
parser.add_argument(
'--parameter',
metavar='<key=value>',
action='append',
help=_('Parameter values used to create the stack. This can be '
'specified multiple times')
)
parser.add_argument(
'--ignore-errors',
metavar='<error1,error2,...>',
help=_('List of heat errors to ignore')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
heat_client = self.app.client_manager.orchestration
return _validate(heat_client, parsed_args)
def _validate(heat_client, args):
tpl_files, template = template_utils.process_template_path(
args.template,
object_request=http.authenticated_fetcher(heat_client))
env_files_list = []
env_files, env = template_utils.process_multiple_environments_and_files(
env_paths=args.environment, env_list_tracker=env_files_list)
fields = {
'template': template,
'parameters': heat_utils.format_parameters(args.parameter),
'files': dict(list(tpl_files.items()) + list(env_files.items())),
'environment': env,
}
if args.ignore_errors:
fields['ignore_errors'] = args.ignore_errors
# If one or more environments is found, pass the listing to the server
if env_files_list:
fields['environment_files'] = env_files_list
if args.show_nested:
fields['show_nested'] = args.show_nested
validation = heat_client.stacks.validate(**fields)
data = list(six.itervalues(validation))
columns = list(six.iterkeys(validation))
return columns, data

View File

@ -62,7 +62,6 @@ class TestStackCreate(TestStack):
return_value={'stack_status': 'create_complete'})
self.stack_client.preview = mock.MagicMock(
return_value=stacks.Stack(None, {'stack': {'id', '1234'}}))
stack._authenticated_fetcher = mock.MagicMock()
def test_stack_create_defaults(self):
arglist = ['my_stack', '-t', self.template_path]
@ -186,7 +185,6 @@ class TestStackUpdate(TestStack):
'updated': []}})
self.stack_client.get = mock.MagicMock(
return_value={'stack_status': 'create_complete'})
stack._authenticated_fetcher = mock.MagicMock()
def test_stack_update_defaults(self):
arglist = ['my_stack', '-t', self.template_path]

View File

@ -80,3 +80,76 @@ class TestTemplateFunctionList(TestTemplate):
parsed_args = self.check_parser(self.cmd, arglist, [])
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
class TestTemplateValidate(TestTemplate):
template_path = 'heatclient/tests/test_templates/empty.yaml'
env_path = 'heatclient/tests/unit/var/environment.json'
defaults = {
'environment': {},
'files': {},
'parameters': {},
'template': {'heat_template_version': '2013-05-23'}
}
def setUp(self):
super(TestTemplateValidate, self).setUp()
self.stack_client = self.app.client_manager.orchestration.stacks
self.stack_client.validate = mock.MagicMock(return_value={})
self.cmd = template.Validate(self.app, None)
def test_validate(self):
arglist = ['-t', self.template_path]
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.stack_client.validate.assert_called_once_with(**self.defaults)
self.assertEqual([], columns)
self.assertEqual([], data)
def test_validate_env(self):
arglist = ['-t', self.template_path, '-e', self.env_path]
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(1, self.stack_client.validate.call_count)
args = self.stack_client.validate.call_args[1]
self.assertEqual(args.get('environment'), {'parameters': {}})
self.assertTrue(self.env_path in args.get('environment_files')[0])
self.assertEqual([], columns)
self.assertEqual([], data)
def test_validate_nested(self):
arglist = ['-t', self.template_path, '--show-nested']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
args = dict(self.defaults)
args['show_nested'] = True
self.stack_client.validate.assert_called_once_with(**args)
self.assertEqual([], columns)
self.assertEqual([], data)
def test_validate_parameter(self):
arglist = ['-t', self.template_path,
'--parameter', 'key1=value1',
'--parameter', 'key2=value2']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
args = dict(self.defaults)
args['parameters'] = {'key1': 'value1', 'key2': 'value2'}
self.stack_client.validate.assert_called_once_with(**args)
self.assertEqual([], columns)
self.assertEqual([], data)
def test_validate_ignore_errors(self):
arglist = ['-t', self.template_path,
'--ignore-errors', 'err1,err2']
parsed_args = self.check_parser(self.cmd, arglist, [])
columns, data = self.cmd.take_action(parsed_args)
args = dict(self.defaults)
args['ignore_errors'] = 'err1,err2'
self.stack_client.validate.assert_called_once_with(**args)
self.assertEqual([], columns)
self.assertEqual([], data)

View File

@ -39,19 +39,6 @@ import heatclient.exc as exc
logger = logging.getLogger(__name__)
def _authenticated_fetcher(hc):
"""A wrapper around the heat client object to fetch a template."""
def _do(*args, **kwargs):
if isinstance(hc.http_client, http.SessionClient):
method, url = args
return hc.http_client.request(url, method, **kwargs).content
else:
return hc.http_client.raw_request(*args, **kwargs).content
return _do
def show_deprecated(deprecated, recommended):
logger.warning(_LW('"%(old)s" is deprecated, '
'please use "%(new)s" instead'),
@ -114,7 +101,7 @@ def do_stack_create(hc, args):
args.template_file,
args.template_url,
args.template_object,
_authenticated_fetcher(hc))
http.authenticated_fetcher(hc))
env_files_list = []
env_files, env = template_utils.process_multiple_environments_and_files(
env_paths=args.environment_file, env_list_tracker=env_files_list)
@ -269,7 +256,7 @@ def do_stack_preview(hc, args):
args.template_file,
args.template_url,
args.template_object,
_authenticated_fetcher(hc))
http.authenticated_fetcher(hc))
env_files_list = []
env_files, env = template_utils.process_multiple_environments_and_files(
env_paths=args.environment_file, env_list_tracker=env_files_list)
@ -518,7 +505,7 @@ def do_stack_update(hc, args):
args.template_file,
args.template_url,
args.template_object,
_authenticated_fetcher(hc),
http.authenticated_fetcher(hc),
existing=args.existing)
env_files_list = []
env_files, env = template_utils.process_multiple_environments_and_files(
@ -906,12 +893,14 @@ def do_template_show(hc, args):
help=_('List of heat errors to ignore.'))
def do_template_validate(hc, args):
"""Validate a template with parameters."""
show_deprecated('heat template-validate',
'openstack orchestration template validate')
tpl_files, template = template_utils.get_template_contents(
args.template_file,
args.template_url,
args.template_object,
_authenticated_fetcher(hc))
http.authenticated_fetcher(hc))
env_files_list = []
env_files, env = template_utils.process_multiple_environments_and_files(

View File

@ -33,6 +33,7 @@ openstack.orchestration.v1 =
orchestration_build_info = heatclient.osc.v1.build_info:BuildInfo
orchestration_service_list = heatclient.osc.v1.service:ListService
orchestration_template_function_list = heatclient.osc.v1.template:FunctionList
orchestration_template_validate = heatclient.osc.v1.template:Validate
orchestration_template_version_list = heatclient.osc.v1.template:VersionList
orchestration_resource_type_list = heatclient.osc.v1.resource_type:ResourceTypeList
orchestration_resource_type_show = heatclient.osc.v1.resource_type:ResourceTypeShow