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:
parent
f13d36ae01
commit
1d4c407fc5
|
@ -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,
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue