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:
@@ -39,6 +39,19 @@ SENSITIVE_HEADERS = ('X-Auth-Token',)
|
|||||||
osprofiler_web = importutils.try_import("osprofiler.web")
|
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():
|
def get_system_ca_file():
|
||||||
"""Return path to system default CA file."""
|
"""Return path to system default CA file."""
|
||||||
# Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
|
# 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
|
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):
|
class CreateStack(show.ShowOne):
|
||||||
"""Create a stack."""
|
"""Create a stack."""
|
||||||
|
|
||||||
@@ -136,7 +125,7 @@ class CreateStack(show.ShowOne):
|
|||||||
|
|
||||||
tpl_files, template = template_utils.process_template_path(
|
tpl_files, template = template_utils.process_template_path(
|
||||||
parsed_args.template,
|
parsed_args.template,
|
||||||
object_request=_authenticated_fetcher(client))
|
object_request=http.authenticated_fetcher(client))
|
||||||
|
|
||||||
env_files, env = (
|
env_files, env = (
|
||||||
template_utils.process_multiple_environments_and_files(
|
template_utils.process_multiple_environments_and_files(
|
||||||
@@ -297,7 +286,7 @@ class UpdateStack(show.ShowOne):
|
|||||||
|
|
||||||
tpl_files, template = template_utils.process_template_path(
|
tpl_files, template = template_utils.process_template_path(
|
||||||
parsed_args.template,
|
parsed_args.template,
|
||||||
object_request=_authenticated_fetcher(client),
|
object_request=http.authenticated_fetcher(client),
|
||||||
existing=parsed_args.existing)
|
existing=parsed_args.existing)
|
||||||
|
|
||||||
env_files, env = (
|
env_files, env = (
|
||||||
|
@@ -13,10 +13,16 @@
|
|||||||
# Copyright 2015 IBM Corp.
|
# Copyright 2015 IBM Corp.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
from cliff import lister
|
from cliff import lister
|
||||||
from openstackclient.common import utils
|
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 import exc
|
||||||
from heatclient.openstack.common._i18n import _
|
from heatclient.openstack.common._i18n import _
|
||||||
|
|
||||||
@@ -72,3 +78,80 @@ class FunctionList(lister.Lister):
|
|||||||
fields,
|
fields,
|
||||||
(utils.get_item_properties(s, fields) for s in functions)
|
(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'})
|
return_value={'stack_status': 'create_complete'})
|
||||||
self.stack_client.preview = mock.MagicMock(
|
self.stack_client.preview = mock.MagicMock(
|
||||||
return_value=stacks.Stack(None, {'stack': {'id', '1234'}}))
|
return_value=stacks.Stack(None, {'stack': {'id', '1234'}}))
|
||||||
stack._authenticated_fetcher = mock.MagicMock()
|
|
||||||
|
|
||||||
def test_stack_create_defaults(self):
|
def test_stack_create_defaults(self):
|
||||||
arglist = ['my_stack', '-t', self.template_path]
|
arglist = ['my_stack', '-t', self.template_path]
|
||||||
@@ -186,7 +185,6 @@ class TestStackUpdate(TestStack):
|
|||||||
'updated': []}})
|
'updated': []}})
|
||||||
self.stack_client.get = mock.MagicMock(
|
self.stack_client.get = mock.MagicMock(
|
||||||
return_value={'stack_status': 'create_complete'})
|
return_value={'stack_status': 'create_complete'})
|
||||||
stack._authenticated_fetcher = mock.MagicMock()
|
|
||||||
|
|
||||||
def test_stack_update_defaults(self):
|
def test_stack_update_defaults(self):
|
||||||
arglist = ['my_stack', '-t', self.template_path]
|
arglist = ['my_stack', '-t', self.template_path]
|
||||||
|
@@ -80,3 +80,76 @@ class TestTemplateFunctionList(TestTemplate):
|
|||||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||||
|
|
||||||
self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args)
|
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__)
|
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):
|
def show_deprecated(deprecated, recommended):
|
||||||
logger.warning(_LW('"%(old)s" is deprecated, '
|
logger.warning(_LW('"%(old)s" is deprecated, '
|
||||||
'please use "%(new)s" instead'),
|
'please use "%(new)s" instead'),
|
||||||
@@ -114,7 +101,7 @@ def do_stack_create(hc, args):
|
|||||||
args.template_file,
|
args.template_file,
|
||||||
args.template_url,
|
args.template_url,
|
||||||
args.template_object,
|
args.template_object,
|
||||||
_authenticated_fetcher(hc))
|
http.authenticated_fetcher(hc))
|
||||||
env_files_list = []
|
env_files_list = []
|
||||||
env_files, env = template_utils.process_multiple_environments_and_files(
|
env_files, env = template_utils.process_multiple_environments_and_files(
|
||||||
env_paths=args.environment_file, env_list_tracker=env_files_list)
|
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_file,
|
||||||
args.template_url,
|
args.template_url,
|
||||||
args.template_object,
|
args.template_object,
|
||||||
_authenticated_fetcher(hc))
|
http.authenticated_fetcher(hc))
|
||||||
env_files_list = []
|
env_files_list = []
|
||||||
env_files, env = template_utils.process_multiple_environments_and_files(
|
env_files, env = template_utils.process_multiple_environments_and_files(
|
||||||
env_paths=args.environment_file, env_list_tracker=env_files_list)
|
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_file,
|
||||||
args.template_url,
|
args.template_url,
|
||||||
args.template_object,
|
args.template_object,
|
||||||
_authenticated_fetcher(hc),
|
http.authenticated_fetcher(hc),
|
||||||
existing=args.existing)
|
existing=args.existing)
|
||||||
env_files_list = []
|
env_files_list = []
|
||||||
env_files, env = template_utils.process_multiple_environments_and_files(
|
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.'))
|
help=_('List of heat errors to ignore.'))
|
||||||
def do_template_validate(hc, args):
|
def do_template_validate(hc, args):
|
||||||
"""Validate a template with parameters."""
|
"""Validate a template with parameters."""
|
||||||
|
show_deprecated('heat template-validate',
|
||||||
|
'openstack orchestration template validate')
|
||||||
|
|
||||||
tpl_files, template = template_utils.get_template_contents(
|
tpl_files, template = template_utils.get_template_contents(
|
||||||
args.template_file,
|
args.template_file,
|
||||||
args.template_url,
|
args.template_url,
|
||||||
args.template_object,
|
args.template_object,
|
||||||
_authenticated_fetcher(hc))
|
http.authenticated_fetcher(hc))
|
||||||
|
|
||||||
env_files_list = []
|
env_files_list = []
|
||||||
env_files, env = template_utils.process_multiple_environments_and_files(
|
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_build_info = heatclient.osc.v1.build_info:BuildInfo
|
||||||
orchestration_service_list = heatclient.osc.v1.service:ListService
|
orchestration_service_list = heatclient.osc.v1.service:ListService
|
||||||
orchestration_template_function_list = heatclient.osc.v1.template:FunctionList
|
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_template_version_list = heatclient.osc.v1.template:VersionList
|
||||||
orchestration_resource_type_list = heatclient.osc.v1.resource_type:ResourceTypeList
|
orchestration_resource_type_list = heatclient.osc.v1.resource_type:ResourceTypeList
|
||||||
orchestration_resource_type_show = heatclient.osc.v1.resource_type:ResourceTypeShow
|
orchestration_resource_type_show = heatclient.osc.v1.resource_type:ResourceTypeShow
|
||||||
|
Reference in New Issue
Block a user