Skip to content

Commit

Permalink
Merge pull request #30483 from borgstrom/pyobjects_recursive-2015.8
Browse files Browse the repository at this point in the history
Pyobjects recursive import support (for 2015.8)
  • Loading branch information
Mike Place committed Jan 21, 2016
2 parents d8d19cf + 788b672 commit 119f025
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 53 deletions.
126 changes: 74 additions & 52 deletions salt/renderers/pyobjects.py
Expand Up @@ -261,6 +261,7 @@ class RedHat:
# Import Python Libs
from __future__ import absolute_import
import logging
import os
import re

# Import Salt Libs
Expand All @@ -284,6 +285,17 @@ class RedHat:
__context__ = {}


class PyobjectsModule(object):
'''This provides a wrapper for bare imports.'''

def __init__(self, name, attrs):
self.name = name
self.__dict__ = attrs

def __repr__(self):
return "<module '{0!s}' (pyobjects)>".format(self.name)


def load_states():
'''
This loads our states into the salt __context__
Expand Down Expand Up @@ -377,59 +389,69 @@ def render(template, saltenv='base', sls='', salt_data=True, **kwargs):
# so that they may bring in objects from other files. while we do this we
# disable the registry since all we're looking for here is python objects,
# not salt state data
template_data = []
Registry.enabled = False
for line in template.readlines():
line = line.rstrip('\r\n')
matched = False
for RE in (IMPORT_RE, FROM_RE):
matches = RE.match(line)
if not matches:
continue

import_file = matches.group(1).strip()
try:
imports = matches.group(2).split(',')
except IndexError:
# if we don't have a third group in the matches object it means
# that we're importing everything
imports = None

state_file = client.cache_file(import_file, saltenv)
if not state_file:
raise ImportError("Could not find the file {0!r}".format(import_file))

with salt.utils.fopen(state_file) as f:
state_contents = f.read()

state_locals = {}
exec_(state_contents, _globals, state_locals)

if imports is None:
imports = list(state_locals)

for name in imports:
name = alias = name.strip()

matches = FROM_AS_RE.match(name)
if matches is not None:
name = matches.group(1).strip()
alias = matches.group(2).strip()

if name not in state_locals:
raise ImportError("{0!r} was not found in {1!r}".format(
name,
import_file
))
_globals[alias] = state_locals[name]

matched = True
break

if not matched:
template_data.append(line)

final_template = "\n".join(template_data)

def process_template(template, template_globals):
template_data = []
state_globals = {}
for line in template.readlines():
line = line.rstrip('\r\n')
matched = False
for RE in (IMPORT_RE, FROM_RE):
matches = RE.match(line)
if not matches:
continue

import_file = matches.group(1).strip()
try:
imports = matches.group(2).split(',')
except IndexError:
# if we don't have a third group in the matches object it means
# that we're importing everything
imports = None

state_file = client.cache_file(import_file, saltenv)
if not state_file:
raise ImportError("Could not find the file {0!r}".format(import_file))

state_locals = {}
with salt.utils.fopen(state_file) as state_fh:
state_contents, state_locals = process_template(state_fh, template_globals)
exec_(state_contents, template_globals, state_locals)

# if no imports have been specified then we are being imported as: import salt://foo.sls
# so we want to stick all of the locals from our state file into the template globals
# under the name of the module -> i.e. foo.MapClass
if imports is None:
import_name = os.path.splitext(os.path.basename(state_file))[0]
state_globals[import_name] = PyobjectsModule(import_name, state_locals)
else:
for name in imports:
name = alias = name.strip()

matches = FROM_AS_RE.match(name)
if matches is not None:
name = matches.group(1).strip()
alias = matches.group(2).strip()

if name not in state_locals:
raise ImportError("{0!r} was not found in {1!r}".format(
name,
import_file
))
state_globals[alias] = state_locals[name]

matched = True
break

if not matched:
template_data.append(line)

return "\n".join(template_data), state_globals

# process the template that triggered the render
final_template, final_locals = process_template(template, _globals)
_globals.update(final_locals)

# re-enable the registry
Registry.enabled = True
Expand Down
36 changes: 35 additions & 1 deletion tests/unit/pyobjects_test.py
Expand Up @@ -83,9 +83,27 @@ class Ubuntu:
import_template = '''#!pyobjects
import salt://map.sls
Pkg.removed("samba-imported", names=[Samba.server, Samba.client])
Pkg.removed("samba-imported", names=[map.Samba.server, map.Samba.client])
'''

recursive_map_template = '''#!pyobjects
from salt://map.sls import Samba
class CustomSamba(Samba):
pass
'''

recursive_import_template = '''#!pyobjects
from salt://recursive_map.sls import CustomSamba
Pkg.removed("samba-imported", names=[CustomSamba.server, CustomSamba.client])'''

scope_test_import_template = '''#!pyobjects
from salt://recursive_map.sls import CustomSamba
# since we import CustomSamba we should shouldn't be able to see Samba
Pkg.removed("samba-imported", names=[Samba.server, Samba.client])'''

from_import_template = '''#!pyobjects
# this spacing is like this on purpose to ensure it's stripped properly
from salt://map.sls import Samba
Expand Down Expand Up @@ -326,6 +344,22 @@ def render_and_assert(template):
render_and_assert(from_import_template)
render_and_assert(import_as_template)

self.write_template_file("recursive_map.sls", recursive_map_template)
render_and_assert(recursive_import_template)

def test_import_scope(self):
self.write_template_file("map.sls", map_template)
self.write_template_file("recursive_map.sls", recursive_map_template)

def do_render():
ret = self.render(scope_test_import_template,
{'grains': {
'os_family': 'Debian',
'os': 'Debian'
}})

self.assertRaises(NameError, do_render)

def test_random_password(self):
'''Test for https://github.com/saltstack/salt/issues/21796'''
ret = self.render(random_password_template)
Expand Down

0 comments on commit 119f025

Please sign in to comment.