Check the latest version of cloudbaseinit
We're hitting an API with the current version and we expect to receive a response if the current version is not the latest. This helps the user to see that a new version of cloudbaseinit is available, which is a big improvement than going to the github page to see the latest version. Change-Id: Ibfb721973c84474c8fef8e1989dfb7566938134f
This commit is contained in:
parent
5a42ef3b07
commit
bccc7d5215
@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from oslo.config import cfg
|
||||
@ -29,6 +30,10 @@ opts = [
|
||||
cfg.BoolOpt('stop_service_on_exit', default=True, help='In case of '
|
||||
'execution as a service, specifies if the service '
|
||||
'must be gracefully stopped before exiting'),
|
||||
cfg.BoolOpt('check_latest_version', default=True, help='Check if '
|
||||
'there is a newer version of cloudbase-init available. '
|
||||
'If this option is activated, a log message will be '
|
||||
'emitted if there is a newer version available.')
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -94,6 +99,13 @@ class InitManager(object):
|
||||
'supported' % plugin_name)
|
||||
return supported
|
||||
|
||||
@staticmethod
|
||||
def _check_latest_version():
|
||||
if CONF.check_latest_version:
|
||||
log_version = functools.partial(
|
||||
LOG.info, 'Found new version of cloudbase-init %s')
|
||||
version.check_latest_version(log_version)
|
||||
|
||||
def configure_host(self):
|
||||
LOG.info('Cloudbase-Init version: %s', version.get_version())
|
||||
|
||||
@ -104,6 +116,8 @@ class InitManager(object):
|
||||
LOG.info('Metadata service loaded: \'%s\'' %
|
||||
service.get_name())
|
||||
|
||||
self._check_latest_version()
|
||||
|
||||
instance_id = service.get_instance_id()
|
||||
LOG.debug('Instance id: %s', instance_id)
|
||||
|
||||
|
@ -136,6 +136,7 @@ class InitManagerTest(unittest.TestCase):
|
||||
def test_check_plugin_os_requirements_other_requirenments(self):
|
||||
self._test_check_plugin_os_requirements(('linux', (5, 2)))
|
||||
|
||||
@mock.patch('cloudbaseinit.init.InitManager._check_latest_version')
|
||||
@mock.patch('cloudbaseinit.version.get_version')
|
||||
@mock.patch('cloudbaseinit.init.InitManager'
|
||||
'._check_plugin_os_requirements')
|
||||
@ -147,7 +148,8 @@ class InitManagerTest(unittest.TestCase):
|
||||
mock_get_os_utils, mock_load_plugins,
|
||||
mock_exec_plugin,
|
||||
mock_check_os_requirements,
|
||||
mock_get_version, expected_logging,
|
||||
mock_get_version, mock_check_latest_version,
|
||||
expected_logging,
|
||||
version, name, instance_id, reboot=True):
|
||||
|
||||
mock_get_version.return_value = version
|
||||
@ -175,6 +177,7 @@ class InitManagerTest(unittest.TestCase):
|
||||
self.osutils.reboot.assert_called_once_with()
|
||||
else:
|
||||
self.assertFalse(self.osutils.reboot.called)
|
||||
mock_check_latest_version.assert_called_once_with()
|
||||
|
||||
def _test_configure_host_with_logging(self, extra_logging, reboot=True):
|
||||
instance_id = 'fake id'
|
||||
@ -209,3 +212,22 @@ class InitManagerTest(unittest.TestCase):
|
||||
def test_configure_host_reboot(self):
|
||||
self._test_configure_host_with_logging(
|
||||
extra_logging=['Rebooting'])
|
||||
|
||||
@testutils.ConfPatcher('check_latest_version', False)
|
||||
@mock.patch('cloudbaseinit.version.check_latest_version')
|
||||
def test_configure_host(self, mock_check_last_version):
|
||||
self._init._check_latest_version()
|
||||
|
||||
self.assertFalse(mock_check_last_version.called)
|
||||
|
||||
@testutils.ConfPatcher('check_latest_version', True)
|
||||
@mock.patch('functools.partial')
|
||||
@mock.patch('cloudbaseinit.version.check_latest_version')
|
||||
def test_configure_host_with_version_check(self, mock_check_last_version,
|
||||
mock_partial):
|
||||
self._init._check_latest_version()
|
||||
|
||||
mock_check_last_version.assert_called_once_with(
|
||||
mock_partial.return_value)
|
||||
mock_partial.assert_called_once_with(
|
||||
init.LOG.info, 'Found new version of cloudbase-init %s')
|
||||
|
@ -12,19 +12,99 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import importlib
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
from cloudbaseinit import version
|
||||
from cloudbaseinit.tests import testutils
|
||||
|
||||
|
||||
class TestVersion(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.version = importlib.import_module('cloudbaseinit.version')
|
||||
|
||||
@mock.patch('pbr.version.VersionInfo')
|
||||
def test_get_version(self, mock_version_info):
|
||||
package_version = version.get_version()
|
||||
package_version = self.version.get_version()
|
||||
|
||||
mock_version_info.assert_called_once_with('cloudbase-init')
|
||||
release_string = mock_version_info.return_value.release_string
|
||||
self.assertEqual(release_string.return_value, package_version)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
@mock.patch('json.loads')
|
||||
def test__read_url(self, mock_loads, mock_get):
|
||||
mock_url = mock.Mock()
|
||||
|
||||
result = self.version._read_url(mock_url)
|
||||
|
||||
headers = {'User-Agent': self.version._PRODUCT_NAME}
|
||||
mock_get.assert_called_once_with(mock_url, verify=six.PY3,
|
||||
headers=headers)
|
||||
request = mock_get.return_value
|
||||
request.raise_for_status.assert_called_once_with()
|
||||
mock_loads.assert_called_once_with(request.text)
|
||||
self.assertEqual(mock_loads.return_value, result)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test__read_url_empty_text(self, mock_get):
|
||||
mock_get.return_value.text = None
|
||||
|
||||
result = self.version._read_url(mock.Mock())
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch('threading.Thread')
|
||||
def test_check_latest_version(self, mock_thread):
|
||||
mock_callback = mock.Mock()
|
||||
|
||||
self.version.check_latest_version(mock_callback)
|
||||
|
||||
mock_thread.assert_called_once_with(
|
||||
target=self.version._check_latest_version,
|
||||
args=(mock_callback, ))
|
||||
thread = mock_thread.return_value
|
||||
thread.start.assert_called_once_with()
|
||||
|
||||
@mock.patch('cloudbaseinit.version._read_url')
|
||||
def test__check_latest_version(self, mock_read_url):
|
||||
mock_read_url.return_value = {'new_version': 42}
|
||||
mock_callback = mock.Mock()
|
||||
|
||||
self.version._check_latest_version(mock_callback)
|
||||
|
||||
mock_callback.assert_called_once_with(42)
|
||||
|
||||
@mock.patch('cloudbaseinit.version._read_url')
|
||||
def test__check_latest_version_fails(self, mock_read_url):
|
||||
mock_read_url.side_effect = Exception('no worky')
|
||||
mock_callback = mock.Mock()
|
||||
|
||||
with testutils.LogSnatcher('cloudbaseinit.version') as snatcher:
|
||||
self.version._check_latest_version(mock_callback)
|
||||
|
||||
expected_logging = ['Failed checking for new versions: no worky']
|
||||
self.assertEqual(expected_logging, snatcher.output)
|
||||
self.assertFalse(mock_callback.called)
|
||||
|
||||
@mock.patch('cloudbaseinit.version._read_url')
|
||||
def test__check_latest_version_no_content(self, mock_read_url):
|
||||
mock_read_url.return_value = None
|
||||
mock_callback = mock.Mock()
|
||||
|
||||
self.version._check_latest_version(mock_callback)
|
||||
|
||||
self.assertFalse(mock_callback.called)
|
||||
|
||||
@mock.patch('cloudbaseinit.version._read_url')
|
||||
def test__check_latest_version_no_new_version(self, mock_read_url):
|
||||
mock_read_url.return_value = {'new_versio': 42}
|
||||
mock_callback = mock.Mock()
|
||||
|
||||
result = self.version._check_latest_version(mock_callback)
|
||||
|
||||
self.assertFalse(mock_callback.called)
|
||||
self.assertIsNone(result)
|
||||
|
@ -12,7 +12,54 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import threading
|
||||
|
||||
import pbr.version
|
||||
import requests
|
||||
import six
|
||||
|
||||
from cloudbaseinit.openstack.common import log as logging
|
||||
|
||||
|
||||
_UPDATE_CHECK_URL = 'https://www.cloudbase.it/checkupdates.php?p={0}&v={1}'
|
||||
_PRODUCT_NAME = 'Cloudbase-Init'
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _read_url(url):
|
||||
# Disable certificate verification on Python 2 as
|
||||
# requests's CA list is incomplete. Works fine on Python3.
|
||||
req = requests.get(url, verify=six.PY3,
|
||||
headers={'User-Agent': _PRODUCT_NAME})
|
||||
req.raise_for_status()
|
||||
if req.text:
|
||||
return json.loads(req.text)
|
||||
|
||||
|
||||
def _check_latest_version(callback):
|
||||
product_version = get_version()
|
||||
url = _UPDATE_CHECK_URL.format(_PRODUCT_NAME, product_version)
|
||||
try:
|
||||
content = _read_url(url)
|
||||
if not content:
|
||||
return
|
||||
|
||||
version = content.get('new_version')
|
||||
if version:
|
||||
callback(version)
|
||||
|
||||
except Exception as exc:
|
||||
LOG.debug('Failed checking for new versions: %s', exc)
|
||||
return
|
||||
|
||||
|
||||
def check_latest_version(done_callback):
|
||||
"""Try to obtain the latest version of the product."""
|
||||
thread = threading.Thread(target=_check_latest_version,
|
||||
args=(done_callback, ))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
|
||||
def get_version():
|
||||
|
@ -10,3 +10,4 @@ oauthlib
|
||||
netifaces
|
||||
PyYAML
|
||||
tzlocal
|
||||
requests
|
Loading…
x
Reference in New Issue
Block a user