Fix get_file in out-of-tree templates

We already have special processing in place for out-of-tree environment
files and templates, but it didn't handle `get_file` (or `type`)
links. This commit adds that handling.

Heatclient automatically uses absolute `file:///` links when processing
the external environment files and other files referenced from them via
`get_file` or `type`. Because we upload all our environment files and
templates to Swift, such links don't work. We need to use relative links
in `get_file` and `type`.

Change-Id: I009f75cbc6278a0a2ff75e93e1ed44f2c4893783
Closes-Bug: #1631426
This commit is contained in:
Jiri Stransky 2016-10-07 18:29:46 +02:00
parent 4215712bce
commit 73f30b7286
3 changed files with 144 additions and 9 deletions

View File

@ -19,6 +19,7 @@ import mock
import os.path import os.path
import tempfile import tempfile
from unittest import TestCase from unittest import TestCase
import yaml
from tripleoclient import exceptions from tripleoclient import exceptions
from tripleoclient.tests.v1.utils import ( from tripleoclient.tests.v1.utils import (
@ -718,3 +719,69 @@ class TestAssignVerifyProfiles(TestCase):
self.nodes[:] = [self._get_fake_node(profile=None)] self.nodes[:] = [self._get_fake_node(profile=None)]
self.flavors = {'baremetal': (FakeFlavor('baremetal', None), 1)} self.flavors = {'baremetal': (FakeFlavor('baremetal', None), 1)}
self._test(0, 0) self._test(0, 0)
class TestReplaceLinks(TestCase):
def setUp(self):
super(TestReplaceLinks, self).setUp()
self.link_replacement = {
'file:///home/stack/test.sh':
'user-files/home/stack/test.sh',
'file:///usr/share/extra-templates/my.yml':
'user-files/usr/share/extra-templates/my.yml',
}
def test_replace_links(self):
source = (
'description: my template\n'
'heat_template_version: "2014-10-16"\n'
'resources:\n'
' test_config:\n'
' properties:\n'
' config: {get_file: "file:///home/stack/test.sh"}\n'
' type: OS::Heat::SoftwareConfig\n'
)
expected = (
'description: my template\n'
'heat_template_version: "2014-10-16"\n'
'resources:\n'
' test_config:\n'
' properties:\n'
' config: {get_file: user-files/home/stack/test.sh}\n'
' type: OS::Heat::SoftwareConfig\n'
)
# the yaml->string dumps aren't always character-precise, so
# we need to parse them into dicts for comparison
expected_dict = yaml.safe_load(expected)
result_dict = yaml.safe_load(utils.replace_links_in_template_contents(
source, self.link_replacement))
self.assertEqual(expected_dict, result_dict)
def test_replace_links_not_template(self):
# valid JSON/YAML, but doesn't have heat_template_version
source = '{"get_file": "file:///home/stack/test.sh"}'
self.assertEqual(
source,
utils.replace_links_in_template_contents(
source, self.link_replacement))
def test_replace_links_not_yaml(self):
# invalid JSON/YAML -- curly brace left open
source = '{"invalid JSON"'
self.assertEqual(
source,
utils.replace_links_in_template_contents(
source, self.link_replacement))
def test_relative_link_replacement(self):
current_dir = 'user-files/home/stack'
expected = {
'file:///home/stack/test.sh':
'test.sh',
'file:///usr/share/extra-templates/my.yml':
'../../usr/share/extra-templates/my.yml',
}
self.assertEqual(expected, utils.relative_link_replacement(
self.link_replacement, current_dir))

View File

@ -900,3 +900,69 @@ def parse_env_file(env_file, file_type=None):
nodes_config = nodes_config['nodes'] nodes_config = nodes_config['nodes']
return nodes_config return nodes_config
def replace_links_in_template_contents(contents, link_replacement):
"""Replace get_file and type file links in Heat template contents
If the string contents passed in is a Heat template, scan the
template for 'get_file' and 'type' occurences, and replace the
file paths according to link_replacement dict. (Key/value in
link_replacement are from/to, respectively.)
If the string contents don't look like a Heat template, return the
contents unmodified.
"""
template = {}
try:
template = yaml.safe_load(contents)
except yaml.YAMLError:
return contents
if not (isinstance(template, dict) and
template.get('heat_template_version')):
return contents
template = replace_links_in_template(template, link_replacement)
return yaml.safe_dump(template)
def replace_links_in_template(template_part, link_replacement):
"""Replace get_file and type file links in a Heat template
Scan the template for 'get_file' and 'type' occurences, and
replace the file paths according to link_replacement
dict. (Key/value in link_replacement are from/to, respectively.)
"""
def replaced_dict_value(key, value):
if ((key == 'get_file' or key == 'type') and
isinstance(value, six.string_types)):
return link_replacement.get(value, value)
else:
return replace_links_in_template(value, link_replacement)
def replaced_list_value(value):
return replace_links_in_template(value, link_replacement)
if isinstance(template_part, dict):
return {k: replaced_dict_value(k, v)
for k, v in six.iteritems(template_part)}
elif isinstance(template_part, list):
return map(replaced_list_value, template_part)
else:
return template_part
def relative_link_replacement(link_replacement, current_dir):
"""Generate a relative version of link_replacement dictionary.
Get a link_replacement dictionary (where key/value are from/to
respectively), and make the values in that dictionary relative
paths with respect to current_dir.
"""
return {k: os.path.relpath(v, current_dir)
for k, v in six.iteritems(link_replacement)}

View File

@ -16,7 +16,6 @@ from __future__ import print_function
import argparse import argparse
import glob import glob
import hashlib
import logging import logging
import os import os
import os.path import os.path
@ -360,7 +359,8 @@ class DeployOvercloud(command.Command):
file_relocation = {} file_relocation = {}
file_prefix = "file://" file_prefix = "file://"
for fullpath, contents in files_dict.items(): # select files files for relocation & upload
for fullpath in files_dict.keys():
if not fullpath.startswith(file_prefix): if not fullpath.startswith(file_prefix):
continue continue
@ -371,13 +371,15 @@ class DeployOvercloud(command.Command):
# This should already be uploaded. # This should already be uploaded.
continue continue
filename = os.path.basename(path) file_relocation[fullpath] = "user-files/{}".format(path[1:])
checksum = hashlib.md5()
checksum.update(path) # make sure links within files point to new locations, and upload them
digest = checksum.hexdigest() for orig_path, reloc_path in file_relocation.items():
swift_path = "user-files/{}-{}".format(digest, filename) link_replacement = utils.relative_link_replacement(
swift_client.put_object(container_name, swift_path, contents) file_relocation, os.path.dirname(reloc_path))
file_relocation[fullpath] = swift_path contents = utils.replace_links_in_template_contents(
files_dict[orig_path], link_replacement)
swift_client.put_object(container_name, reloc_path, contents)
return file_relocation return file_relocation