Add files-container option for stack create and update

If files-container option is specified:

- All template/env files would be fetched by the heat engine
  relative to the files_container and no local files other
  than the root template would be sent to server.
- Relative path of environment files would be sent in the
  environment_files list.

Also adds the option to template validate.

Change-Id: I1a703ab8798a003365be650886bb78be5af472b7
Story: #1755453
Task: 19319
This commit is contained in:
rabi
2018-03-12 07:52:33 +05:30
parent 38e03316b1
commit bd2bfaa514
5 changed files with 97 additions and 29 deletions

View File

@@ -27,7 +27,8 @@ from heatclient.common import utils
from heatclient import exc from heatclient import exc
def process_template_path(template_path, object_request=None, existing=False): def process_template_path(template_path, object_request=None,
existing=False, fetch_child=True):
"""Read template from template path. """Read template from template path.
Attempt to read template first as a file or url. If that is unsuccessful, Attempt to read template first as a file or url. If that is unsuccessful,
@@ -37,17 +38,20 @@ def process_template_path(template_path, object_request=None, existing=False):
:param object_request: custom object request function used to get template :param object_request: custom object request function used to get template
if local or uri path fails if local or uri path fails
:param existing: if the current stack's template should be used :param existing: if the current stack's template should be used
:param fetch_child: Whether to fetch the child templates
:returns: get_file dict and template contents :returns: get_file dict and template contents
:raises: error.URLError :raises: error.URLError
""" """
try: try:
return get_template_contents(template_file=template_path, return get_template_contents(template_file=template_path,
existing=existing) existing=existing,
fetch_child=fetch_child)
except error.URLError as template_file_exc: except error.URLError as template_file_exc:
try: try:
return get_template_contents(template_object=template_path, return get_template_contents(template_object=template_path,
object_request=object_request, object_request=object_request,
existing=existing) existing=existing,
fetch_child=fetch_child)
except exc.HTTPNotFound: except exc.HTTPNotFound:
# The initial exception gives the user better failure context. # The initial exception gives the user better failure context.
raise template_file_exc raise template_file_exc
@@ -55,7 +59,8 @@ def process_template_path(template_path, object_request=None, existing=False):
def get_template_contents(template_file=None, template_url=None, def get_template_contents(template_file=None, template_url=None,
template_object=None, object_request=None, template_object=None, object_request=None,
files=None, existing=False): files=None, existing=False,
fetch_child=True):
is_object = False is_object = False
# Transform a bare file path to a file:// URL. # Transform a bare file path to a file:// URL.
@@ -93,12 +98,13 @@ def get_template_contents(template_file=None, template_url=None,
except ValueError as e: except ValueError as e:
raise exc.CommandError(_('Error parsing template %(url)s %(error)s') % raise exc.CommandError(_('Error parsing template %(url)s %(error)s') %
{'url': template_url, 'error': e}) {'url': template_url, 'error': e})
tmpl_base_url = utils.base_url_for_url(template_url)
if files is None: if files is None:
files = {} files = {}
resolve_template_get_files(template, files, tmpl_base_url, is_object,
object_request) if fetch_child:
tmpl_base_url = utils.base_url_for_url(template_url)
resolve_template_get_files(template, files, tmpl_base_url, is_object,
object_request)
return files, template return files, template
@@ -212,7 +218,8 @@ def process_multiple_environments_and_files(env_paths=None, template=None,
template_url=None, template_url=None,
env_path_is_object=None, env_path_is_object=None,
object_request=None, object_request=None,
env_list_tracker=None): env_list_tracker=None,
fetch_env_files=True):
"""Reads one or more environment files. """Reads one or more environment files.
Reads in each specified environment file and returns a dictionary Reads in each specified environment file and returns a dictionary
@@ -239,6 +246,7 @@ def process_multiple_environments_and_files(env_paths=None, template=None,
:type env_list_tracker: list or None :type env_list_tracker: list or None
:return: tuple of files dict and a dict of the consolidated environment :return: tuple of files dict and a dict of the consolidated environment
:rtype: tuple :rtype: tuple
:param fetch_env_files: fetch env_files or leave it to server
""" """
merged_files = {} merged_files = {}
merged_env = {} merged_env = {}
@@ -249,24 +257,28 @@ def process_multiple_environments_and_files(env_paths=None, template=None,
if env_paths: if env_paths:
for env_path in env_paths: for env_path in env_paths:
files, env = process_environment_and_files( if fetch_env_files:
env_path=env_path, files, env = process_environment_and_files(
template=template, env_path=env_path,
template_url=template_url, template=template,
env_path_is_object=env_path_is_object, template_url=template_url,
object_request=object_request, env_path_is_object=env_path_is_object,
include_env_in_files=include_env_in_files) object_request=object_request,
include_env_in_files=include_env_in_files)
# 'files' looks like {"filename1": contents, "filename2": contents} # 'files' looks like:
# so a simple update is enough for merging # {"filename1": contents, "filename2": contents}
merged_files.update(files) # so a simple update is enough for merging
merged_files.update(files)
# 'env' can be a deeply nested dictionary, so a simple update is # 'env' can be a deeply nested dictionary, so a simple
# not enough # update is not enough
merged_env = deep_update(merged_env, env) merged_env = deep_update(merged_env, env)
env_url = utils.normalise_file_path_to_url(env_path)
else:
env_url = env_path
if env_list_tracker is not None: if env_list_tracker is not None:
env_url = utils.normalise_file_path_to_url(env_path)
env_list_tracker.append(env_url) env_list_tracker.append(env_url)
return merged_files, merged_env return merged_files, merged_env

View File

@@ -46,6 +46,13 @@ class CreateStack(command.ShowOne):
action='append', action='append',
help=_('Path to the environment. Can be specified multiple times') help=_('Path to the environment. Can be specified multiple times')
) )
parser.add_argument(
'-s', '--files-container',
metavar='<files-container>',
help=_('Swift files container name. Local files other than '
'root template would be ignored. If other files are not '
'found in swift, heat engine would raise an error.')
)
parser.add_argument( parser.add_argument(
'--timeout', '--timeout',
metavar='<timeout>', metavar='<timeout>',
@@ -121,13 +128,15 @@ class CreateStack(command.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=http.authenticated_fetcher(client)) object_request=http.authenticated_fetcher(client),
fetch_child=parsed_args.files_container is None)
env_files_list = [] env_files_list = []
env_files, env = ( env_files, env = (
template_utils.process_multiple_environments_and_files( template_utils.process_multiple_environments_and_files(
env_paths=parsed_args.environment, env_paths=parsed_args.environment,
env_list_tracker=env_files_list)) env_list_tracker=env_files_list,
fetch_env_files=parsed_args.files_container is None))
parameters = heat_utils.format_all_parameters( parameters = heat_utils.format_all_parameters(
parsed_args.parameter, parsed_args.parameter,
@@ -151,6 +160,9 @@ class CreateStack(command.ShowOne):
if env_files_list: if env_files_list:
fields['environment_files'] = env_files_list fields['environment_files'] = env_files_list
if parsed_args.files_container:
fields['files_container'] = parsed_args.files_container
if parsed_args.tags: if parsed_args.tags:
fields['tags'] = parsed_args.tags fields['tags'] = parsed_args.tags
if parsed_args.timeout: if parsed_args.timeout:
@@ -201,6 +213,13 @@ class UpdateStack(command.ShowOne):
'-t', '--template', metavar='<template>', '-t', '--template', metavar='<template>',
help=_('Path to the template') help=_('Path to the template')
) )
parser.add_argument(
'-s', '--files-container',
metavar='<files-container>',
help=_('Swift files container name. Local files other than '
'root template would be ignored. If other files are not '
'found in swift, heat engine would raise an error.')
)
parser.add_argument( parser.add_argument(
'-e', '--environment', metavar='<environment>', '-e', '--environment', metavar='<environment>',
action='append', action='append',
@@ -298,13 +317,15 @@ class UpdateStack(command.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=http.authenticated_fetcher(client), object_request=http.authenticated_fetcher(client),
existing=parsed_args.existing) existing=parsed_args.existing,
fetch_child=parsed_args.files_container is None)
env_files_list = [] env_files_list = []
env_files, env = ( env_files, env = (
template_utils.process_multiple_environments_and_files( template_utils.process_multiple_environments_and_files(
env_paths=parsed_args.environment, env_paths=parsed_args.environment,
env_list_tracker=env_files_list)) env_list_tracker=env_files_list,
fetch_env_files=parsed_args.files_container is None))
parameters = heat_utils.format_all_parameters( parameters = heat_utils.format_all_parameters(
parsed_args.parameter, parsed_args.parameter,
@@ -328,6 +349,9 @@ class UpdateStack(command.ShowOne):
if env_files_list: if env_files_list:
fields['environment_files'] = env_files_list fields['environment_files'] = env_files_list
if parsed_args.files_container:
fields['files_container'] = parsed_args.files_container
if parsed_args.tags: if parsed_args.tags:
fields['tags'] = parsed_args.tags fields['tags'] = parsed_args.tags
if parsed_args.timeout: if parsed_args.timeout:

View File

@@ -122,6 +122,13 @@ class Validate(format_utils.YamlFormat):
help=_('Parameter values used to create the stack. This can be ' help=_('Parameter values used to create the stack. This can be '
'specified multiple times') 'specified multiple times')
) )
parser.add_argument(
'-s', '--files-container',
metavar='<files-container>',
help=_('Swift files container name. Local files other than '
'root template would be ignored. If other files are not '
'found in swift, heat engine would raise an error.')
)
parser.add_argument( parser.add_argument(
'--ignore-errors', '--ignore-errors',
metavar='<error1,error2,...>', metavar='<error1,error2,...>',
@@ -145,11 +152,13 @@ class Validate(format_utils.YamlFormat):
def _validate(heat_client, args): def _validate(heat_client, args):
tpl_files, template = template_utils.process_template_path( tpl_files, template = template_utils.process_template_path(
args.template, args.template,
object_request=http.authenticated_fetcher(heat_client)) object_request=http.authenticated_fetcher(heat_client),
fetch_child=args.files_container is None)
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, env_list_tracker=env_files_list) env_paths=args.environment, env_list_tracker=env_files_list,
fetch_env_files=args.files_container is None)
fields = { fields = {
'template': template, 'template': template,
@@ -168,6 +177,9 @@ def _validate(heat_client, args):
if args.show_nested: if args.show_nested:
fields['show_nested'] = args.show_nested fields['show_nested'] = args.show_nested
if args.files_container:
fields['files_container'] = args.files_container
validation = heat_client.stacks.validate(**fields) validation = heat_client.stacks.validate(**fields)
data = list(six.itervalues(validation)) data = list(six.itervalues(validation))
columns = list(six.iterkeys(validation)) columns = list(six.iterkeys(validation))

View File

@@ -127,6 +127,18 @@ class ShellEnvironmentTest(testtools.TestCase):
mock.call('file:///home/my/dir/a.yaml') mock.call('file:///home/my/dir/a.yaml')
]) ])
def test_process_multiple_environment_files_container(self):
env_list_tracker = []
env_paths = ['/home/my/dir/env.yaml']
files, env = template_utils.process_multiple_environments_and_files(
env_paths, env_list_tracker=env_list_tracker,
fetch_env_files=False)
self.assertEqual(env_paths, env_list_tracker)
self.assertEqual({}, files)
self.assertEqual({}, env)
@mock.patch('six.moves.urllib.request.urlopen') @mock.patch('six.moves.urllib.request.urlopen')
def test_process_environment_relative_file_up(self, mock_url): def test_process_environment_relative_file_up(self, mock_url):

View File

@@ -0,0 +1,8 @@
---
features:
- |
Adds ``files-container`` option for stack create, update and
template validate with openstackclient. If specified, no local
files other than root template would be sent to heat engine.
Heat engine would try and download the all other files relative
to the ``files-container``, else raise an error.