diff --git a/salt/config.py b/salt/config.py index bca8e22adb..c7c4715014 100644 --- a/salt/config.py +++ b/salt/config.py @@ -2121,4 +2121,4 @@ def api_config(path): # Let's override them with salt-api's required defaults defaults.update(DEFAULT_API_OPTS) - return master_config(path, defaults=defaults) + return client_config(path, defaults=defaults) diff --git a/salt/modules/hashutil.py b/salt/modules/hashutil.py new file mode 100644 index 0000000000..07b81ada11 --- /dev/null +++ b/salt/modules/hashutil.py @@ -0,0 +1,131 @@ +# encoding: utf-8 +''' +A collection of hashing and encoding functions +''' +import base64 +import hashlib +import hmac + + +def base64_encode(infile, outfile): + ''' + Encode a file as base64 and write the result to a new file + + .. versionadded:: Helium + + CLI Example: + + .. code-block:: + + salt '*' hashutil.base64_encode /path/to/file1 /path/to/encoded_file1 + ''' + return base64.encode(infile, outfile) + + +def base64_decode(infile, outfile): + ''' + Decode a base64-encoded file and write the result to a new file + + .. versionadded:: Helium + + CLI Example: + + .. code-block:: bash + + salt '*' hashutil.base64_decode /path/to/encoded_file1 /path/to/file1 + ''' + return base64.decode(infile, outfile) + + +def base64_encodestring(instr): + ''' + Encode a string as base64 + + .. versionadded:: Helium + + CLI Example: + + .. code-block:: bash + + salt '*' hashutil.base64_encodestring 'get salted' + ''' + return base64.encodestring(instr) + + +def base64_decodestring(instr): + ''' + Decode a base64-encoded string + + .. versionadded:: Helium + + CLI Example: + + .. code-block:: bash + + salt '*' hashutil.base64_decodestring 'Z2V0IHNhbHRlZA==\n' + ''' + return base64.decodestring(instr) + + +def md5_digest(instr): + ''' + Generate an md5 hash of a given string + + .. versionadded:: Helium + + CLI Example: + + .. code-block:: bash + + salt '*' hashutil.md5_digest 'get salted' + ''' + return hashlib.md5(instr).hexdigest() + + +def sha256_digest(instr): + ''' + Generate an sha256 hash of a given string + + .. versionadded:: Helium + + CLI Example: + + .. code-block:: bash + + salt '*' hashutil.sha256_digest 'get salted' + ''' + return hashlib.sha256(instr).hexdigest() + + +def sha512_digest(instr): + ''' + Generate an sha512 hash of a given string + + .. versionadded:: Helium + + CLI Example: + + .. code-block:: bash + + salt '*' hashutil.sha512_digest 'get salted' + ''' + return hashlib.sha512(instr).hexdigest() + + +def hmac_signature(string, shared_secret, challenge_hmac): + ''' + Verify a challenging hmac signature against a string / shared-secret + + .. versionadded:: Helium + + Returns a boolean if the verification succeeded or failed. + + CLI Example: + + .. code-block:: bash + + salt '*' hashutil.hmac_signature 'get salted' 'shared secret' 'NS2BvKxFRk+rndAlFbCYIFNVkPtI/3KiIYQw4okNKU8=' + ''' + hmac_hash = hmac.new(string, shared_secret, hashlib.sha256) + valid_hmac = base64.b64encode(hmac_hash.digest()) + return valid_hmac == challenge_hmac diff --git a/salt/modules/network.py b/salt/modules/network.py index 0dd0ac4aa6..cb7aa6b1c8 100644 --- a/salt/modules/network.py +++ b/salt/modules/network.py @@ -12,6 +12,7 @@ import socket # Import salt libs import salt.utils +import salt.utils.network from salt.exceptions import CommandExecutionError import salt.utils.validate.net @@ -704,3 +705,29 @@ def connect(host, port=None, **kwargs): ret['result'] = True ret['comment'] = 'Successfully connected to {0} ({1}) on {2} port {3}'.format(host, _address[0], proto, port) return ret + + +def is_private(ip_addr): + ''' + Check if the given IP address is a private address + + .. versionadded:: Helium + + CLI Example:: + + salt '*' network.is_private 10.0.0.3 + ''' + return salt.utils.network.IPv4Address(ip_addr).is_private + + +def is_loopback(ip_addr): + ''' + Check if the given IP address is a loopback address + + .. versionadded:: Helium + + CLI Example:: + + salt '*' network.is_loopback 127.0.0.1 + ''' + return salt.utils.network.IPv4Address(ip_addr).is_loopback diff --git a/salt/netapi/__init__.py b/salt/netapi/__init__.py index 5c023e0c16..068abbf743 100644 --- a/salt/netapi/__init__.py +++ b/salt/netapi/__init__.py @@ -22,16 +22,12 @@ class NetapiClient(object): Provide a uniform method of accessing the various client interfaces in Salt in the form of low-data data structures. For example: - >>> client = NetapiClient() + >>> client = NetapiClient(__opts__) >>> lowstate = {'client': 'local', 'tgt': '*', 'fun': 'test.ping', 'arg': ''} >>> client.run(lowstate) - - :param mopts: A copy of the client_config dictionary if already loaded into memory. - :param opts: Ignored; preserved for backward-compat ''' - def __init__(self, opts=None, mopts=None): - self.opts = mopts or salt.config.client_config(os.path.join( - salt.syspaths.CONFIG_DIR, 'master')) + def __init__(self, opts): + self.opts = opts def run(self, low): ''' diff --git a/salt/renderers/jinja.py b/salt/renderers/jinja.py index ded1a1ccf2..1f52cd4bad 100644 --- a/salt/renderers/jinja.py +++ b/salt/renderers/jinja.py @@ -221,6 +221,12 @@ import salt.utils.templates log = logging.getLogger(__name__) +class SaltDotLookup(dict): + def __init__(self, *args, **kwargs): + dict.__init__(self, *args, **kwargs) + self.__dict__ = self + + def _split_module_dicts(__salt__): ''' Create a dictionary from module.function as module[function] @@ -231,10 +237,11 @@ def _split_module_dicts(__salt__): {{ salt.cmd.run('uptime') }} ''' - mod_dict = {} + mod_dict = SaltDotLookup() for module_func_name in __salt__.keys(): mod, _, fun = module_func_name.partition('.') - mod_dict.setdefault(mod, {})[fun] = __salt__[module_func_name] + mod_dict.setdefault(mod, + SaltDotLookup())[fun] = __salt__[module_func_name] return mod_dict @@ -252,9 +259,8 @@ def render(template_file, saltenv='base', sls='', argline='', 'Unknown renderer option: {opt}'.format(opt=argline) ) - salt_dict = {} - salt_dict.update(_split_module_dicts(__salt__)) - salt_dict.update(__salt__) + salt_dict = SaltDotLookup(**__salt__) + salt_dict.__dict__.update(_split_module_dicts(__salt__)) tmp_data = salt.utils.templates.JINJA(template_file, to_str=True, diff --git a/tests/integration/netapi/__init__.py b/tests/integration/netapi/__init__.py index b13b966ce7..5dc6c5b6a4 100644 --- a/tests/integration/netapi/__init__.py +++ b/tests/integration/netapi/__init__.py @@ -24,7 +24,7 @@ class NetapiClientTest(TestCase): Set up a NetapiClient instance ''' opts = salt.config.client_config(os.path.join(TMP_CONF_DIR, 'master')) - self.netapi = salt.netapi.NetapiClient(mopts=opts) + self.netapi = salt.netapi.NetapiClient(opts) def test_local(self): low = {'client': 'local', 'tgt': '*', 'fun': 'test.ping'} diff --git a/tests/unit/modules/hashutil_test.py b/tests/unit/modules/hashutil_test.py new file mode 100644 index 0000000000..b253e3af88 --- /dev/null +++ b/tests/unit/modules/hashutil_test.py @@ -0,0 +1,49 @@ +# coding: utf-8 +import salt.loader + +from tests.integration import ModuleCase + + +class HashutilTestCase(ModuleCase): + the_string = 'get salted' + the_string_base64 = 'Z2V0IHNhbHRlZA==\n' + the_string_md5 = '2aacf29e92feaf528fb738bcf9d647ac' + the_string_sha256 = 'd49859ccbc854fa68d800b5734efc70d72383e6479d545468bc300263164ff33' + the_string_sha512 = 'a8c174a7941c64a068e686812a2fafd7624c840fde800f5965fbeca675f2f6e37061ffe41e17728c919bdea290eab7a21e13c04ae71661955a87f2e0e04bb045' + the_string_hmac = 'NS2BvKxFRk+rndAlFbCYIFNVkPtI/3KiIYQw4okNKU8=' + + def setUp(self): + self.hashutil = salt.loader.raw_mod(self.minion_opts, 'hashutil', None) + + def test_base64_encodestring(self): + ret = self.hashutil['hashutil.base64_encodestring'](self.the_string) + self.assertEqual(ret, self.the_string_base64) + + def test_base64_decodestring(self): + ret = self.hashutil['hashutil.base64_decodestring'](self.the_string_base64) + self.assertEqual(ret, self.the_string) + + def test_md5_digest(self): + ret = self.hashutil['hashutil.md5_digest'](self.the_string) + self.assertEqual(ret, self.the_string_md5) + + def test_sha256_digest(self): + ret = self.hashutil['hashutil.sha256_digest'](self.the_string) + self.assertEqual(ret, self.the_string_sha256) + + def test_sha512_digest(self): + ret = self.hashutil['hashutil.sha512_digest'](self.the_string) + self.assertEqual(ret, self.the_string_sha512) + + def test_hmac_signature(self): + ret = self.hashutil['hashutil.hmac_signature']( + self.the_string, + 'shared secret', + self.the_string_hmac) + self.assertTrue(ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(HashutilTestCase, + needs_daemon=False) diff --git a/tests/unit/modules/test_network.py b/tests/unit/modules/test_network.py new file mode 100644 index 0000000000..7569fcb2a7 --- /dev/null +++ b/tests/unit/modules/test_network.py @@ -0,0 +1,19 @@ +# coding: utf-8 +import salt.loader + +from tests.integration import ModuleCase + + +class NetworkUtilsTestCase(ModuleCase): + def test_is_private(self): + __salt__ = salt.loader.raw_mod(self.minion_opts, 'network', None) + self.assertTrue(__salt__['network.is_private']('10.0.0.1'), True) + + def test_is_loopback(self): + __salt__ = salt.loader.raw_mod(self.minion_opts, 'network', None) + self.assertTrue(__salt__['network.is_loopback']('127.0.0.1'), True) + +if __name__ == '__main__': + from integration import run_tests + run_tests(NetworkUtilsTestCase, + needs_daemon=False) diff --git a/tests/unit/templates/jinja_test.py b/tests/unit/templates/jinja_test.py index 6a0c51da15..6f1078281d 100644 --- a/tests/unit/templates/jinja_test.py +++ b/tests/unit/templates/jinja_test.py @@ -8,11 +8,13 @@ import datetime import pprint # Import Salt Testing libs +from tests.integration import ModuleCase from salttesting import skipIf, TestCase from salttesting.helpers import ensure_in_syspath ensure_in_syspath('../../') # Import salt libs +import salt.loader import salt.utils from salt.exceptions import SaltRenderError from salt.utils import get_context @@ -631,7 +633,51 @@ class TestCustomExtensions(TestCase): # return +class TestDotNotationLookup(ModuleCase): + ''' + Tests to call Salt functions via Jinja with various lookup syntaxes + ''' + def setUp(self, *args, **kwargs): + functions = { + 'mocktest.ping': lambda: True, + 'mockgrains.get': lambda x: 'jerry', + } + render = salt.loader.render(self.minion_opts, functions) + self.jinja = render.get('jinja') + + def render(self, tmpl_str, context=None): + return self.jinja(tmpl_str, context=context or {}, from_str=True).read() + + def test_normlookup(self): + ''' + Sanity-check the normal dictionary-lookup syntax for our stub function + ''' + tmpl_str = '''Hello, {{ salt['mocktest.ping']() }}.''' + + ret = self.render(tmpl_str) + self.assertEqual(ret, 'Hello, True.') + + def test_dotlookup(self): + ''' + Check calling a stub function using awesome dot-notation + ''' + tmpl_str = '''Hello, {{ salt.mocktest.ping() }}.''' + + ret = self.render(tmpl_str) + self.assertEqual(ret, 'Hello, True.') + + def test_shadowed_dict_method(self): + ''' + Check calling a stub function with a name that shadows a ``dict`` + method name + ''' + tmpl_str = '''Hello, {{ salt.mockgrains.get('id') }}.''' + + ret = self.render(tmpl_str) + self.assertEqual(ret, 'Hello, jerry.') + if __name__ == '__main__': from integration import run_tests run_tests(TestSaltCacheLoader, TestGetTemplate, TestCustomExtensions, + TestDotNotationLookup, needs_daemon=False)