From 89fd1a83d282a10728077a08466627271a052733 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Wed, 1 Mar 2017 10:19:33 -0600 Subject: [PATCH] Fix regression in file.get_managed, add unit tests This is no longer needed since we're invoking the state module directly and not via the state compiler. * Fix regression in file.get_managed when skip_verify=True * Add integration tests for remote file sources * Remove next(iter()) extraction --- salt/modules/file.py | 6 +-- salt/states/archive.py | 11 ---- tests/integration/states/file.py | 105 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 14 deletions(-) diff --git a/salt/modules/file.py b/salt/modules/file.py index 8f0c6914b6..381800bc1a 100644 --- a/salt/modules/file.py +++ b/salt/modules/file.py @@ -3745,13 +3745,13 @@ def get_managed( if cached_dest and (source_hash or skip_verify): htype = source_sum.get('hash_type', 'sha256') cached_sum = get_hash(cached_dest, form=htype) - if cached_sum != source_sum['hsum']: - cache_refetch = True - elif skip_verify: + if skip_verify: # prev: if skip_verify or cached_sum == source_sum['hsum']: # but `cached_sum == source_sum['hsum']` is elliptical as prev if sfn = cached_dest source_sum = {'hsum': cached_sum, 'hash_type': htype} + elif cached_sum != source_sum['hsum']: + cache_refetch = True # If we didn't have the template or remote file, let's get it # Similarly when the file has been updated and the cache has to be refreshed diff --git a/salt/states/archive.py b/salt/states/archive.py index c5df213620..46146e971e 100644 --- a/salt/states/archive.py +++ b/salt/states/archive.py @@ -897,17 +897,6 @@ def extracted(name, ret['comment'] = '\n'.join([str(x) for x in file_result]) return ret - # Get actual state result. The state.single return is a single-element - # dictionary with the state's unique ID at the top level, and its value - # being the state's return dictionary. next(iter(dict_name)) will give - # us the value of the first key, so - # file_result[next(iter(file_result))] will give us the results of the - # state.single we just ran. - try: - file_result = file_result[next(iter(file_result))] - except AttributeError: - pass - try: if not file_result['result']: log.debug('failed to download {0}'.format(source_match)) diff --git a/tests/integration/states/file.py b/tests/integration/states/file.py index d63f318064..faa83d00e8 100644 --- a/tests/integration/states/file.py +++ b/tests/integration/states/file.py @@ -9,15 +9,22 @@ from __future__ import absolute_import from distutils.version import LooseVersion import errno import glob +import logging import os import re import sys import shutil +import socket import stat import tempfile import textwrap +import threading +import tornado.ioloop +import tornado.web import filecmp +log = logging.getLogger(__name__) + # Import 3rd-party libs from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin @@ -2392,6 +2399,104 @@ class FileTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn): if check_file: self.run_function('file.remove', [file]) + +PORT = 9999 +FILE_SOURCE = 'http://localhost:{0}/grail/scene33'.format(PORT) +FILE_HASH = 'd2feb3beb323c79fc7a0f44f1408b4a3' +STATE_DIR = os.path.join(integration.FILES, 'file', 'base') + + +class RemoteFileTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn): + ''' + Uses a local tornado webserver to test http(s) file.managed states with and + without skip_verify + ''' + @classmethod + def webserver(cls): + ''' + method to start tornado static web app + ''' + application = tornado.web.Application([ + (r'/(.*)', tornado.web.StaticFileHandler, {'path': STATE_DIR}) + ]) + application.listen(PORT) + tornado.ioloop.IOLoop.instance().start() + + @classmethod + def setUpClass(cls): + ''' + start tornado app on thread and wait until it is running + ''' + cls.server_thread = threading.Thread(target=cls.webserver) + cls.server_thread.daemon = True + cls.server_thread.start() + # check if tornado app is up + port_closed = True + while port_closed: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.connect_ex(('127.0.0.1', PORT)) + if result == 0: + port_closed = False + + @classmethod + def tearDownClass(cls): + tornado.ioloop.IOLoop.instance().stop() + cls.server_thread.join() + + def setUp(self): + fd_, self.name = tempfile.mkstemp(dir=integration.TMP) + try: + os.close(fd_) + except OSError as exc: + if exc.errno != errno.EBADF: + raise exc + # Remove the file that mkstemp just created so that the states can test + # creating a new file instead of a diff from a zero-length file. + self.tearDown() + + def tearDown(self): + try: + os.remove(self.name) + except OSError as exc: + if exc.errno != errno.ENOENT: + raise exc + + def test_file_managed_http_source_no_hash(self): + ''' + Test a remote file with no hash + ''' + ret = self.run_state('file.managed', + name=self.name, + source=FILE_SOURCE, + skip_verify=False) + log.debug('ret = %s', ret) + # This should fail because no hash was provided + self.assertSaltFalseReturn(ret) + + def test_file_managed_http_source(self): + ''' + Test a remote file with no hash + ''' + ret = self.run_state('file.managed', + name=self.name, + source=FILE_SOURCE, + source_hash=FILE_HASH, + skip_verify=False) + log.debug('ret = %s', ret) + self.assertSaltTrueReturn(ret) + + def test_file_managed_http_source_skip_verify(self): + ''' + Test a remote file using skip_verify + ''' + ret = self.run_state('file.managed', + name=self.name, + source=FILE_SOURCE, + skip_verify=True) + log.debug('ret = %s', ret) + self.assertSaltTrueReturn(ret) + + if __name__ == '__main__': from integration import run_tests run_tests(FileTest) -- 2.11.0